feat: major memory managment & PMactor
This commit is contained in:
@@ -4,19 +4,22 @@ use crate::mem::allocator::Locked;
|
||||
pub const PAGE_SIZE: u64 = 4096;
|
||||
|
||||
pub struct BitmapPMM {
|
||||
bitmap: &'static mut [u8],
|
||||
bitmap: &'static mut [u8],
|
||||
total_pages: usize,
|
||||
used_pages: usize,
|
||||
last_idx: usize,
|
||||
used_pages: usize,
|
||||
/// Byte index hint: next search starts here to amortise O(N) scans.
|
||||
last_byte: usize,
|
||||
}
|
||||
|
||||
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
||||
|
||||
impl BitmapPMM {
|
||||
#[allow(dead_code)]
|
||||
pub fn used_pages(&self) -> usize { self.used_pages }
|
||||
pub fn used_pages(&self) -> usize { self.used_pages }
|
||||
#[allow(dead_code)]
|
||||
pub fn total_pages(&self) -> usize { self.total_pages }
|
||||
#[allow(dead_code)]
|
||||
pub fn free_pages(&self) -> usize { self.total_pages.saturating_sub(self.used_pages) }
|
||||
|
||||
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse, hhdm_offset: u64) {
|
||||
let max_addr = mmap.entries().iter()
|
||||
@@ -27,22 +30,28 @@ impl BitmapPMM {
|
||||
let total_pages = (max_addr / PAGE_SIZE) as usize;
|
||||
let bitmap_size = total_pages.div_ceil(8);
|
||||
|
||||
let bitmap_phys_addr = mmap.entries().iter()
|
||||
.find(|e| e.entry_type == limine::memory_map::EntryType::USABLE && e.length >= bitmap_size as u64)
|
||||
// Find a usable region large enough to hold the bitmap.
|
||||
let bitmap_phys = mmap.entries().iter()
|
||||
.find(|e| {
|
||||
e.entry_type == limine::memory_map::EntryType::USABLE
|
||||
&& e.length >= bitmap_size as u64
|
||||
})
|
||||
.map(|e| e.base)
|
||||
.expect("PMM: Insufficient memory for bitmap");
|
||||
.expect("PMM: no usable region large enough for the bitmap");
|
||||
|
||||
let bitmap_virt_ptr = (bitmap_phys_addr + hhdm_offset) as *mut u8;
|
||||
let bitmap_slice = unsafe { core::slice::from_raw_parts_mut(bitmap_virt_ptr, bitmap_size) };
|
||||
bitmap_slice.fill(0xFF);
|
||||
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 mut pmm = Self {
|
||||
bitmap: bitmap_slice,
|
||||
bitmap,
|
||||
total_pages,
|
||||
used_pages: total_pages,
|
||||
last_idx: 0,
|
||||
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) {
|
||||
@@ -51,71 +60,131 @@ impl BitmapPMM {
|
||||
}
|
||||
}
|
||||
|
||||
for addr in (bitmap_phys_addr..bitmap_phys_addr + bitmap_size as u64).step_by(PAGE_SIZE as usize) {
|
||||
// … then re-lock the bitmap pages themselves …
|
||||
for addr in (bitmap_phys..bitmap_phys + bitmap_size as u64).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);
|
||||
*PMM.lock() = Some(pmm);
|
||||
}
|
||||
|
||||
// ── 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 {
|
||||
let byte_idx = idx / 8;
|
||||
let bit_idx = idx % 8;
|
||||
if (self.bitmap[byte_idx] & (1 << bit_idx)) != 0 {
|
||||
self.bitmap[byte_idx] &= !(1 << bit_idx);
|
||||
self.used_pages -= 1;
|
||||
}
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let byte_idx = idx / 8;
|
||||
let bit_idx = idx % 8;
|
||||
if (self.bitmap[byte_idx] & (1 << bit_idx)) == 0 {
|
||||
self.bitmap[byte_idx] |= 1 << bit_idx;
|
||||
self.used_pages += 1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
||||
let start_byte = self.last_idx / 8;
|
||||
for i in start_byte..(self.bitmap.len()) {
|
||||
if self.bitmap[i] != 0xFF {
|
||||
for bit in 0..8 {
|
||||
let idx = i * 8 + bit;
|
||||
if idx >= self.total_pages { return None; }
|
||||
|
||||
let addr = PhysAddr(idx as u64 * PAGE_SIZE);
|
||||
if !self.is_locked(addr) {
|
||||
self.lock_frame(addr);
|
||||
self.last_idx = idx;
|
||||
return Some(addr);
|
||||
/// 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();
|
||||
|
||||
for pass in 0..2usize {
|
||||
let (from, to) = if pass == 0 {
|
||||
(self.last_byte, len)
|
||||
} else {
|
||||
(0, self.last_byte)
|
||||
};
|
||||
|
||||
for byte_idx in from..to {
|
||||
// Fast path: skip fully-used bytes.
|
||||
if self.bitmap[byte_idx] == 0xFF { continue; }
|
||||
|
||||
for bit in 0..8u8 {
|
||||
if self.bitmap[byte_idx] & (1 << bit) == 0 {
|
||||
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.used_pages += 1;
|
||||
self.last_byte = byte_idx;
|
||||
|
||||
return Some(PhysAddr(page_idx as u64 * PAGE_SIZE));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
None // genuinely out of memory
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_locked(&self, addr: PhysAddr) -> bool {
|
||||
let idx = (addr.0 / PAGE_SIZE) as usize;
|
||||
(self.bitmap[idx / 8] & (1 << (idx % 8))) != 0
|
||||
/// 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; }
|
||||
|
||||
let mut run_start = 0usize;
|
||||
let mut run_len = 0usize;
|
||||
|
||||
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.used_pages += count;
|
||||
self.last_byte = run_start / 8;
|
||||
return Some(PhysAddr(run_start as u64 * PAGE_SIZE));
|
||||
}
|
||||
} else {
|
||||
run_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// ── Module-level convenience functions ───────────────────────────────────────
|
||||
|
||||
pub fn alloc_frame() -> Option<PhysAddr> {
|
||||
PMM.lock().as_mut()?.alloc_frame()
|
||||
}
|
||||
|
||||
pub fn alloc_contiguous(count: usize) -> Option<PhysAddr> {
|
||||
PMM.lock().as_mut()?.alloc_contiguous(count)
|
||||
}
|
||||
|
||||
pub fn free_frame(addr: PhysAddr) {
|
||||
if let Some(pmm) = PMM.lock().as_mut() {
|
||||
pmm.free_frame(addr);
|
||||
|
||||
Reference in New Issue
Block a user