This note collects the decisions we have made around memory management in meniOS and the current state of the implementation. The focus is to document the boot-time virtual address layout, the physical allocator, and how user processes consume address space today (and what is planned next).
- meniOS boots via Limine. We request the following services and use their responses during memory bring-up:
- Memory map (
LIMINE_MEMMAP_REQUEST) to enumerate usable, reserved, framebuffer, and other regions. - Higher-Half Direct Map (HHDM) (
LIMINE_HHDM_REQUEST) so the kernel can directly address all physical memory using a single offset (kernel_offset).
- Memory map (
- The bootloader leaves the kernel running in long mode with a 64-bit page table already active. We keep that mapping, but normalise our own bookkeeping via
init_cr3().
- The PMM is a bitmap allocator defined in
src/kernel/mem/pmm.c. - Page size is 4 KiB; the bitmap is sized by
PAGE_BITMAP_SIZE(seeinclude/kernel/pmm.h). Each bit tracks the availability of one physical page. - During
pmm_initialize()we:- Initialise the bitmap marking everything used (
init_page_bitmap). - Iterate Limine's memory map and mark usable ranges as free (
bulk_page_bitmap_as_free). - Capture the HHDM offset so we can translate physical↔virtual addresses via
physical_to_virtual()/virtual_to_physical(). - Record the kernel's current CR3 so we know the original PML4 and can clone it for new address spaces.
- Initialise the bitmap marking everything used (
- Allocation helpers (
pmm_alloc_pages,pmm_free_pages) operate on contiguous runs of pages. There is no buddy allocator yet; the bitmap search is linear. - DMA-aware helpers (
pmm_alloc_aligned_pages) can reserve contiguous spans under a caller-specified physical ceiling and alignment, used bydma_buffer_allocto provide device-friendly buffers.
The kernel currently relies on two key mappings:
- Higher-Half Direct Map: The HHDM covers all physical memory starting at
kernel_offset(reported by Limine). We use it for quick physical-to-virtual translations when touching page tables or zeroing newly allocated pages. - Higher-Half Kernel Image: The kernel ELF is loaded in the higher half (Limine's default). We keep Limine's layout: kernel text/data/BSS live above the canonical base (0xffffff8000000000). There is no explicit relocator; we simply reuse the bootloader's mapping.
Other points:
- Page-table helpers in
pmm.coperate on 4-level structures (pml4_t,page_directory_pointer_t, etc.). When we need a new mapping we allocate intermediate tables on demand and set permissions (pmm_map_page_in_root). clear_kernel_user_permissions()walks all kernel entries and ensures the user bit is cleared so user address spaces cannot access kernel pages even if cloned.
- The kernel heap is initialised by
heap_initialize(NULL, PAGE_SIZE * HEAP_SIZE)inmemory_initialize(). At the moment the heap carves memory from the higher-half mapping; it is not yet virtual-memory aware. - The heap feeds
kmalloc,kfree, and related APIs. Memory compaction is handled by a background thread (mem_compactor).
proc_create_user()still clones the kernel PML4 viapmm_clone_kernel_address_space(), giving each process the shared higher-half mappings while providing a unique CR3 for user pages.- Each process carries
vm_regions[]metadata. The stack region is registered as grow-down; only the top page is mapped eagerly so the task can start executing, and further growth is handled lazily. elf64_load_image()maps PT_LOAD segments and records them in bothuser_segments(for physical cleanup) and the owning region metadata so we know exactly which virtual ranges are populated.- A userland heap shim (
brk/sbrk) has been implemented viasrc/libc/brk.c. It maintains a locked arena backed bymmap(MAP_ANONYMOUS), providing POSIX-compatible dynamic memory allocation without kernel syscalls. Native kernel-side heap regions remain planned for future work. - User-mode page faults are intercepted by
vm_region_handle_page_fault(). Faults inside growable regions allocate zeroed pages on demand; illegal or permission-violating faults still fall back to the diagnostic handler.
Work remaining after issue #28 (also expanded in docs/architecture/per_process_vm.md):
- Canonical Layout: Define fixed bases for text, rodata, data/BSS, heap, stack, and mmappable regions so PIDs no longer influence addresses and we can reserve guard pages.
- Native Kernel Heap Region: Introduce a true kernel-managed grow-up heap region for
brk/sbrksyscalls, wire it into the lazy allocation path, and converge cleanup logic on the region metadata instead of the flatuser_segmentsarray. (Currently, userland uses a libc shim backed bymmap.) - Address-Space Isolation: Replace the "clone kernel CR3" strategy with a curated template that maps only the required shared kernel ranges plus user regions, then teach
proc_exitto unmap via region descriptors. - Demand Paging: Build on the region infrastructure to lazily populate PT_LOAD segments or file-backed mappings once the filesystem and VFS layers arrive.
-
Demand paging for large executables or memory-mapped files is not yet designed. When we add a filesystem this document should be updated to cover file-backed mappings.
-
The kernel heap still operates purely inside the HHDM; longer term we may want a VM-aware heap that can grow by mapping new virtual arenas.
-
/proc/meminfo(procfs) now reports physical allocator statistics (usable/free bytes) so userland tools can observe system memory without digging through serial logs. For user-mode heap details, libc exposesmenios_malloc_stats()with arena/direct-mmap counters. -
We have not finalised the layout for kernel modules, PCI MMIO windows, or per-CPU structures. When SMP work begins, the memory map will need to document per-CPU stacks and data segments.
Last updated: 2025-11-03