feat: base PM scheduller

This commit is contained in:
Faynot
2026-06-28 20:31:23 +03:00
parent f5dd56a379
commit 1b38c7f445
9 changed files with 667 additions and 145 deletions

View File

@@ -5,6 +5,7 @@ pub const PAGE_SIZE: u64 = 4096;
pub struct BitmapPMM {
bitmap: &'static mut [u8],
ref_counts: &'static mut [u16],
total_pages: usize,
used_pages: usize,
/// Byte index hint: next search starts here to amortise O(N) scans.
@@ -28,30 +29,36 @@ impl BitmapPMM {
.unwrap_or(0);
let total_pages = (max_addr / PAGE_SIZE) as usize;
let bitmap_size = total_pages.div_ceil(8);
// Find a usable region large enough to hold the bitmap.
let bitmap_phys = mmap.entries().iter()
let bitmap_size = total_pages.div_ceil(8);
let ref_counts_size = total_pages * core::mem::size_of::<u16>();
let total_meta_size = bitmap_size + ref_counts_size;
let meta_phys = mmap.entries().iter()
.find(|e| {
e.entry_type == limine::memory_map::EntryType::USABLE
&& e.length >= bitmap_size as u64
&& e.length >= total_meta_size as u64
})
.map(|e| e.base)
.expect("PMM: no usable region large enough for the bitmap");
.expect("PMM: no usable region large enough for metadata");
let bitmap_ptr = (meta_phys + hhdm_offset) as *mut u8;
let ref_counts_ptr = (meta_phys + hhdm_offset + bitmap_size as u64) as *mut u16;
let bitmap_ptr = (bitmap_phys + hhdm_offset) as *mut u8;
// Mark everything as used (all bits = 1) and free usable entries below.
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
bitmap.fill(0xFF);
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
ref_counts.fill(1);
let mut pmm = Self {
bitmap,
ref_counts,
total_pages,
used_pages: total_pages,
last_byte: 0,
};
// Free all usable pages …
for entry in mmap.entries() {
if entry.entry_type == limine::memory_map::EntryType::USABLE {
for addr in (entry.base..entry.base + entry.length).step_by(PAGE_SIZE as usize) {
@@ -60,12 +67,11 @@ impl BitmapPMM {
}
}
// … then re-lock the bitmap pages themselves …
for addr in (bitmap_phys..bitmap_phys + bitmap_size as u64).step_by(PAGE_SIZE as usize) {
let meta_end = (meta_phys + total_meta_size as u64 + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
for addr in (meta_phys..meta_end).step_by(PAGE_SIZE as usize) {
pmm.lock_frame(PhysAddr(addr));
}
// … and the null page (physical 0x0 must never be returned as a valid frame).
pmm.lock_frame(PhysAddr(0));
*PMM.lock() = Some(pmm);
@@ -73,37 +79,52 @@ impl BitmapPMM {
//Core operations
/// Mark a frame as free. Idempotent (double-free is a no-op, not UB).
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
if idx >= self.total_pages { return; }
let byte = idx / 8;
let bit = idx % 8;
if self.bitmap[byte] & (1 << bit) != 0 {
self.bitmap[byte] &= !(1 << bit);
self.used_pages -= 1;
// Pull the hint back so the freed page can be found quickly.
if byte < self.last_byte { self.last_byte = byte; }
self.ref_counts[idx] = self.ref_counts[idx].saturating_sub(1);
if self.ref_counts[idx] == 0 {
self.bitmap[byte] &= !(1 << bit);
self.used_pages -= 1;
if byte < self.last_byte { self.last_byte = byte; }
}
}
}
/// Mark a frame as allocated (reserved). Idempotent.
pub fn lock_frame(&mut self, phys_addr: PhysAddr) {
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
if idx >= self.total_pages { return; }
let byte = idx / 8;
let bit = idx % 8;
if self.bitmap[byte] & (1 << bit) == 0 {
self.bitmap[byte] |= 1 << bit;
self.ref_counts[idx] = 1;
self.used_pages += 1;
} else if self.ref_counts[idx] == 0 {
self.ref_counts[idx] = 1;
}
}
pub fn inc_ref_frame(&mut self, phys_addr: PhysAddr) {
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
if idx >= self.total_pages { return; }
let byte = idx / 8;
let bit = idx % 8;
if self.bitmap[byte] & (1 << bit) != 0 {
self.ref_counts[idx] = self.ref_counts[idx].saturating_add(1);
}
}
/// Allocate one physical frame.
///
/// Uses a two-pass search (linear scan from `last_byte` hint, then wraps
/// to 0 if not found in the first pass) to avoid returning `None` when
/// free frames exist before the hint.
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
let len = self.bitmap.len();
@@ -115,7 +136,6 @@ impl BitmapPMM {
};
for byte_idx in from..to {
// Fast path: skip fully-used bytes.
if self.bitmap[byte_idx] == 0xFF { continue; }
for bit in 0..8u8 {
@@ -123,8 +143,8 @@ impl BitmapPMM {
let page_idx = byte_idx * 8 + bit as usize;
if page_idx >= self.total_pages { return None; }
// Mark allocated.
self.bitmap[byte_idx] |= 1 << bit;
self.ref_counts[page_idx] = 1;
self.used_pages += 1;
self.last_byte = byte_idx;
@@ -134,17 +154,9 @@ impl BitmapPMM {
}
}
None // genuinely out of memory
None
}
/// Try to allocate `count` **contiguous** physical frames.
///
/// Returns the base physical address of the run, or `None` if no run of
/// sufficient length exists. This is needed for (e.g.) allocating 2 MiB
/// huge-page aligned regions or DMA buffers that must be physically
/// contiguous.
///
/// O(N) worst-case; use sparingly and prefer small counts.
pub fn alloc_contiguous(&mut self, count: usize) -> Option<PhysAddr> {
if count == 0 { return None; }
@@ -154,13 +166,15 @@ impl BitmapPMM {
for page_idx in 0..self.total_pages {
let byte = page_idx / 8;
let bit = page_idx % 8;
if self.bitmap[byte] & (1 << bit) == 0 {
if run_len == 0 { run_start = page_idx; }
run_len += 1;
if run_len == count {
// Lock every frame in the run.
for i in run_start..run_start + count {
self.bitmap[i / 8] |= 1 << (i % 8);
self.ref_counts[i] = 1;
}
self.used_pages += count;
self.last_byte = run_start / 8;
@@ -191,6 +205,12 @@ pub fn free_frame(addr: PhysAddr) {
}
}
pub fn inc_ref_frame(addr: PhysAddr) {
if let Some(pmm) = PMM.lock().as_mut() {
pmm.inc_ref_frame(addr);
}
}
pub fn get_stats() -> (usize, usize) {
if let Some(pmm) = PMM.lock().as_ref() {
(pmm.used_pages(), pmm.total_pages())
@@ -198,3 +218,7 @@ pub fn get_stats() -> (usize, usize) {
(0, 0)
}
}