feat(mm/phys): implement alloc and free

This commit is contained in:
Loïc Grégoire
2026-04-03 21:16:53 -04:00
parent 5b60f08db5
commit d2bc8f2bf8
6 changed files with 185 additions and 15 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
mainmenu "Bach Configuration" mainmenu "Zag Configuration"
choice choice
+1 -1
View File
@@ -98,7 +98,7 @@ pub fn init_kernel() void {
const higher_half_start = phys_to_virt(0); const higher_half_start = phys_to_virt(0);
const higher_half_size: usize = 256 * (1 << 39); const higher_half_size: usize = 256 * (1 << 39);
mi.kernel_pmap.impl.root_pa = mi.phys.alloc_page(); mi.kernel_pmap.impl.root_pa = mi.phys.alloc();
const table_ptr: [*]u64 = @ptrFromInt(mm.p2v(mi.kernel_pmap.impl.root_pa)); const table_ptr: [*]u64 = @ptrFromInt(mm.p2v(mi.kernel_pmap.impl.root_pa));
@memset(table_ptr[0..512], 0); @memset(table_ptr[0..512], 0);
+166 -10
View File
@@ -1,17 +1,95 @@
//! Physical memory allocator:
//! - Uses a per-CPU magazined allocator with a global Treiber stack as the fallback.
//! Each CPU has 4 batches of 64 pages each for fast allocation and deallocation without contention.
//! When a CPU's active batch is exhausted, it tries to get a new batch from its local depot.
//! If the depot is empty, it pops a batch from the global stack.
//! When freeing pages, they are added to the active batch until it reaches capacity,
//! at which point the full batch is moved to the local depot.
//! If the depot is full when trying to add a new batch, one batch is pushed back to the global stack to make room.
const b = @import("base"); const b = @import("base");
const std = @import("std"); const std = @import("std");
const rtl = @import("rtl"); const rtl = @import("rtl");
const pl = b.pl; const pl = b.pl;
const mm = b.mm; const mm = b.mm;
const mi = mm.private; const mi = mm.private;
const ke = b.ke;
// === Early state ===
var bootstrapped = false;
var early_alloc_entry_idx: usize = 0; var early_alloc_entry_idx: usize = 0;
var memory_map: *pl.BootInfo.MemMap = undefined; var memory_map: *pl.BootInfo.MemMap = undefined;
var early_allocs: usize = 0; var early_allocs: usize = 0;
pub var bootstrapped = false; // === Memory allocator state ===
/// Head for the global treiber stack.
const GlobalHead = packed struct(u64) {
pfn: mm.Pfn,
// A 32-bit ABA tag is more than enough for this,
// on 32-bit machines which do not support cmpxchg8b, we could use 12 bits instead.
tag: u32,
};
pub fn early_alloc() usize { /// Per-CPU state.
const PerCpu = struct {
depot: [4]*mm.Page,
depot_count: u8,
active_batch: ?*mm.Page,
active_count: u8,
};
const percpu = ke.CpuLocal(PerCpu, undefined);
const null_pfn: mm.Pfn = std.math.maxInt(mm.Pfn);
// TODO: make this configurable (or dynamic).
const batch_size: u8 = 64;
var batch_pool: std.atomic.Value(GlobalHead) = .init(.{
.pfn = null_pfn,
.tag = 0,
});
/// Push a batch of free pages to the global pool.
fn push_to_pool(batch: *mm.Page) void {
var old_head = batch_pool.load(.acquire);
while (true) {
batch.batch_next = old_head.pfn;
const new_head = GlobalHead{
.pfn = mm.struct_page_to_pfn(batch),
.tag = old_head.tag + 1,
};
old_head = batch_pool.cmpxchgWeak(old_head, new_head, .acq_rel, .acquire) orelse break;
}
}
/// Pop a batch of free pages from the global pool. Returns null if the pool is empty.
fn pop_from_pool() ?*mm.Page {
var old_head = batch_pool.load(.acquire);
while (true) {
const batch_pfn = old_head.pfn;
if (batch_pfn == null_pfn) {
return null;
}
const batch = mm.pfn_to_struct_page(batch_pfn);
const new_head = GlobalHead{
.pfn = batch.batch_next,
.tag = old_head.tag + 1,
};
old_head = batch_pool.cmpxchgWeak(old_head, new_head, .acq_rel, .acquire) orelse return batch;
}
return null;
}
fn early_alloc() usize {
while (early_alloc_entry_idx < memory_map.entry_count) { while (early_alloc_entry_idx < memory_map.entry_count) {
const entry = &memory_map.entries[early_alloc_entry_idx]; const entry = &memory_map.entries[early_alloc_entry_idx];
@@ -32,11 +110,92 @@ pub fn early_alloc() usize {
@panic("mm/phys: Out of memory for early allocation"); @panic("mm/phys: Out of memory for early allocation");
} }
pub fn alloc_page() usize { /// Allocate a page of physical memory.
pub fn alloc() b.PAddr {
if (!bootstrapped) { if (!bootstrapped) {
return early_alloc(); return early_alloc();
} }
@panic("not implemented");
const ipl = ke.ipl.raise(.High);
const cpu = percpu.local();
defer ke.ipl.lower(ipl);
if (cpu.active_count == 0) {
// Our batch is empty, get another one.
if (cpu.depot_count > 0) {
// Get a new batch from the local depot.
cpu.depot_count -= 1;
cpu.active_batch = cpu.depot[cpu.depot_count];
cpu.active_count = batch_size;
} else {
// Get one from the global pool.
const new_batch = pop_from_pool();
if (new_batch) |new| {
cpu.active_batch = new;
cpu.active_count = new.batch_count;
} else {
// TODO: call an IPI here for the other CPUs to fill the global pool before giving up.
@panic("OOM");
}
}
}
const batch = cpu.active_batch.?;
if (cpu.active_count > 1) {
// Pop the next page from the batch and set it as the new head.
const next_page = mm.pfn_to_struct_page(batch.next_pfn);
cpu.active_batch = next_page;
} else {
cpu.active_batch = null;
}
cpu.active_count -= 1;
return mm.pfn_to_page(mm.struct_page_to_pfn(batch));
}
/// Free a page of physical memory.
pub fn free(addr: b.PAddr) void {
const ipl = ke.ipl.raise(.High);
defer ke.ipl.lower(ipl);
const cpu = percpu.local();
const page = mm.pfn_to_struct_page(mm.page_to_pfn(addr));
if (cpu.active_batch) |curr| {
// Add the page to the current batch.
page.next_pfn = mm.struct_page_to_pfn(curr);
} else {
page.next_pfn = null_pfn;
}
cpu.active_batch = page;
cpu.active_count += 1;
if (cpu.active_count == batch_size) {
// The batch is full, move it to the depot.
page.batch_count = @intCast(cpu.active_count);
if (cpu.depot_count == cpu.depot.len) {
// The depot is full, push one batch back to the global pool to make room.
push_to_pool(cpu.depot[0]);
cpu.depot[0] = cpu.depot[1];
cpu.depot[1] = cpu.depot[2];
cpu.depot[2] = cpu.depot[3];
cpu.depot_count -= 1;
}
cpu.depot[cpu.depot_count] = cpu.active_batch.?;
cpu.depot_count += 1;
cpu.active_batch = null;
cpu.active_count = 0;
}
} }
pub fn init(boot_info: *pl.BootInfo) linksection(b.init) void { pub fn init(boot_info: *pl.BootInfo) linksection(b.init) void {
@@ -91,8 +250,6 @@ pub fn init(boot_info: *pl.BootInfo) linksection(b.init) void {
} }
pub fn init_pfndb() void { pub fn init_pfndb() void {
const pfndb: [*]mm.Page = @ptrFromInt(mi.impl.pfndb_base);
for (0..memory_map.entry_count) |i| { for (0..memory_map.entry_count) |i| {
const entry = memory_map.entries[i]; const entry = memory_map.entries[i];
@@ -100,13 +257,12 @@ pub fn init_pfndb() void {
continue; continue;
} }
const start_pfn: usize = mm.page_to_pfn(entry.base);
const npages = entry.size / mm.page_size; const npages = entry.size / mm.page_size;
for (0..npages) |j| { for (0..npages) |j| {
const pfn = start_pfn + j; free(entry.base + (j * mm.page_size));
const page = &pfndb[pfn];
page.batch_count = 0;
} }
} }
bootstrapped = true;
} }
+1 -1
View File
@@ -55,7 +55,7 @@ const AllocatingSource = struct {
flags: mm.MapFlags, flags: mm.MapFlags,
pub fn next(self: *const AllocatingSource, _: b.VAddr) MapItem { pub fn next(self: *const AllocatingSource, _: b.VAddr) MapItem {
const pa = mi.phys.alloc_page(); const pa = mi.phys.alloc();
return .{ return .{
.pa = pa, .pa = pa,
.len = mm.page_size, .len = mm.page_size,
+1 -1
View File
@@ -133,7 +133,7 @@ pub fn RadixPmap(comptime Impl: type) type {
return error.PageNotMapped; return error.PageNotMapped;
} }
const new_table_pa = mi.phys.alloc_page(); const new_table_pa = mi.phys.alloc();
const table_ptr: [*]Impl.Pte = @ptrFromInt(mm.p2v(new_table_pa)); const table_ptr: [*]Impl.Pte = @ptrFromInt(mm.p2v(new_table_pa));
@memset(table_ptr[0..entries_per_table], std.mem.zeroes(Impl.Pte)); @memset(table_ptr[0..entries_per_table], std.mem.zeroes(Impl.Pte));
+15 -1
View File
@@ -28,9 +28,23 @@ pub const MapFlags = packed struct {
cache_disable: bool = false, cache_disable: bool = false,
}; };
pub fn page_to_pfn(addr: usize) Pfn { pub inline fn page_to_pfn(addr: usize) Pfn {
return @intCast(addr >> 12); return @intCast(addr >> 12);
} }
pub inline fn pfn_to_page(pfn: Pfn) usize {
return @intCast(pfn << 12);
}
pub inline fn struct_page_to_pfn(page: *Page) Pfn {
return @intCast((@intFromPtr(page) - private.impl.pfndb_base) / @sizeOf(Page));
}
pub inline fn pfn_to_struct_page(pfn: Pfn) *Page {
return @ptrFromInt(private.impl.pfndb_base + (@as(usize, @intCast(pfn)) * @sizeOf(Page)));
}
pub var pfndb: [*]Page = @ptrFromInt(private.impl.pfndb_base);
pub const p2v = private.impl.phys_to_virt; pub const p2v = private.impl.phys_to_virt;
pub const v2p = private.impl.virt_to_phys; pub const v2p = private.impl.virt_to_phys;