Files
frostbyte/include/mmap.h
T
2026-09-01 20:48:46 +02:00

121 lines
6.8 KiB
C

#ifndef FROSTBYTE_MMAP_H
#define FROSTBYTE_MMAP_H
#include "types.h"
#include "fs.h"
struct Thread;
/* Cap on live mappings per thread. Small and fixed like every other
per-Thread table in this kernel (maxOpenFiles=16, sigMaxSignals=32) --
nothing here needs more than a handful of distinct mappings at once, and
a hard cap keeps Vma[] a plain fixed-size array embedded directly in
Thread (sched.h), exactly like FileDescriptor fds[maxOpenFiles] already
is, rather than something that needs its own allocator. */
#define maxVmasPerThread 8
/* One live mapping inside a single thread's own elfSlotSize slot (see
elf.h) -- mmap.c reserves [start, end) at mmapReserve time but allocates
no physical frames and reads no file data yet; mmapFaultHandler below
fills each page lazily, the first time a real #PF (isr.c) proves it was
actually touched. `fd` (meaningful only when `anonymous` is false) names
a slot in the OWNING thread's own fd table (vfs.h) -- this mapping is
only ever resolved for its own owning thread (mmapFaultHandler's own
contract), so there is no cross-thread fd ambiguity. Unlike a real
kernel's struct vm_area_struct, there is no backing inode reference
independent of that fd: closing `fd` before every page has been faulted
in leaves this mapping unable to fill the rest (mmapFaultHandler treats
a since-closed fd as a fault it cannot resolve, turning into a real
SIGSEGV rather than silently serving zeroed pages for wrong reasons) --
an honestly scoped limitation (real mmap(2) lets a mapping outlive the
fd that created it), not a hidden bug, chosen because holding a
reference to a file independent of any thread's fd table would need a
refcounted VfsNode/inode layer this kernel does not otherwise have. */
typedef struct Vma {
bool used;
uint64 start;
uint64 end;
uint32 prot;
bool anonymous;
int fd;
uint64 fileOffset;
} Vma;
/* Reserves [addr, addr+length) -- or, when addr is 0, a first-fit region
of `length` bytes the kernel picks itself, real mmap(NULL, ...)-style --
inside `thread`'s own mmap region ([userSlotBase+elfSlotStackOffset,
userSlotBase+elfSlotSize), see elf.h: the 128 MiB of every elfSlotSize
slot that sits above the small fixed-size stack and is never otherwise
touched by an ELF image or brk). No physical frame is allocated and no
byte is read yet -- see mmapFaultHandler for when that actually happens.
On success, writes the mapping's real base address to *outAddr (matters
when addr was 0) and returns true. Returns false, touching *outAddr not
at all, if: length is 0, addr or length is not frameSize-aligned, a
non-anonymous mapping's fileOffset is not frameSize-aligned, the
resulting range would fall outside the mmap region, it would overlap an
existing Vma of this same thread, no addr was given and no free region
of that size exists, or every one of maxVmasPerThread's own slots is
already in use. */
bool mmapReserve(struct Thread* thread, uint64 addr, uint64 length, uint32 prot,
bool anonymous, int fd, uint64 fileOffset, uint64* outAddr);
/* Real munmap(2): removes every Vma of `thread` covering any part of
[addr, addr+length), giving back to the pmm whatever physical frames
were actually faulted in for the removed range (pagingUnmapUserRange,
paging.h -- the same "only what is actually present costs anything"
contract that function already documents for itself). A length that only
overlaps one end of an existing Vma shrinks it in place; a length that
lands entirely inside one Vma's interior, touching neither end, splits
it into two (consuming one more Vma slot for the upper half) -- since
Vmas of the same thread never overlap each other (mmapReserve's own
refusal), at most one Vma can ever need splitting per call, so this
either finds the one free slot it needs or refuses the WHOLE call up
front, leaving every Vma of this thread completely untouched, rather
than leaving the caller's mapping table half-modified by a resource
failure it could not have predicted from addr/length alone. A range that
overlaps no Vma of this thread at all is a no-op returning true, real
munmap(2)'s own "unmapping already-unmapped memory is not an error"
behavior. Returns false only for a misaligned addr, a zero length, or
the above split-with-no-free-slot case. */
bool mmapRelease(struct Thread* thread, uint64 addr, uint64 length);
/* Looks up the (at most one, since a thread's own Vmas never overlap each
other) Vma of `thread` covering `addr`, or nullptr if none does. */
Vma* mmapFind(struct Thread* thread, uint64 addr);
/* The real page-fault resolver: called by isr.c for a ring-3 #PF whose
faulting address might belong to `thread`'s own mmap region. Looks up
the covering Vma (mmapFind); returns false, mapping nothing, if there is
none, or if `forWrite` is true but the Vma's own prot lacks
mmapProtWrite (isr.c turns either case into a real SIGSEGV, see its own
comment) or (mmapProtRead is missing on a read fault, refused the same
way). Otherwise allocates one physical frame, fills it -- zeroed for an
anonymous Vma; for a file-backed one, read from the Vma's own `fd`
starting at the file offset that corresponds to this page (whatever
vfsRead returns short of a full frameSize read, e.g. a page straddling
end-of-file, is left zero-padded, since the frame was already
zero-filled before the read) -- maps it at the containing page with
pageUser | (mmapProtWrite set ? pageWrite : 0), and returns true so
isr.c can simply retry the faulting instruction, exactly like any other
successfully resolved demand-paging fault. Only ever meaningful when
`thread` is the CURRENTLY RUNNING thread (it reads/restores that
thread's own fd offset through vfsRead/vfsLseek, which resolve fd
numbers against currentThread() -- true for both isr.c's real caller and
mmapTest.c's own KTEST coverage, which runs entirely on the idle thread,
itself always currentThread() while KTEST executes). */
bool mmapFaultHandler(struct Thread* thread, uint64 faultAddr, bool forWrite);
/* Zeroes every Vma of `thread` without touching any physical frame or
page-table entry -- callers that already reclaim frames some other way
(doExec's own pagingUnmapUserRange over the relevant range, syscall.c)
call this afterward purely to stop a later mmapReserve on the same
thread from ever finding a stale Vma left over from an image this
thread no longer runs. threadReap/threadDiscard (sched.c) do NOT need
to call this themselves: both already memset() the whole Thread struct
before a slot is handed back to allocSlot, which zeroes the embedded
Vma[] array as a side effect, exactly like every other per-thread table
in it. */
void mmapReleaseAll(struct Thread* thread);
#endif