Core kernel infrastructure: - Multiboot2 boot with GRUB, long mode setup, higher-half kernel - Serial port output for debugging - Unified boot info abstraction for future UEFI support Memory management: - Physical frame allocator with bitmap tracking - Page table manipulation via recursive mapping (PML4[510]) - Support for 4KB, 2MB, and 1GB page mappings - TLB invalidation and proper NXE support Build system: - Cargo-based build with custom x86_64 target - Makefile for QEMU and Bochs testing - GRUB ISO generation for multiboot2 boot
8.4 KiB
Physical Memory Allocator
This document describes the physical page frame allocator used by the XOmB exokernel to track and allocate physical memory pages.
Overview
The kernel multiplexes hardware resources through the virtual memory system. To do this, it needs to allocate physical memory pages for:
- Page table entries (PML4, PDPT, PD, PT)
- Resource mappings for applications
- Kernel data structures
The allocator maintains a bitmap tracking which physical pages (frames) are free or in use. Applications may request specific physical pages for resources like device MMIO or DMA buffers.
Design
Bitmap Allocator
We use a bitmap-based allocator where each bit represents one 4KB physical frame:
- 0 = frame is free
- 1 = frame is allocated or reserved
The bitmap is stored as an array of u64 words, where each word tracks 64 frames (256KB of physical memory).
Bitmap word 0: [frame 0-63]
Bitmap word 1: [frame 64-127]
...
Bitmap word N: [frame N*64 to N*64+63]
Memory Limits
| Constant | Value | Description |
|---|---|---|
PAGE_SIZE |
4096 bytes | Standard x86-64 page size |
MAX_PHYSICAL_MEMORY |
16 GB | Maximum supported physical memory |
MAX_FRAMES |
4,194,304 | Maximum number of 4KB frames |
BITMAP_WORDS |
65,536 | Number of u64 words in bitmap |
| Bitmap size | 512 KB | Total bitmap memory footprint |
Data Structures
PhysAddr
A wrapper type for physical addresses with utility methods:
pub struct PhysAddr(u64);
impl PhysAddr {
pub const fn new(addr: u64) -> Self; // Create with masking
pub const fn as_u64(self) -> u64; // Get raw value
pub const fn is_aligned(self) -> bool; // Check 4KB alignment
pub const fn align_down(self) -> Self; // Round down to page
pub const fn align_up(self) -> Self; // Round up to page
pub const fn containing_frame(self) -> Frame; // Get containing frame
}
Physical addresses on x86-64 are limited to 52 bits. The upper bits are masked on creation.
Frame
Represents a single 4KB physical page frame:
pub struct Frame {
number: usize, // Frame number = physical_address / PAGE_SIZE
}
impl Frame {
pub const fn from_number(number: usize) -> Self;
pub const fn containing_address(addr: PhysAddr) -> Self;
pub const fn number(self) -> usize;
pub const fn start_address(self) -> PhysAddr;
}
FrameAllocator
The main allocator structure:
pub struct FrameAllocator {
bitmap: [u64; BITMAP_WORDS], // 512KB bitmap
total_frames: usize, // Total usable frames
free_frames: usize, // Currently free frames
initialized: bool, // Initialization flag
next_free_hint: usize, // Optimization hint
}
Initialization
The allocator is initialized from the boot memory map in two phases:
Phase 1: Mark All As Used
for word in self.bitmap.iter_mut() {
*word = !0u64; // All bits set = all frames used
}
This ensures any gaps or reserved regions in the memory map remain marked as unavailable.
Phase 2: Free Usable Regions
for region in memory_map.iter() {
if region.region_type == MemoryRegionType::Usable {
// Free each complete frame in the region
for frame_num in first_frame..last_frame {
self.mark_free(frame_num);
}
}
}
Only frames that fall completely within usable memory regions are marked as free.
Phase 3: Reserve Kernel Memory
After initialization, we reserve memory used by the kernel:
// Reserve first 1MB (BIOS, real mode IVT, etc.)
allocator.reserve_range(PhysAddr::new(0), 1024 * 1024);
// Reserve kernel physical memory (16MB from load address)
allocator.reserve_kernel(PhysAddr::new(0x100000), 16 * 1024 * 1024);
Allocation Algorithm
Allocate Any Frame
The allocate() function finds and allocates any free frame:
pub fn allocate(&mut self) -> Result<Frame, FrameAllocatorError> {
// Start search from hint position
let start_word = self.next_free_hint / 64;
// Search for a word with at least one free bit
for word_idx in start_word..BITMAP_WORDS {
if self.bitmap[word_idx] != !0u64 {
// Find first free bit using: (!word).trailing_zeros()
let bit = self.find_free_bit(self.bitmap[word_idx]);
let frame_num = word_idx * 64 + bit;
self.mark_used(frame_num);
self.next_free_hint = frame_num + 1;
return Ok(Frame::from_number(frame_num));
}
}
// Wrap around if needed...
Err(FrameAllocatorError::OutOfMemory)
}
Complexity: O(n/64) worst case, but typically O(1) due to the hint optimization.
Allocate Specific Frame
The allocate_specific() function allocates a particular physical frame:
pub fn allocate_specific(&mut self, frame: Frame) -> Result<(), FrameAllocatorError> {
let frame_num = frame.number();
if frame_num >= MAX_FRAMES {
return Err(FrameAllocatorError::InvalidFrame);
}
if self.is_allocated(frame_num) {
return Err(FrameAllocatorError::FrameInUse);
}
self.mark_used(frame_num);
Ok(())
}
This is essential for the exokernel design where applications may request specific physical pages for:
- Device MMIO regions
- DMA buffers with specific alignment
- Shared memory at known addresses
Complexity: O(1)
Deallocate Frame
pub fn deallocate(&mut self, frame: Frame) -> Result<(), FrameAllocatorError> {
let frame_num = frame.number();
self.mark_free(frame_num);
// Update hint for faster future allocations
if frame_num < self.next_free_hint {
self.next_free_hint = frame_num;
}
Ok(())
}
Complexity: O(1)
Bit Manipulation
The bitmap operations use efficient bitwise operations:
// Check if frame is allocated
fn is_allocated(&self, frame_num: usize) -> bool {
let word_idx = frame_num / 64;
let bit_idx = frame_num % 64;
(self.bitmap[word_idx] & (1u64 << bit_idx)) != 0
}
// Mark frame as used
fn mark_used(&mut self, frame_num: usize) {
let word_idx = frame_num / 64;
let bit_idx = frame_num % 64;
self.bitmap[word_idx] |= 1u64 << bit_idx;
}
// Mark frame as free
fn mark_free(&mut self, frame_num: usize) {
let word_idx = frame_num / 64;
let bit_idx = frame_num % 64;
self.bitmap[word_idx] &= !(1u64 << bit_idx);
}
// Find first free bit (0) in a word
fn find_free_bit(&self, word: u64) -> usize {
(!word).trailing_zeros() as usize
}
Global Interface
The allocator is wrapped in a spinlock for thread-safety:
pub static FRAME_ALLOCATOR: Mutex<FrameAllocator> = Mutex::new(FrameAllocator::new());
Convenience functions provide a simple API:
// Initialize from boot info
pub fn init(boot_info: &BootInfo);
// Allocate a frame
pub fn allocate_frame() -> Result<Frame, FrameAllocatorError>;
// Allocate at specific address
pub fn allocate_frame_at(addr: PhysAddr) -> Result<Frame, FrameAllocatorError>;
// Deallocate a frame
pub fn deallocate_frame(frame: Frame) -> Result<(), FrameAllocatorError>;
// Get statistics
pub fn memory_stats() -> (free_bytes, total_bytes);
Error Handling
pub enum FrameAllocatorError {
OutOfMemory, // No free frames available
FrameInUse, // Requested specific frame is already allocated
InvalidFrame, // Frame number exceeds MAX_FRAMES
NotInitialized, // Allocator not yet initialized
}
Memory Layout Example
After boot on a system with 512MB RAM:
Physical Memory Layout:
0x00000000 - 0x00100000 [Reserved: BIOS, real mode]
0x00100000 - 0x01000000 [Reserved: Kernel + 16MB buffer]
0x01000000 - 0x1FFF0000 [Free: ~495MB available]
0x1FFF0000 - 0x20000000 [ACPI Reclaimable]
Frame Allocator State:
Total frames: ~131,000 (from usable regions)
Free frames: ~126,000 (after kernel reservation)
Free memory: ~494 MB
Source Files
| File | Description |
|---|---|
src/memory/mod.rs |
Module root, constants, alignment functions |
src/memory/frame.rs |
PhysAddr, Frame, FrameAllocator implementation |
Future Considerations
- Buddy Allocator: For contiguous multi-frame allocations (superpages)
- NUMA Awareness: Track which memory node frames belong to
- Memory Zones: Separate low memory (<4GB) for legacy DMA
- Statistics: Track allocation patterns, fragmentation metrics