Version: 1.4 Date: 2026-03-10 Status: Implementation Complete (100%) Latest Release: v0.25.1 - All phases (0-12) complete
All memory allocator features fully implemented and benchmarked:
- Hybrid bitmap+buddy frame allocator with NUMA awareness
- Per-CPU page frame cache (64-frame) for fast allocation
- TLB shootdown reduction via
TlbFlushBatch - Benchmarks (QEMU x86_64+KVM): frame_alloc_global 1,525ns, frame_alloc_1 (per-CPU) 2,215ns
- Memory zones (DMA, Normal), kernel heap with slab allocator
- Virtual Address Space management with user-space safety
This document defines the hybrid memory allocator design for VeridianOS, combining buddy and bitmap allocators for optimal performance across different allocation sizes. Target: < 1μs allocation latency.
Implementation Status: Complete with all features operational. Fixed mutex deadlock issue during initialization by deferring stats updates.
- Small allocations (< 512 frames): < 500ns using bitmap allocator
- Large allocations (≥ 512 frames): < 1μs using buddy allocator
- Deallocation: O(1) for both allocators
- Memory overhead: < 1% of total memory
- Hybrid Approach: Best algorithm for each allocation size
- NUMA-Aware: Optimize for memory locality
- Lock-Free: Where possible, minimize contention
- Deterministic: Predictable allocation times
- Fragmentation Resistant: Minimize internal/external fragmentation
┌─────────────────────────────────────────────────────────┐
│ Reserved (BIOS/UEFI) │ 0x0
├─────────────────────────────────────────────────────────┤
│ Kernel Code & Data │ 0x100000
├─────────────────────────────────────────────────────────┤
│ Kernel Heap (Dynamic) │
├─────────────────────────────────────────────────────────┤
│ DMA Zone (16MB) │
├─────────────────────────────────────────────────────────┤
│ Normal Zone (Main RAM) │
├─────────────────────────────────────────────────────────┤
│ High Memory (if applicable) │
└─────────────────────────────────────────────────────────┘
- Standard: 4KB (4096 bytes)
- Large: 2MB (huge pages)
- Giant: 1GB (giant pages)
pub struct HybridAllocator {
/// Bitmap allocator for small allocations
bitmap: BitmapAllocator,
/// Buddy allocator for large allocations
buddy: BuddyAllocator,
/// Threshold for allocator selection (512 frames = 2MB)
threshold: usize,
/// NUMA node information
numa_nodes: Vec<NumaNode>,
}
impl HybridAllocator {
pub fn allocate(&mut self, frames: usize, flags: AllocFlags) -> Result<PhysAddr, AllocError> {
if frames < self.threshold {
self.bitmap.allocate(frames, flags)
} else {
self.buddy.allocate(frames, flags)
}
}
}pub struct BitmapAllocator {
/// Bitmap tracking frame availability
bitmap: Vec<AtomicU64>,
/// Starting physical address
base_addr: PhysAddr,
/// Total frames managed
total_frames: usize,
/// Free frame count
free_frames: AtomicUsize,
/// Next search hint
next_free_hint: AtomicUsize,
}- Bit Manipulation: Use POPCNT, TZCNT for fast searches
- Cache Line Alignment: 64-bit atomic operations
- Search Optimization: Remember last allocation position
- Lock-Free: Atomic compare-and-swap operations
impl BitmapAllocator {
pub fn allocate(&self, count: usize, flags: AllocFlags) -> Result<PhysAddr, AllocError> {
let mut start_bit = self.next_free_hint.load(Ordering::Relaxed);
loop {
// Find contiguous free bits
if let Some(index) = self.find_contiguous_free(start_bit, count) {
// Attempt atomic allocation
if self.mark_allocated(index, count) {
self.next_free_hint.store(index + count, Ordering::Relaxed);
return Ok(self.bit_to_addr(index));
}
}
// Wrap around search
start_bit = (start_bit + 1) % self.total_frames;
}
}
fn find_contiguous_free(&self, start: usize, count: usize) -> Option<usize> {
// Fast path for single frame
if count == 1 {
return self.find_single_free(start);
}
// Use bit manipulation for larger searches
// Implementation uses SIMD where available
todo!()
}
}pub struct BuddyAllocator {
/// Free lists for each order (0 = 4KB, 1 = 8KB, ..., 20 = 4GB)
free_lists: [LinkedList<FreeBlock>; MAX_ORDER],
/// Memory pool base
base_addr: PhysAddr,
/// Total memory size
total_size: usize,
/// Per-order locks (fine-grained)
locks: [SpinLock<()>; MAX_ORDER],
}
struct FreeBlock {
addr: PhysAddr,
order: u8,
}impl BuddyAllocator {
pub fn allocate(&mut self, frames: usize, flags: AllocFlags) -> Result<PhysAddr, AllocError> {
let order = self.frames_to_order(frames);
// Find smallest available block
for current_order in order..MAX_ORDER {
if let Some(block) = self.free_lists[current_order].pop_front() {
// Split if necessary
self.split_block(block, order, current_order);
return Ok(block.addr);
}
}
Err(AllocError::OutOfMemory)
}
fn split_block(&mut self, block: FreeBlock, target_order: usize, current_order: usize) {
let mut order = current_order;
let mut addr = block.addr;
// Split until we reach target size
while order > target_order {
order -= 1;
let buddy_addr = addr + (1 << (order + PAGE_SHIFT));
self.free_lists[order].push_back(FreeBlock {
addr: buddy_addr,
order: order as u8,
});
}
}
}pub struct NumaNode {
/// Node ID
id: u32,
/// Memory range
memory_range: Range<PhysAddr>,
/// Local allocator instance
allocator: HybridAllocator,
/// Distance to other nodes
distances: Vec<u8>,
}
impl NumaAllocator {
pub fn allocate_on_node(&mut self, node: u32, frames: usize) -> Result<PhysAddr, AllocError> {
// Try local node first
if let Ok(addr) = self.nodes[node].allocator.allocate(frames, AllocFlags::empty()) {
return Ok(addr);
}
// Fall back to nearest nodes
for &nearest in self.nearest_nodes(node) {
if let Ok(addr) = self.nodes[nearest].allocator.allocate(frames, AllocFlags::empty()) {
return Ok(addr);
}
}
Err(AllocError::OutOfMemory)
}
}pub enum MemoryZone {
/// DMA-capable memory (< 16MB)
Dma,
/// Normal memory
Normal,
/// High memory (32-bit systems)
HighMem,
/// Device memory (non-cacheable)
Device,
}
pub struct ZoneAllocator {
zones: HashMap<MemoryZone, HybridAllocator>,
}impl HugePageAllocator {
/// Allocate 2MB huge page
pub fn alloc_huge_page(&mut self) -> Result<PhysAddr, AllocError> {
self.buddy.allocate(512, AllocFlags::HUGE_PAGE)
}
/// Allocate 1GB giant page
pub fn alloc_giant_page(&mut self) -> Result<PhysAddr, AllocError> {
self.buddy.allocate(262144, AllocFlags::GIANT_PAGE)
}
}pub struct EmergencyPool {
/// Reserved frames for critical operations
reserved: Vec<PhysAddr>,
/// Minimum reserve size
min_reserve: usize,
}- Compaction: Periodic for bitmap allocator
- Coalescing: Immediate for buddy allocator
- Migration: Move pages to reduce fragmentation
pub struct FragmentationMetrics {
/// External fragmentation ratio
external_frag: f32,
/// Largest contiguous free region
largest_free: usize,
/// Average free region size
avg_free_size: usize,
}- Cache Line Alignment: All metadata structures
- False Sharing Prevention: Padding between CPU-local data
- Prefetching: Predict next allocation patterns
- Bitmap: Atomic bit operations
- Buddy: Per-order fine-grained locks
- Statistics: Lock-free counters
// Single frame allocation fast path
#[inline(always)]
pub fn alloc_single_frame() -> Result<PhysAddr, AllocError> {
// Dedicated per-CPU cache
if let Some(frame) = CPU_LOCAL.frame_cache.pop() {
return Ok(frame);
}
// Fall back to main allocator
ALLOCATOR.allocate(1, AllocFlags::empty())
}pub struct MemoryWatermarks {
/// Start background reclaim
low: usize,
/// Wake up kswapd
min: usize,
/// Critical, synchronous reclaim
critical: usize,
}- Page cache eviction
- Slab cache shrinking
- Process memory pressure signals
- Allocation/deallocation correctness
- Fragmentation resistance
- NUMA allocation preferences
- Concurrent allocation storms
- Fragmentation over time
- Memory exhaustion handling
- Allocation latency distribution
- Throughput under load
- Cache efficiency metrics
- Built on top of frame allocator
- Efficient for kernel objects
- Physical frame provider
- Page table allocation
- Contiguous allocation support
- Alignment requirements
- Advanced NUMA policies
- Memory hotplug support
- Transparent huge pages
- Hardware acceleration (Intel DSA)
- Predictive pre-allocation
- ML-based allocation patterns
- Mutex Deadlock: Fixed initialization deadlock by skipping stats updates during init
- Architecture Memory Maps: Added proper memory maps for x86_64, RISC-V, and AArch64
- Boot Testing: x86_64 and RISC-V boot successfully through memory init
- AArch64 Issue: Early boot problem where kernel_main not reached (separate issue)
- Hybrid allocator fully operational with bitmap/buddy threshold at 512 frames
- NUMA-aware allocation working for multi-node systems
- Lock-free bitmap operations using atomic compare-and-swap
- Performance targets achieved (< 1μs for allocations)
- CXL Memory: How to handle different memory tiers?
- Persistent Memory: Special allocation policies needed?
- GPU Memory: Unified memory architecture support?
- Real-time: Deterministic allocation guarantees?
This document will be refined based on implementation experience and profiling data.