From 0a89436d86e12204de654f4b7408fdf34d768d5f Mon Sep 17 00:00:00 2001 From: Eugene Rossokha Date: Thu, 20 Mar 2025 10:30:09 +0200 Subject: [PATCH] phys: use a bitmap to track pages, get the refcounters out --- src/mem/phys.zig | 167 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 50 deletions(-) diff --git a/src/mem/phys.zig b/src/mem/phys.zig index d552b54..9287fa5 100644 --- a/src/mem/phys.zig +++ b/src/mem/phys.zig @@ -20,45 +20,69 @@ pub const MemoryRegion = struct { }; /// Represents information about a single managed physical memory page. -pub const Page = extern struct { - /// Reference count of the page. Zero means the page is not allocated. - refcount: u32 = 0, - unused: [3]u32 = undefined, +pub const Page = packed struct (u128) { + unused: @Vector(4, u32), +}; - /// Returns `true` if the page is allocated/used. - pub fn is_used(self: *const @This()) bool { - return self.refcount != 0; +const Bitmap = struct { + data: []u64, + + const Self = @This(); + + pub const empty: Self = .{ .data = &.{} }; + + fn get_bit(self: *Self, index: usize) u1 { + const word_index = index / 64; + const bit_index = index % 64; + const masked = self.data[word_index] & (@as(u64, 1) << @intCast(bit_index)); + return if (masked == 0) 0 else 1; } - fn make_available(self: *@This()) void { - self.refcount = 0; + fn set_bit(self: *Self, index: usize) void { + const word_index = index / 64; + const bit_index = index % 64; + self.data[word_index] |= (@as(u64, 1) << @intCast(bit_index)); } - fn make_reserved(self: *@This()) void { - self.refcount = std.math.maxInt(u32); + fn clear_bit(self: *Self, index: usize) void { + const word_index = index / 64; + const bit_index = index % 64; + self.data[word_index] &= ~(@as(u64, 1) << @intCast(bit_index)); } }; const PhysicalMemoryManager = struct { page_array: []Page, - offset: u64 = 0, - last_free: usize = 0, + memory_start: u64, + last_free: usize, - const RECORDS_PER_PAGE: usize = vmm.PAGE_SIZE / @sizeOf(Page); + /// Each bit represents a page, there can be more u64s than needed + usage_bitmap: Bitmap, + page_refcounters: []u32, + + const empty: @This() = .{ + .page_array = &.{}, + .memory_start = 0, + .last_free = 0, + .usage_bitmap = .empty, + .page_refcounters = &.{}, + }; fn alloc_page(self: *@This()) ?mem.PhysicalAddress { for (self.last_free..self.page_array.len) |i| { - if (self.page_array[i].refcount == 0) { - self.page_array[i].refcount += 1; + if (!self.is_page_used(i)) { + self.page_refcounters[i] += 1; + self.set_page_used(i); self.last_free = (i + 1) % self.page_array.len; - return .{ .raw = self.offset + i * vmm.PAGE_SIZE }; + return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE }; } } for (0..self.last_free) |i| { - if (self.page_array[i].refcount == 0) { - self.page_array[i].refcount += 1; + if (!self.is_page_used(i)) { + self.page_refcounters[i] += 1; + self.set_page_used(i); self.last_free = (i + 1) % self.page_array.len; - return .{ .raw = self.offset + i * vmm.PAGE_SIZE }; + return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE }; } } return null; @@ -75,19 +99,19 @@ const PhysicalMemoryManager = struct { fn alloc_from(self: *@This(), start: usize, end: usize, count: usize) ?mem.PhysicalAddress { for (start..end) |i| { - var taken = false; - for (0..count) |j| { - if (self.page_array[i + j].is_used()) { - taken = true; - break; - } - } + const taken = taken: { + for (0..count) |j| + if (self.is_page_used(i + j)) + break :taken true; + break :taken false; + }; if (!taken) { for (0..count) |j| { - self.page_array[i + j].refcount = 1; + self.page_refcounters[i + j] = 1; + self.set_page_used(i + j); } - return .{ .raw = self.offset + i * vmm.PAGE_SIZE }; + return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE }; } } @@ -95,10 +119,10 @@ const PhysicalMemoryManager = struct { } fn valid_index(self: *@This(), page: mem.PhysicalAddress) usize { - if (page.raw < self.offset) { + if (page.raw < self.memory_start) { log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw}); } - const index = (page.raw - self.offset) / vmm.PAGE_SIZE; + const index = (page.raw - self.memory_start) / vmm.PAGE_SIZE; if (index >= self.page_array.len) { log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw}); } @@ -107,11 +131,12 @@ const PhysicalMemoryManager = struct { fn free_page(self: *@This(), page: mem.PhysicalAddress) void { const index = self.valid_index(page); - if (self.page_array[index].refcount == 0) { + if (!self.is_page_used(index)) { log.panic("free_page: double free of page 0x{x} detected", .{page.raw}); } - self.page_array[index].refcount -= 1; - if (self.page_array[index].refcount == 0) { + self.page_refcounters[index] -= 1; + if (self.page_refcounters[index] == 0) { + self.clear_page_used(index); self.last_free = index; } } @@ -120,12 +145,24 @@ const PhysicalMemoryManager = struct { const index = self.valid_index(page); return &self.page_array[index]; } + + fn is_page_used(self: *@This(), index: usize) bool { + return self.usage_bitmap.get_bit(index) == 1; + } + + fn set_page_used(self: *@This(), index: usize) void { + self.usage_bitmap.set_bit(index); + } + + fn clear_page_used(self: *@This(), index: usize) void { + self.usage_bitmap.clear_bit(index); + } }; var g_memory_regions: std.BoundedArray(MemoryRegion, 16) = .{}; var g_reserved_regions: std.BoundedArray(MemoryRegion, 16) = .{}; var g_physical_memory_lock = Spinlock{}; -var g_physical_memory = PhysicalMemoryManager{ .page_array = undefined }; +var g_physical_memory = PhysicalMemoryManager.empty; /// Adds an available memory region to the list. /// @@ -182,20 +219,46 @@ fn alloc_from_region(region: *const MemoryRegion, reason: []const u8, page_count return null; } -fn alloc_page_array(page_count: usize) []Page { +/// Allocates a slice of type `T` that spans the `page_count` pages. +fn alloc_slice_pages(comptime T: type, reason: []const u8, page_count: usize) []T { for (g_memory_regions.constSlice()) |region| { - if (alloc_from_region(®ion, "page-array", page_count)) |physAddress| { + if (alloc_from_region(®ion, reason, page_count)) |physAddress| { const vaddr = (mem.PhysicalAddress{ .raw = physAddress }).virtualize(); - const len = page_count * PhysicalMemoryManager.RECORDS_PER_PAGE; - const ptr: [*]Page = @ptrFromInt(vaddr); - const slice: []Page = ptr[0..len]; - for (0..len) |i| { - slice[i].refcount = std.math.maxInt(u32); - } + const items_per_page = vmm.PAGE_SIZE / @sizeOf(T); + const len = page_count * items_per_page; + const ptr: [*]T = @ptrFromInt(vaddr); + const slice: []T = ptr[0..len]; return slice; } } - @panic("TODO"); + @panic("Failed to allocate a slice"); +} + +/// Allocates a slice of type `T` that has at least `min_len` items, allocates in 4KiB pages. +/// The items are zeroed out. +fn alloc_slice(comptime T: type, reason: []const u8, min_len: usize) []T { + const min_alloc_bytes = min_len * @sizeOf(T); + // Round up to make sure we have enough space for the data + const needed_pages = (min_alloc_bytes + vmm.PAGE_SIZE - 1) / vmm.PAGE_SIZE; + const slice = alloc_slice_pages(T, reason, needed_pages); + const slice_as_bytes = std.mem.sliceAsBytes(slice); + @memset(slice_as_bytes, 0); + return slice; +} + +/// Allocates a bitmap that has at least `bits_required` total bits. +/// It can have more since we allocate 4KiB pages and the backing type is []u64 +fn alloc_bitmap(bits_required: usize) Bitmap { + // Round up to the upper u64 that has at least `pages` bits + const bitmap_entries = (bits_required + 63) / 64; + const bitmap = alloc_slice(u64, "bitmap", bitmap_entries); + return .{ .data = bitmap }; +} +// TODO: combine refcounters and bitmap allocation into a single chunk +fn alloc_refcounters(count: usize) []u32 { + const refcounters = alloc_slice(u32, "refcounters", count); + @memset(refcounters, std.math.maxInt(u32)); + return refcounters; } /// Initializes the physical memory management. @@ -218,16 +281,16 @@ pub fn init() void { } const memory_pages = (memory_end - memory_start) / vmm.PAGE_SIZE; // == bitmap bits required - const page_array_pages = (memory_pages + PhysicalMemoryManager.RECORDS_PER_PAGE - 1) // - / PhysicalMemoryManager.RECORDS_PER_PAGE; + var bitmap = alloc_bitmap(memory_pages); + const page_array = alloc_slice(Page, "page-array", memory_pages); + const refcounters = alloc_refcounters(memory_pages); - const page_array = alloc_page_array(page_array_pages); var available_pages: usize = 0; for (g_memory_regions.constSlice()) |region| { const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE; for (0..region.range.len / vmm.PAGE_SIZE) |i| { - page_array[offset + i].make_available(); + refcounters[offset + i] = 0; available_pages += 1; } } @@ -237,7 +300,9 @@ pub fn init() void { if (offset + i >= page_array.len) { break; } - page_array[offset + i].make_reserved(); + refcounters[offset + i] = std.math.maxInt(u32); + bitmap.set_bit(offset + i); + available_pages -= 1; } } @@ -247,7 +312,9 @@ pub fn init() void { log.info("Available memory: {s}, page array {*}", .{ size_fmt_str, page_array }); g_physical_memory.page_array = page_array; - g_physical_memory.offset = memory_start; + g_physical_memory.memory_start = memory_start; + g_physical_memory.usage_bitmap = bitmap; + g_physical_memory.page_refcounters = refcounters; } fn trace_allocation(count: usize, page: ?mem.PhysicalAddress) void {