# 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:
```rust
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:
```rust
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:
```rust
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
```rust
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
```rust
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:
```rust
// 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:
```rust
pub fn allocate(&mut self) -> Result {
// 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:
```rust
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
```rust
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:
```rust
// 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:
```rust
pub static FRAME_ALLOCATOR: Mutex = Mutex::new(FrameAllocator::new());
```
Convenience functions provide a simple API:
```rust
// Initialize from boot info
pub fn init(boot_info: &BootInfo);
// Allocate a frame
pub fn allocate_frame() -> Result;
// Allocate at specific address
pub fn allocate_frame_at(addr: PhysAddr) -> Result;
// Deallocate a frame
pub fn deallocate_frame(frame: Frame) -> Result<(), FrameAllocatorError>;
// Get statistics
pub fn memory_stats() -> (free_bytes, total_bytes);
```
## Error Handling
```rust
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
1. **Buddy Allocator**: For contiguous multi-frame allocations (superpages)
2. **NUMA Awareness**: Track which memory node frames belong to
3. **Memory Zones**: Separate low memory (<4GB) for legacy DMA
4. **Statistics**: Track allocation patterns, fragmentation metrics