Merge pull request #104 from SamTebbs33/feature/phys-mem-manager
Add physical memory manager
This commit is contained in:
commit
a095fd3947
10 changed files with 567 additions and 7 deletions
|
@ -11,6 +11,7 @@ const paging = @import("paging.zig");
|
||||||
const syscalls = @import("syscalls.zig");
|
const syscalls = @import("syscalls.zig");
|
||||||
const mem = @import("../../mem.zig");
|
const mem = @import("../../mem.zig");
|
||||||
const multiboot = @import("../../multiboot.zig");
|
const multiboot = @import("../../multiboot.zig");
|
||||||
|
const pmm = @import("pmm.zig");
|
||||||
const MemProfile = mem.MemProfile;
|
const MemProfile = mem.MemProfile;
|
||||||
|
|
||||||
/// The interrupt context that is given to a interrupt handler. It contains most of the registers
|
/// The interrupt context that is given to a interrupt handler. It contains most of the registers
|
||||||
|
@ -46,6 +47,9 @@ pub const InterruptContext = struct {
|
||||||
ss: u32,
|
ss: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The size of each allocatable block of memory, normally set to the page size.
|
||||||
|
pub const MEMORY_BLOCK_SIZE = paging.PAGE_SIZE_4KB;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Assembly to write to a given port with a byte of data.
|
/// Assembly to write to a given port with a byte of data.
|
||||||
///
|
///
|
||||||
|
|
|
@ -83,12 +83,6 @@ const ENTRIES_PER_DIRECTORY: u32 = 1024;
|
||||||
/// Each table has 1024 entries
|
/// Each table has 1024 entries
|
||||||
const ENTRIES_PER_TABLE: u32 = 1024;
|
const ENTRIES_PER_TABLE: u32 = 1024;
|
||||||
|
|
||||||
/// The number of bytes in 4MB
|
|
||||||
const PAGE_SIZE_4MB: u32 = 0x400000;
|
|
||||||
|
|
||||||
/// The number of bytes in 4KB
|
|
||||||
const PAGE_SIZE_4KB: u32 = PAGE_SIZE_4MB / 1024;
|
|
||||||
|
|
||||||
/// There are 1024 entries per directory with each one covering 4KB
|
/// There are 1024 entries per directory with each one covering 4KB
|
||||||
const PAGES_PER_DIR_ENTRY: u32 = 1024;
|
const PAGES_PER_DIR_ENTRY: u32 = 1024;
|
||||||
|
|
||||||
|
@ -121,6 +115,12 @@ const TENTRY_GLOBAL: u32 = 0x100;
|
||||||
const TENTRY_AVAILABLE: u32 = 0xE00;
|
const TENTRY_AVAILABLE: u32 = 0xE00;
|
||||||
const TENTRY_PAGE_ADDR: u32 = 0xFFFFF000;
|
const TENTRY_PAGE_ADDR: u32 = 0xFFFFF000;
|
||||||
|
|
||||||
|
/// The number of bytes in 4MB
|
||||||
|
pub const PAGE_SIZE_4MB: u32 = 0x400000;
|
||||||
|
|
||||||
|
/// The number of bytes in 4KB
|
||||||
|
pub const PAGE_SIZE_4KB: u32 = PAGE_SIZE_4MB / 1024;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Convert a virtual address to an index within an array of directory entries.
|
/// Convert a virtual address to an index within an array of directory entries.
|
||||||
///
|
///
|
||||||
|
|
306
src/kernel/bitmap.zig
Normal file
306
src/kernel/bitmap.zig
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// A bitmap that uses a specific type to store the entries.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN BitmapType: type - The integer type to use to store entries.
|
||||||
|
///
|
||||||
|
/// Return: type.
|
||||||
|
/// The bitmap type created.
|
||||||
|
///
|
||||||
|
pub fn Bitmap(comptime BitmapType: type) type {
|
||||||
|
return struct {
|
||||||
|
/// The possible errors thrown by bitmap functions
|
||||||
|
pub const BitmapError = error{
|
||||||
|
/// The address given was outside the region covered by a bitmap
|
||||||
|
OutOfBounds,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// The number of entries that one bitmap type can hold. Evaluates to the number of bits the type has
|
||||||
|
pub const ENTRIES_PER_BITMAP: u32 = std.meta.bitCount(BitmapType);
|
||||||
|
|
||||||
|
/// The value that a full bitmap will have
|
||||||
|
pub const BITMAP_FULL = std.math.maxInt(BitmapType);
|
||||||
|
|
||||||
|
/// The type of an index into a bitmap entry. The smallest integer needed to represent all bit positions in the bitmap entry type
|
||||||
|
pub const IndexType = @Type(builtin.TypeInfo{ .Int = builtin.TypeInfo.Int{ .is_signed = false, .bits = std.math.log2(std.meta.bitCount(BitmapType)) } });
|
||||||
|
|
||||||
|
num_bitmaps: u32,
|
||||||
|
num_entries: u32,
|
||||||
|
bitmaps: []BitmapType,
|
||||||
|
num_free_entries: u32,
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Create an instance of this bitmap type.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN num_entries: u32 - The number of entries that the bitmap created will have.
|
||||||
|
/// The number of BitmapType required to store this many entries will be allocated and each will be zeroed.
|
||||||
|
/// IN allocator: *std.mem.Allocator - The allocator to use when allocating the BitmapTypes required.
|
||||||
|
///
|
||||||
|
/// Return: Self.
|
||||||
|
/// The bitmap instance.
|
||||||
|
///
|
||||||
|
/// Error: std.mem.Allocator.Error
|
||||||
|
/// OutOfMemory: There isn't enough memory available to allocate the required number of BitmapType.
|
||||||
|
///
|
||||||
|
pub fn init(num_entries: u32, allocator: *std.mem.Allocator) !Self {
|
||||||
|
const num = @floatToInt(u32, @ceil(@intToFloat(f32, num_entries) / @intToFloat(f32, ENTRIES_PER_BITMAP)));
|
||||||
|
const self = Self{
|
||||||
|
.num_bitmaps = num,
|
||||||
|
.num_entries = num_entries,
|
||||||
|
.bitmaps = try allocator.alloc(BitmapType, num),
|
||||||
|
.num_free_entries = num_entries,
|
||||||
|
};
|
||||||
|
for (self.bitmaps) |*bmp| {
|
||||||
|
bmp.* = 0;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Set an entry within a bitmap as occupied.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// INOUT self: *Self - The bitmap to modify.
|
||||||
|
/// IN idx: BitmapIndex - The index within the bitmap to set.
|
||||||
|
///
|
||||||
|
/// Error: BitmapError.
|
||||||
|
/// OutOfBounds: The index given is out of bounds.
|
||||||
|
///
|
||||||
|
pub fn setEntry(self: *Self, idx: u32) BitmapError!void {
|
||||||
|
if (idx >= self.num_entries) return BitmapError.OutOfBounds;
|
||||||
|
if (!try self.isSet(idx)) {
|
||||||
|
const bit = self.indexToBit(idx);
|
||||||
|
self.bitmaps[idx / ENTRIES_PER_BITMAP] |= bit;
|
||||||
|
self.num_free_entries -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Set an entry within a bitmap as unoccupied.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// INOUT self: *Self - The bitmap to modify.
|
||||||
|
/// IN idx: BitmapIndex - The index within the bitmap to clear.
|
||||||
|
///
|
||||||
|
/// Error: BitmapError.
|
||||||
|
/// OutOfBounds: The index given is out of bounds.
|
||||||
|
///
|
||||||
|
pub fn clearEntry(self: *Self, idx: u32) BitmapError!void {
|
||||||
|
if (idx >= self.num_entries) return BitmapError.OutOfBounds;
|
||||||
|
if (try self.isSet(idx)) {
|
||||||
|
const bit = self.indexToBit(idx);
|
||||||
|
self.bitmaps[idx / ENTRIES_PER_BITMAP] &= ~bit;
|
||||||
|
self.num_free_entries += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Convert a global bitmap index into the bit corresponding to an entry within a single BitmapType.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN self: *Self - The bitmap to use.
|
||||||
|
/// IN idx: u32 - The index into all of the bitmap's entries.
|
||||||
|
///
|
||||||
|
/// Return: BitmapType.
|
||||||
|
/// The bit corresponding to that index but within a single BitmapType.
|
||||||
|
///
|
||||||
|
fn indexToBit(self: *Self, idx: u32) BitmapType {
|
||||||
|
return @as(BitmapType, 1) << @intCast(IndexType, idx % ENTRIES_PER_BITMAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Set the first free entry within the bitmaps as occupied.
|
||||||
|
///
|
||||||
|
/// Return: ?u32.
|
||||||
|
/// The index within all bitmaps that was set or null if there wasn't one free.
|
||||||
|
/// 0 .. ENTRIES_PER_BITMAP - 1 if in the first bitmap, ENTRIES_PER_BITMAP .. ENTRIES_PER_BITMAP * 2 - 1 if in the second etc.
|
||||||
|
///
|
||||||
|
pub fn setFirstFree(self: *Self) ?u32 {
|
||||||
|
if (self.num_free_entries == 0) return null;
|
||||||
|
for (self.bitmaps) |*bmp, i| {
|
||||||
|
if (bmp.* == BITMAP_FULL)
|
||||||
|
continue;
|
||||||
|
const bit = @truncate(IndexType, @ctz(BitmapType, ~bmp.*));
|
||||||
|
const idx = bit + @intCast(u32, i) * ENTRIES_PER_BITMAP;
|
||||||
|
// Failing here means that the index is outside of the bitmap, so there are no free entries
|
||||||
|
self.setEntry(idx) catch return null;
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Check if an entry is set.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN self: *Bitmap - The bitmap to check.
|
||||||
|
/// IN idx: u32 - The entry to check.
|
||||||
|
///
|
||||||
|
/// Return: bool.
|
||||||
|
/// True if the entry is set, else false.
|
||||||
|
///
|
||||||
|
/// Error: BitmapError.
|
||||||
|
/// OutOfBounds: The index given is out of bounds.
|
||||||
|
///
|
||||||
|
pub fn isSet(self: *Self, idx: u32) BitmapError!bool {
|
||||||
|
if (idx >= self.num_entries) return BitmapError.OutOfBounds;
|
||||||
|
return (self.bitmaps[idx / ENTRIES_PER_BITMAP] & self.indexToBit(idx)) != 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "setEntry" {
|
||||||
|
var bmp = try Bitmap(u32).init(31, std.heap.direct_allocator);
|
||||||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||||||
|
|
||||||
|
try bmp.setEntry(0);
|
||||||
|
testing.expectEqual(@as(u32, 1), bmp.bitmaps[0]);
|
||||||
|
testing.expectEqual(@as(u32, 30), bmp.num_free_entries);
|
||||||
|
|
||||||
|
try bmp.setEntry(1);
|
||||||
|
testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]);
|
||||||
|
testing.expectEqual(@as(u32, 29), bmp.num_free_entries);
|
||||||
|
|
||||||
|
// Repeat setting entry 1 to make sure state doesn't change
|
||||||
|
try bmp.setEntry(1);
|
||||||
|
testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]);
|
||||||
|
testing.expectEqual(@as(u32, 29), bmp.num_free_entries);
|
||||||
|
|
||||||
|
testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.setEntry(31));
|
||||||
|
testing.expectEqual(@as(u32, 29), bmp.num_free_entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "clearEntry" {
|
||||||
|
var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator);
|
||||||
|
testing.expectEqual(@as(u32, 32), bmp.num_free_entries);
|
||||||
|
|
||||||
|
try bmp.setEntry(0);
|
||||||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||||||
|
try bmp.setEntry(1);
|
||||||
|
testing.expectEqual(@as(u32, 30), bmp.num_free_entries);
|
||||||
|
testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]);
|
||||||
|
try bmp.clearEntry(0);
|
||||||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||||||
|
testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]);
|
||||||
|
|
||||||
|
// Repeat to make sure state doesn't change
|
||||||
|
try bmp.clearEntry(0);
|
||||||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||||||
|
testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]);
|
||||||
|
|
||||||
|
// Try clearing an unset entry to make sure state doesn't change
|
||||||
|
try bmp.clearEntry(2);
|
||||||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||||||
|
testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]);
|
||||||
|
|
||||||
|
testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.clearEntry(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "setFirstFree multiple bitmaps" {
|
||||||
|
var bmp = try Bitmap(u8).init(9, std.heap.direct_allocator);
|
||||||
|
|
||||||
|
// Allocate the first entry
|
||||||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, 0);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], 1);
|
||||||
|
|
||||||
|
// Allocate the second entry
|
||||||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, 1);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], 3);
|
||||||
|
|
||||||
|
// Allocate the entirety of the first bitmap
|
||||||
|
var entry: u32 = 2;
|
||||||
|
var expected: u8 = 7;
|
||||||
|
while (entry < Bitmap(u8).ENTRIES_PER_BITMAP) {
|
||||||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, entry);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], expected);
|
||||||
|
if (entry + 1 < Bitmap(u8).ENTRIES_PER_BITMAP) {
|
||||||
|
entry += 1;
|
||||||
|
expected = expected * 2 + 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try allocating an entry in the next bitmap
|
||||||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, Bitmap(u8).ENTRIES_PER_BITMAP);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], Bitmap(u8).BITMAP_FULL);
|
||||||
|
testing.expectEqual(bmp.bitmaps[1], 1);
|
||||||
|
|
||||||
|
// We should no longer be able to allocate any entries
|
||||||
|
testing.expectEqual(bmp.setFirstFree(), null);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], Bitmap(u8).BITMAP_FULL);
|
||||||
|
testing.expectEqual(bmp.bitmaps[1], 1);
|
||||||
|
}
|
||||||
|
test "setFirstFree" {
|
||||||
|
var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator);
|
||||||
|
|
||||||
|
// Allocate the first entry
|
||||||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, 0);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], 1);
|
||||||
|
|
||||||
|
// Allocate the second entry
|
||||||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, 1);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], 3);
|
||||||
|
|
||||||
|
// Make all but the MSB occupied and try to allocate it
|
||||||
|
bmp.bitmaps[0] = Bitmap(u32).BITMAP_FULL & ~@as(u32, 1 << (Bitmap(u32).ENTRIES_PER_BITMAP - 1));
|
||||||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, Bitmap(u32).ENTRIES_PER_BITMAP - 1);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], Bitmap(u32).BITMAP_FULL);
|
||||||
|
|
||||||
|
// We should no longer be able to allocate any entries
|
||||||
|
testing.expectEqual(bmp.setFirstFree(), null);
|
||||||
|
testing.expectEqual(bmp.bitmaps[0], Bitmap(u32).BITMAP_FULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "isSet" {
|
||||||
|
var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator);
|
||||||
|
|
||||||
|
bmp.bitmaps[0] = 1;
|
||||||
|
// Make sure that only the set entry is considered set
|
||||||
|
testing.expect(try bmp.isSet(0));
|
||||||
|
var i: u32 = 1;
|
||||||
|
while (i < bmp.num_entries) : (i += 1) {
|
||||||
|
testing.expect(!try bmp.isSet(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
bmp.bitmaps[0] = 3;
|
||||||
|
testing.expect(try bmp.isSet(0));
|
||||||
|
testing.expect(try bmp.isSet(1));
|
||||||
|
i = 2;
|
||||||
|
while (i < bmp.num_entries) : (i += 1) {
|
||||||
|
testing.expect(!try bmp.isSet(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
bmp.bitmaps[0] = 11;
|
||||||
|
testing.expect(try bmp.isSet(0));
|
||||||
|
testing.expect(try bmp.isSet(1));
|
||||||
|
testing.expect(!try bmp.isSet(2));
|
||||||
|
testing.expect(try bmp.isSet(3));
|
||||||
|
i = 4;
|
||||||
|
while (i < bmp.num_entries) : (i += 1) {
|
||||||
|
testing.expect(!try bmp.isSet(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.isSet(33));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "indexToBit" {
|
||||||
|
var bmp = try Bitmap(u8).init(10, std.heap.direct_allocator);
|
||||||
|
testing.expectEqual(bmp.indexToBit(0), 1);
|
||||||
|
testing.expectEqual(bmp.indexToBit(1), 2);
|
||||||
|
testing.expectEqual(bmp.indexToBit(2), 4);
|
||||||
|
testing.expectEqual(bmp.indexToBit(3), 8);
|
||||||
|
testing.expectEqual(bmp.indexToBit(4), 16);
|
||||||
|
testing.expectEqual(bmp.indexToBit(5), 32);
|
||||||
|
testing.expectEqual(bmp.indexToBit(6), 64);
|
||||||
|
testing.expectEqual(bmp.indexToBit(7), 128);
|
||||||
|
testing.expectEqual(bmp.indexToBit(8), 1);
|
||||||
|
testing.expectEqual(bmp.indexToBit(9), 2);
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ const tty = @import("tty.zig");
|
||||||
const vga = @import("vga.zig");
|
const vga = @import("vga.zig");
|
||||||
const log = @import("log.zig");
|
const log = @import("log.zig");
|
||||||
const serial = @import("serial.zig");
|
const serial = @import("serial.zig");
|
||||||
|
const pmm = @import("pmm.zig");
|
||||||
const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig");
|
const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig");
|
||||||
const panic_root = if (is_test) @import(mock_path ++ "panic_mock.zig") else @import("panic.zig");
|
const panic_root = if (is_test) @import(mock_path ++ "panic_mock.zig") else @import("panic.zig");
|
||||||
const options = @import("build_options");
|
const options = @import("build_options");
|
||||||
|
@ -42,6 +43,7 @@ export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void {
|
||||||
var buffer = mem_profile.vaddr_end[0..mem_profile.fixed_alloc_size];
|
var buffer = mem_profile.vaddr_end[0..mem_profile.fixed_alloc_size];
|
||||||
var fixed_allocator = std.heap.FixedBufferAllocator.init(buffer);
|
var fixed_allocator = std.heap.FixedBufferAllocator.init(buffer);
|
||||||
|
|
||||||
|
pmm.init(&mem_profile, &fixed_allocator.allocator);
|
||||||
log.logInfo("Init arch " ++ @tagName(builtin.arch) ++ "\n", .{});
|
log.logInfo("Init arch " ++ @tagName(builtin.arch) ++ "\n", .{});
|
||||||
arch.init(mb_info, &mem_profile, &fixed_allocator.allocator);
|
arch.init(mb_info, &mem_profile, &fixed_allocator.allocator);
|
||||||
log.logInfo("Arch init done\n", .{});
|
log.logInfo("Arch init done\n", .{});
|
||||||
|
|
|
@ -4,13 +4,30 @@ const expectEqual = std.testing.expectEqual;
|
||||||
const log = @import("log.zig");
|
const log = @import("log.zig");
|
||||||
|
|
||||||
pub const MemProfile = struct {
|
pub const MemProfile = struct {
|
||||||
|
/// The virtual end address of the kernel code.
|
||||||
vaddr_end: [*]u8,
|
vaddr_end: [*]u8,
|
||||||
|
|
||||||
|
/// The virtual end address of the kernel code.
|
||||||
vaddr_start: [*]u8,
|
vaddr_start: [*]u8,
|
||||||
|
|
||||||
|
/// The physical end address of the kernel code.
|
||||||
physaddr_end: [*]u8,
|
physaddr_end: [*]u8,
|
||||||
|
|
||||||
|
/// The physical start address of the kernel code.
|
||||||
physaddr_start: [*]u8,
|
physaddr_start: [*]u8,
|
||||||
|
|
||||||
|
/// The amount of memory in the system, in kilobytes.
|
||||||
mem_kb: u32,
|
mem_kb: u32,
|
||||||
|
|
||||||
|
/// The size of the fixed buffer allocator used as the first memory allocator.
|
||||||
fixed_alloc_size: u32,
|
fixed_alloc_size: u32,
|
||||||
|
|
||||||
|
/// The boot modules provided by the bootloader.
|
||||||
boot_modules: []multiboot.multiboot_module_t,
|
boot_modules: []multiboot.multiboot_module_t,
|
||||||
|
|
||||||
|
/// The memory map provided by the bootloader. Desribes which areas of memory are available and
|
||||||
|
/// which are reserved.
|
||||||
|
mem_map: []multiboot.multiboot_memory_map_t,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The virtual end of the kernel code
|
/// The virtual end of the kernel code
|
||||||
|
@ -47,8 +64,11 @@ var ADDR_OFFSET: usize = undefined;
|
||||||
///
|
///
|
||||||
pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile {
|
pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile {
|
||||||
log.logInfo("Init mem\n", .{});
|
log.logInfo("Init mem\n", .{});
|
||||||
|
defer log.logInfo("Done\n", .{});
|
||||||
const mods_count = mb_info.mods_count;
|
const mods_count = mb_info.mods_count;
|
||||||
ADDR_OFFSET = @ptrToInt(&KERNEL_ADDR_OFFSET);
|
ADDR_OFFSET = @ptrToInt(&KERNEL_ADDR_OFFSET);
|
||||||
|
const mmap_addr = mb_info.mmap_addr;
|
||||||
|
const num_mmap_entries = mb_info.mmap_length / @sizeOf(multiboot.multiboot_memory_map_t);
|
||||||
const mem_profile = MemProfile{
|
const mem_profile = MemProfile{
|
||||||
.vaddr_end = @ptrCast([*]u8, &KERNEL_VADDR_END),
|
.vaddr_end = @ptrCast([*]u8, &KERNEL_VADDR_END),
|
||||||
.vaddr_start = @ptrCast([*]u8, &KERNEL_VADDR_START),
|
.vaddr_start = @ptrCast([*]u8, &KERNEL_VADDR_START),
|
||||||
|
@ -58,8 +78,8 @@ pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile {
|
||||||
.mem_kb = mb_info.mem_upper + mb_info.mem_lower + 1024,
|
.mem_kb = mb_info.mem_upper + mb_info.mem_lower + 1024,
|
||||||
.fixed_alloc_size = FIXED_ALLOC_SIZE,
|
.fixed_alloc_size = FIXED_ALLOC_SIZE,
|
||||||
.boot_modules = @intToPtr([*]multiboot.multiboot_mod_list, physToVirt(mb_info.mods_addr))[0..mods_count],
|
.boot_modules = @intToPtr([*]multiboot.multiboot_mod_list, physToVirt(mb_info.mods_addr))[0..mods_count],
|
||||||
|
.mem_map = @intToPtr([*]multiboot.multiboot_memory_map_t, mmap_addr)[0..num_mmap_entries],
|
||||||
};
|
};
|
||||||
log.logInfo("Done\n", .{});
|
|
||||||
return mem_profile;
|
return mem_profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
220
src/kernel/pmm.zig
Normal file
220
src/kernel/pmm.zig
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
const is_test = @import("builtin").is_test;
|
||||||
|
const std = @import("std");
|
||||||
|
const build_options = @import("build_options");
|
||||||
|
const mock_path = build_options.mock_path;
|
||||||
|
const arch = @import("arch.zig").internals;
|
||||||
|
const MemProfile = (if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig")).MemProfile;
|
||||||
|
const testing = std.testing;
|
||||||
|
const panic = @import("panic.zig").panic;
|
||||||
|
const log = @import("log.zig");
|
||||||
|
const MEMORY_AVAILABLE = @import("multiboot.zig").MULTIBOOT_MEMORY_AVAILABLE;
|
||||||
|
const Bitmap = @import("bitmap.zig").Bitmap;
|
||||||
|
|
||||||
|
const PmmBitmap = Bitmap(u32);
|
||||||
|
|
||||||
|
/// The possible errors thrown by bitmap functions
|
||||||
|
const PmmError = error{
|
||||||
|
/// The address given hasn't been allocated
|
||||||
|
NotAllocated,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The size of memory associated with each bitmap entry
|
||||||
|
const BLOCK_SIZE = arch.MEMORY_BLOCK_SIZE;
|
||||||
|
|
||||||
|
var bitmap: PmmBitmap = undefined;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Set the bitmap entry for an address as occupied
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN addr: usize - The address.
|
||||||
|
///
|
||||||
|
/// Error: PmmBitmap.BitmapError.
|
||||||
|
/// *: See PmmBitmap.setEntry. Could occur if the address is out of bounds.
|
||||||
|
///
|
||||||
|
fn setAddr(addr: usize) PmmBitmap.BitmapError!void {
|
||||||
|
try bitmap.setEntry(@intCast(u32, addr / BLOCK_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Check if an address is set as occupied.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN addr: usize - The address to check.
|
||||||
|
///
|
||||||
|
/// Return: True if occupied, else false.
|
||||||
|
///
|
||||||
|
/// Error: PmmBitmap.BitmapError.
|
||||||
|
/// *: See PmmBitmap.setEntry. Could occur if the address is out of bounds.
|
||||||
|
///
|
||||||
|
fn isSet(addr: usize) PmmBitmap.BitmapError!bool {
|
||||||
|
return bitmap.isSet(@intCast(u32, addr / BLOCK_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Find the next free memory block, set it as occupied and return it. The region allocated will be of size BLOCK_SIZE.
|
||||||
|
///
|
||||||
|
/// Return: The address that was allocated.
|
||||||
|
///
|
||||||
|
pub fn alloc() ?usize {
|
||||||
|
if (bitmap.setFirstFree()) |entry| {
|
||||||
|
return entry * BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Set the address as free so it can be allocated in the future. This will free a block of size BLOCK_SIZE.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN addr: usize - The previously allocated address to free. Will be aligned down to the nearest multiple of BLOCK_SIZE.
|
||||||
|
///
|
||||||
|
/// Error: PmmError || PmmBitmap.BitmapError.
|
||||||
|
/// PmmError.NotAllocated: The address wasn't allocated.
|
||||||
|
/// PmmBitmap.BitmapError.OutOfBounds: The address given was out of bounds.
|
||||||
|
///
|
||||||
|
pub fn free(addr: usize) (PmmBitmap.BitmapError || PmmError)!void {
|
||||||
|
const idx = @intCast(u32, addr / BLOCK_SIZE);
|
||||||
|
if (try bitmap.isSet(idx)) {
|
||||||
|
try bitmap.clearEntry(idx);
|
||||||
|
} else {
|
||||||
|
return PmmError.NotAllocated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Intiialise the physical memory manager and set all unavailable regions as occupied (those from the memory map and those from the linker symbols).
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN mem: *const MemProfile - The system's memory profile.
|
||||||
|
/// IN allocator: *std.mem.Allocator - The allocator to use to allocate the bitmaps.
|
||||||
|
///
|
||||||
|
pub fn init(mem: *const MemProfile, allocator: *std.mem.Allocator) void {
|
||||||
|
log.logInfo("Init pmm\n", .{});
|
||||||
|
bitmap = PmmBitmap.init(mem.mem_kb * 1024 / BLOCK_SIZE, allocator) catch @panic("Bitmap allocation failed");
|
||||||
|
|
||||||
|
// Occupy the regions of memory that the memory map describes as reserved
|
||||||
|
for (mem.mem_map) |entry| {
|
||||||
|
if (entry.@"type" != MEMORY_AVAILABLE) {
|
||||||
|
var addr = std.mem.alignBackward(@intCast(usize, entry.addr), BLOCK_SIZE);
|
||||||
|
var end = @intCast(usize, entry.addr + (entry.len - 1));
|
||||||
|
// If the end address can be aligned without overflowing then align it
|
||||||
|
if (end <= std.math.maxInt(usize) - BLOCK_SIZE)
|
||||||
|
end = std.mem.alignForward(end, BLOCK_SIZE);
|
||||||
|
while (addr < end) : (addr += BLOCK_SIZE) {
|
||||||
|
setAddr(addr) catch |e| switch (e) {
|
||||||
|
// We can ignore out of bounds errors as the memory won't be available anyway
|
||||||
|
PmmBitmap.BitmapError.OutOfBounds => break,
|
||||||
|
else => panic(@errorReturnTrace(), "Failed setting address 0x{x} from memory map as occupied: {}", .{ addr, e }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Occupy kernel memory
|
||||||
|
var addr = std.mem.alignBackward(@ptrToInt(mem.physaddr_start), BLOCK_SIZE);
|
||||||
|
while (addr < @ptrToInt(mem.physaddr_end)) : (addr += BLOCK_SIZE) {
|
||||||
|
setAddr(addr) catch |e| switch (e) {
|
||||||
|
error.OutOfBounds => panic(@errorReturnTrace(), "Failed setting kernel code address 0x{x} as occupied. The amount of system memory seems to be too low for the kernel image: {}", .{ addr, e }),
|
||||||
|
else => panic(@errorReturnTrace(), "Failed setting kernel code address 0x{x} as occupied: {}", .{ addr, e }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
log.logInfo("Done\n", .{});
|
||||||
|
if (build_options.rt_test) {
|
||||||
|
runtimeTests(mem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Allocate all blocks and make sure they don't overlap with any reserved addresses.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// IN mem: *const MemProfile - The memory profile to check for reserved memory regions.
|
||||||
|
///
|
||||||
|
fn runtimeTests(mem: *const MemProfile) void {
|
||||||
|
// Make sure that occupied memory can't be allocated
|
||||||
|
var prev_alloc: usize = std.math.maxInt(usize);
|
||||||
|
while (alloc()) |alloced| {
|
||||||
|
if (prev_alloc == alloced) {
|
||||||
|
panic(null, "PMM allocated the same address twice: 0x{x}", .{alloced});
|
||||||
|
}
|
||||||
|
prev_alloc = alloced;
|
||||||
|
for (mem.mem_map) |entry| {
|
||||||
|
if (entry.@"type" != MEMORY_AVAILABLE) {
|
||||||
|
var addr = std.mem.alignBackward(@intCast(usize, entry.addr), BLOCK_SIZE);
|
||||||
|
if (addr == alloced) {
|
||||||
|
panic(null, "PMM allocated an address that should be reserved by the memory map: 0x{x}", .{addr});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alloced >= std.mem.alignBackward(@ptrToInt(mem.physaddr_start), BLOCK_SIZE) and alloced < std.mem.alignForward(@ptrToInt(mem.physaddr_end), BLOCK_SIZE)) {
|
||||||
|
panic(null, "PMM allocated an address that should be reserved by kernel code: 0x{x}", .{alloced});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.logInfo("PMM: Tested allocation\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "alloc" {
|
||||||
|
bitmap = try Bitmap(u32).init(32, std.heap.direct_allocator);
|
||||||
|
comptime var addr = 0;
|
||||||
|
comptime var i = 0;
|
||||||
|
// Allocate all entries, making sure they succeed and return the correct addresses
|
||||||
|
inline while (i < 32) : ({
|
||||||
|
i += 1;
|
||||||
|
addr += BLOCK_SIZE;
|
||||||
|
}) {
|
||||||
|
testing.expect(!(try isSet(addr)));
|
||||||
|
testing.expect(alloc().? == addr);
|
||||||
|
testing.expect(try isSet(addr));
|
||||||
|
}
|
||||||
|
// Allocation should now fail
|
||||||
|
testing.expect(alloc() == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "free" {
|
||||||
|
bitmap = try Bitmap(u32).init(32, std.heap.direct_allocator);
|
||||||
|
comptime var i = 0;
|
||||||
|
// Allocate and free all entries
|
||||||
|
inline while (i < 32) : (i += 1) {
|
||||||
|
const addr = alloc().?;
|
||||||
|
testing.expect(try isSet(addr));
|
||||||
|
try free(addr);
|
||||||
|
testing.expect(!(try isSet(addr)));
|
||||||
|
// Double frees should be caught
|
||||||
|
testing.expectError(PmmError.NotAllocated, free(addr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "setAddr and isSet" {
|
||||||
|
const num_entries: u32 = 32;
|
||||||
|
bitmap = try Bitmap(u32).init(num_entries, std.heap.direct_allocator);
|
||||||
|
var addr: u32 = 0;
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < num_entries) : ({
|
||||||
|
i += 1;
|
||||||
|
addr += BLOCK_SIZE;
|
||||||
|
}) {
|
||||||
|
// Ensure all previous blocks are still set
|
||||||
|
var h: u32 = 0;
|
||||||
|
var addr2: u32 = 0;
|
||||||
|
while (h < i) : ({
|
||||||
|
h += 1;
|
||||||
|
addr2 += BLOCK_SIZE;
|
||||||
|
}) {
|
||||||
|
testing.expect(try isSet(addr2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the current block
|
||||||
|
try setAddr(addr);
|
||||||
|
testing.expect(try isSet(addr));
|
||||||
|
|
||||||
|
// Ensure all successive entries are not set
|
||||||
|
var j: u32 = i + 1;
|
||||||
|
var addr3: u32 = addr + BLOCK_SIZE;
|
||||||
|
while (j < num_entries) : ({
|
||||||
|
j += 1;
|
||||||
|
addr3 += BLOCK_SIZE;
|
||||||
|
}) {
|
||||||
|
testing.expect(!try isSet(addr3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ const MemProfile = mem.MemProfile;
|
||||||
const gdt = @import("gdt_mock.zig");
|
const gdt = @import("gdt_mock.zig");
|
||||||
const idt = @import("idt_mock.zig");
|
const idt = @import("idt_mock.zig");
|
||||||
const multiboot = @import("../../../src/kernel/multiboot.zig");
|
const multiboot = @import("../../../src/kernel/multiboot.zig");
|
||||||
|
const paging = @import("paging_mock.zig");
|
||||||
|
|
||||||
const mock_framework = @import("mock_framework.zig");
|
const mock_framework = @import("mock_framework.zig");
|
||||||
pub const initTest = mock_framework.initTest;
|
pub const initTest = mock_framework.initTest;
|
||||||
|
@ -35,6 +36,8 @@ pub const InterruptContext = struct {
|
||||||
ss: u32,
|
ss: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const MEMORY_BLOCK_SIZE = paging.PAGE_SIZE_4KB;
|
||||||
|
|
||||||
pub fn outb(port: u16, data: u8) void {
|
pub fn outb(port: u16, data: u8) void {
|
||||||
return mock_framework.performAction("outb", void, .{ port, data });
|
return mock_framework.performAction("outb", void, .{ port, data });
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub const MemProfile = struct {
|
||||||
physaddr_start: [*]u8,
|
physaddr_start: [*]u8,
|
||||||
mem_kb: u32,
|
mem_kb: u32,
|
||||||
fixed_alloc_size: u32,
|
fixed_alloc_size: u32,
|
||||||
|
mem_map: []multiboot.multiboot_memory_map_t,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The virtual/physical start/end of the kernel code
|
// The virtual/physical start/end of the kernel code
|
||||||
|
@ -28,5 +29,6 @@ pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile {
|
||||||
// Total memory available including the initial 1MiB that grub doesn't include
|
// Total memory available including the initial 1MiB that grub doesn't include
|
||||||
.mem_kb = mb_info.mem_upper + mb_info.mem_lower + 1024,
|
.mem_kb = mb_info.mem_upper + mb_info.mem_lower + 1024,
|
||||||
.fixed_alloc_size = FIXED_ALLOC_SIZE,
|
.fixed_alloc_size = FIXED_ALLOC_SIZE,
|
||||||
|
.mem_map = undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
1
test/mock/kernel/paging_mock.zig
Normal file
1
test/mock/kernel/paging_mock.zig
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub const PAGE_SIZE_4KB = 4096;
|
|
@ -36,6 +36,8 @@ def get_pre_archinit_cases():
|
||||||
TestCase("Log warning tests", [r"Test WARNING level", r"Test WARNING level with args a, 1", r"Test WARNING function", r"Test WARNING function with args a, 1"], "\[WARNING\] "),
|
TestCase("Log warning tests", [r"Test WARNING level", r"Test WARNING level with args a, 1", r"Test WARNING function", r"Test WARNING function with args a, 1"], "\[WARNING\] "),
|
||||||
TestCase("Log error tests", [r"Test ERROR level", r"Test ERROR level with args a, 1", r"Test ERROR function", r"Test ERROR function with args a, 1"], "\[ERROR\] "),
|
TestCase("Log error tests", [r"Test ERROR level", r"Test ERROR level with args a, 1", r"Test ERROR function", r"Test ERROR function with args a, 1"], "\[ERROR\] "),
|
||||||
TestCase("Mem init", [r"Init mem", r"Done"]),
|
TestCase("Mem init", [r"Init mem", r"Done"]),
|
||||||
|
TestCase("PMM init", [r"Init pmm", r"Done"]),
|
||||||
|
TestCase("PMM tests", [r"PMM: Tested allocation"]),
|
||||||
TestCase("Arch init starts", [r"Init arch \w+"])
|
TestCase("Arch init starts", [r"Init arch \w+"])
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue