# The physical frame allocator Once the kernel knows what RAM exists ([memory-map.md](memory-map.md)), it needs a way to *hand out* that RAM: give me a free page of physical memory, and later, here's one back. That's the **physical frame allocator** (a "physical memory manager", hence `system/kernel/pmm.zig`). It deals only in fixed 4 KiB **frames** — the natural unit because that's the granularity the CPU's paging hardware maps — and it is the primitive everything above it stands on: page tables, the kernel heap, per-process memory all ultimately ask the frame allocator for pages. It's **generic kernel code**: it operates on the neutral `system.MemoryRegion` array, so there's no UEFI in it and nothing architecture-specific beyond the 4 KiB page. (Contrast [arch.md](arch.md), which is where CPU-specific code lives.) ## Why a bitmap There are a few classic designs; danos starts with the simplest that still supports freeing: - **Bitmap** (chosen): one bit per frame, `1 = used`, `0 = free`. Freeing is trivial (clear a bit), it's very compact, and you can later extend it to allocate *contiguous* runs by scanning for consecutive zero bits. Allocation is a linear scan, but that's cheap and easy to reason about. - **Intrusive free-list / stack**: store the "next free frame" pointer inside each free frame; O(1) alloc and free. Elegant, but it can't satisfy contiguous multi-frame requests and can't answer "is *this* frame free?". - **Buddy allocator**: great for contiguous power-of-two blocks, but more machinery than a first allocator needs. Compactness matters less than clarity here, but it's a nice property: 128 MiB of RAM is 32768 frames — a **4 KiB bitmap, a single frame**. Even 64 GiB needs only 2 MiB of bitmap. ## How it works State lives in `system/kernel/pmm.zig`: the `bitmap` slice, `total_frames`, `used_frames`, and a `next_hint` marking where the next allocation scan should start. ### init(map) — building it from the memory map 1. **Size it.** Find the highest address across all `usable` regions; `total_frames = highest / page_size`. Reserved and MMIO spans above that (remember the ~12 GiB of MMIO from [memory-map.md](memory-map.md)) sit *outside* the bitmap and are simply never allocatable. 2. **Place it (the bootstrap).** The bitmap needs storage before an allocator exists — a chicken-and-egg. Solution: pick the first `usable` region big enough to hold the bitmap and put it there, addressing it directly as a pointer. That last part relies on the firmware's **identity mapping** still being in effect (physical address == virtual address), which holds until the kernel installs its own page tables. 3. **Mark, then free.** Set the whole bitmap to `used` (`0xff`), then walk the `usable` regions clearing their bits. Doing it in that direction means every gap, reserved span, and hole is unallocatable *by default* — we only ever hand back memory the firmware explicitly called usable. 4. **Take back the essentials.** Re-reserve the frames the bitmap itself occupies (they're inside a usable region we just freed), plus **frame 0**, so an address of `0` can keep meaning "no frame". ### alloc() → ?u64 Scan the bitmap from `next_hint` (wrapping once) for the first free bit, mark it used, advance the hint, and return `frame * page_size`. Returns `null` when no frame is free — genuine out-of-memory. The hint avoids rescanning the low, long-since-allocated frames on every call. ### free(addr) Clear the frame's bit and, if it's below `next_hint`, pull the hint back so the reclaimed frame gets reused soon. Bogus or double frees (a frame already marked free, or one out of range) are ignored rather than corrupting the used count. ## Correctness points worth remembering - **Generic walk.** Because `MemoryRegion` is danos's own type, the map is a plain slice — none of the variable descriptor-stride from the raw UEFI map. - **Identity mapping assumption.** Placing the bitmap by physical address only works while the firmware's identity map is live. When danos sets up its own paging, the bitmap (and any other physical pointer) will need an explicit mapping. This is a deliberate, documented dependency of this stage. - **Frame 0 is reserved** so `0` stays a safe "none" sentinel — and the bitmap is never placed there. (An early bug did exactly that: a `usable` region at physical address 0 collided with a `0`-means-not-found sentinel and tripped a panic. The fix was an optional plus starting the bitmap at least one page in.) - **Everything non-usable is unallocatable by construction** — the "mark all used, then free usable" order gives that for free, so the kernel image, the loader's buffers, MMIO and firmware memory can never be handed out. ## Verifying it `kmain` brings the allocator up and self-tests it. Booted in QEMU with 128 MiB: ``` danos: frame allocator online free frames: 19751 (77 MiB) <- matches the map's 77 MiB usable alloc x3 : 0x2000 0x3000 0x4000 <- frame 0 reserved, bitmap at 0x1000, so allocs start at 0x2000 after free : 19751 frames free <- three freed, count restored ``` The `free frames` MiB agreeing with the memory map's `usable RAM`, the three distinct consecutive addresses, and the count returning to its start after freeing are the three signals that init, alloc and free are all correct. ## Boot-services memory comes pre-reclaimed The UEFI boot-services memory (~44 MiB) is defunct and free once `ExitBootServices` runs, taking usable RAM from ~76 MiB up to ~121 MiB. The frame allocator does **nothing special** to get it: the loader already classified it as `usable` (see [memory-map.md](memory-map.md)), so it's just part of the `usable` regions `init` frees. Keeping that boot-protocol knowledge on the loader side is deliberate — the kernel has no notion of "reclaimable" or of UEFI at all. The one live piece in that memory is the boot stack the kernel starts on; the loader leaves the single region containing it `reserved`, so `init` won't hand it out. A later step will move task 0 onto a kernel-owned stack, freeing that last ~1 MiB region too (and giving user mode the clean stack it wants). ## What's next (not done here) - **Contiguous allocation** — scan for N consecutive free bits — for callers that need physically adjacent frames. - **A kernel stack for task 0**, so the boot stack's region can be freed too (and for the clean stack user mode wants). - **Freeing the `reserved` `loader_data`** (the boot-time map buffers) once the kernel is done reading the memory map.