Skip to content
miroslaw edited this page Sep 29, 2026 · 1 revision

Memory

Two address-space domains: the kernel's, and each process's. They share the top-level page table entry PML4[0] (kernel), while PML4[1..255] are per-process.

Layout

USER_VA_BASE + 0x00000000   ELF image load_base .. +load_size
                            then heap / SYS_MAP allocations
USER_VA_BASE + 0x00100000   USER_STACK_TOP      (ring-3 selftest)
USER_VA_BASE + 0x01000000   USER_STACK_TOP_INIT (init and children)
USER_VA_BASE + 0x02000000   INITRD_VA           (read-only archive)

USER_VA_BASE = 0x0000008000000000 — 512 GiB, so user mappings live in PML4[1]. USER_VA_TOP = 0x0000800000000000 — the canonical lower half.

Physical memory

pmm_alloc_page() returns a 4 KiB physical page, or 0 on exhaustion. pmm_free_page() returns it. The allocator does not track page ownership; whoever allocated a page is responsible for freeing it.

pmm_free_page_count() is used as a leak check in the boot tests: the count is captured before and after the userspace phase and the two must match.

Virtual memory

vmm_create()              -> fresh address space (refcount 1)
vmm_switch(space)         -> load CR3
vmm_map(space, va, pa, flags)
vmm_unmap(space, va)
vmm_query(space, va, &pa, &flags)
vmm_space_ref(space)
vmm_space_unref(space)

Flags:

VM_PRESENT     (1 << 0)   must be set
VM_WRITE       (1 << 1)
VM_USER        (1 << 2)   required for user mappings
VM_NOCACHE     (1 << 3)

Address spaces are refcounted. A process owns one reference; each thread given the space holds its own. When the refcount drops to zero, the whole PML4[1..511] subtree is walked and every leaf page and intermediate table is freed.

User memory access

Kernel code never dereferences user VAs directly. Two options:

  • copy_to_user / copy_from_user / strncpy_from_user — with a pre-check (user_range_ok) plus extable-based fault recovery. A page fault at the copy returns -EFAULT instead of panicking.
  • Bounce buffers — for short syscall arguments, copy into a kernel buffer first, then work on the kernel copy.

user_range_ok(space, va, n) verifies that [va, va+n) lies entirely below USER_VA_TOP and that every page is mapped with VM_USER. It does not touch memory.

The kernel heap

KERNEL_HEAP_BASE = KERNEL_VA_BASE + 0x00100000
KERNEL_HEAP_MAX  = KERNEL_VA_BASE + 0x10000000

Grows by mapping pages into the kernel space as needed. Pages are never returned to the PMM — the free list keeps the VA mapped and usable for later allocations.

heap_reserve(bytes) grows the heap until at least bytes of payload is available in the free list, without allocating anything yet. The kernel calls heap_reserve(HEAP_BOOT_RESERVE) at boot (4 MiB + 256 KiB) so that the largest single allocation the kernel can need in normal operation — the SPAWN_ELF bounce buffer — never triggers a heap growth in the middle of userspace execution. That would show up as pmm-free drift and fail the leak check.

libc allocator (userspace)

The libc malloc maps one 4 MiB arena with SYS_MAP on first use and manages it as a boundary-tag heap. free coalesces but does not return pages to the kernel. realloc grows in place when the next block is free, otherwise allocates and copies.

Clone this wiki locally