phys: use a bitmap to track pages, get the refcounters out
This commit is contained in:
+117
-50
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user