kernel: impl buddy physical allocator/pmm

This commit is contained in:
janis 2026-08-04 00:27:36 +02:00
parent 07b90af46d
commit 238e822751
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
4 changed files with 174 additions and 10 deletions

View file

@ -46,7 +46,7 @@ extern "C" fn _start() -> ! {
kernel::x86_64::paging::get_physical_addr(VirtAddr(_start as *const () as u64))
);
let pmm = kernel::memory::PhysicalMemoryAllocator::from_memory_map(
let pmm = kernel::memory::PhysicalMemoryManager::from_memory_map(
kernel::boot::BOOT_INFO
.get()
.expect("Boot info not initialized")

View file

@ -1,4 +1,10 @@
use core::{alloc::Allocator, cell::Cell, fmt::Debug, num::NonZeroUsize, ptr::NonNull};
use core::{
alloc::Allocator,
cell::Cell,
fmt::Debug,
num::{NonZero, NonZeroUsize},
ptr::NonNull,
};
use bit_field::BitField;
use rbtree::RBTree;
@ -9,6 +15,8 @@ pub static HHDM_BASE: OnceLock<u64> = OnceLock::new();
pub trait VirtAddrTranslationExt: Sized {
fn into_phy_addr(self) -> Option<PhyAddr>;
#[expect(clippy::wrong_self_convention)]
fn is_mapped(self) -> bool {
self.into_phy_addr().is_some()
}
@ -129,7 +137,118 @@ pub struct PhysicalMemoryManager {
///
/// When a range of pages is freed, we try to locate its buddy and merge
/// them into a larger chunk recursively.
buddies: [u64; 40],
buddies: [RBTree<PhysicalPageNode>; 40],
}
impl Debug for PhysicalMemoryManager {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PhysicalMemoryManager")
.field_with("buddies", |f| {
f.debug_list()
.entries(self.buddies.iter().enumerate().map(|(i, tree)| {
struct BuddyDbg<'a> {
index: usize,
tree: &'a RBTree<PhysicalPageNode>,
}
impl Debug for BuddyDbg<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#x}: {:?}", 1u64 << self.index, self.tree)
}
}
BuddyDbg { index: i, tree }
}))
.finish()
})
.finish()
}
}
impl PhysicalMemoryManager {
pub fn from_memory_map(memory_map: &[crate::boot::MemoryRegion]) -> Self {
let usable_regions = memory_map
.iter()
.filter(|region| region.region_type.is_usable());
let buddies = [(); 40].map(|_| RBTree::default());
let mut pmm = PhysicalMemoryManager { buddies };
usable_regions.for_each(|region| {
let idx = region.start / crate::x86_64::PAGE_SIZE as u64;
let count = region.length / crate::x86_64::PAGE_SIZE as u64;
pmm.free_region(idx as usize, count as usize);
});
pmm
}
fn free_region(&mut self, page_index: usize, mut count: usize) {
serial_println!(
"Freeing region: page_index = {:#x}, count = {:#x}",
page_index,
count
);
while let Some(bit) = NonZero::new(count.isolate_lowest_one()).map(NonZero::get) {
count -= bit;
let mut bin = bit.trailing_zeros() as usize;
let mut index = page_index + count;
let mut buddy: NonNull<PhysicalPageNode>;
loop {
// each bin corresponds to a power-of-two number of pages.
// each chunk of pages has a buddy that is the same size (in the same bin)
// for example, the chunk [0, 4) has a buddy [4, 8).
let buddy_index = if index & bit == 0 {
// if the chunk is aligned to the size of the chunk, its buddy
// is located at page_index + bit.
index + bit
} else {
// if the chunk is not aligned to the size of the chunk, its
// buddy is located at page_index - bit.
index - bit
};
serial_println!(
"looking for buddy: page_index = {:#x}, buddy_index = {:#x}, bin = {}",
index,
buddy_index,
bin
);
match self.buddies[bin].remove(PhysicalPageNodeKey::from_page_index(buddy_index)) {
Some(chunk) => {
serial_println!(
"found buddy: page_index = {:#x}, buddy_index = {:#x}, bin = {}",
index,
buddy_index,
bin
);
buddy = chunk;
index = PhysicalPageNode::page_index(buddy).min(index);
bin += 1;
}
None => break,
}
}
let node_ptr = PhysicalPageNode::new_from_page_index(index);
serial_println!(
"inserting node: page_index = {:#x}, size = {:#x}, bin = {}",
index,
bit,
bin
);
let None = self.buddies[bin].insert_node(node_ptr) else {
panic!(
"Attempted to free a region that is already free: page_index = {}, count = {}",
page_index, bit
)
};
}
}
}
pub struct PhysicalMemoryAllocator {
@ -699,6 +818,27 @@ impl Debug for PhysicalPageNode {
}
impl PhysicalPageNode {
fn phy(&self) -> Option<PhyAddr> {
PhyAddr::from_hhdm_virt(self)
}
fn page_index(this: NonNull<Self>) -> usize {
let phy = PhyAddr::from_hhdm_virt(VirtAddr::from(this));
phy.expect("PhysicalPageNode is not in HHDM").page_index() as usize
}
fn new_from_page_index(page_index: usize) -> NonNull<Self> {
let phy = PhyAddr::from_page_index(page_index);
let virt = phy.as_hhdm_virt();
let ptr = virt.as_mut::<Self>();
unsafe {
ptr.write(Self::new_red());
}
NonNull::new(ptr).unwrap()
}
fn new_red() -> Self {
PhysicalPageNode(Cell::new(1 << 120))
}
@ -757,6 +897,26 @@ impl PhysicalPageNode {
/// the key does not require any additional bits.
struct PhysicalPageNodeKey;
impl Debug for PhysicalPageNodeKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", self.phy())
}
}
impl PhysicalPageNodeKey {
fn from_page_index(page_index: usize) -> &'static Self {
let phy = PhyAddr::from_page_index(page_index);
let virt = phy.as_hhdm_virt();
let ptr = virt.as_ptr::<Self>();
unsafe { &*ptr }
}
fn phy(&self) -> Option<PhyAddr> {
PhyAddr::from_hhdm_virt(self)
}
}
impl Eq for PhysicalPageNodeKey {}
impl PartialEq for PhysicalPageNodeKey {

View file

@ -1,3 +1,4 @@
#![allow(dead_code)]
use core::ffi::c_void;
use crate::{
@ -336,12 +337,10 @@ impl Elf64Sym {
if let Some(bytes) = crate::limine::EXECUTABLE_FILE_REQUEST
.file()
.map(LimineFile::bytes)
&& let Ok(ehdr) = plain::from_bytes::<Elf64Ehdr>(bytes)
&& let Some(symtab) = ehdr.symtab(bytes)
{
if let Ok(ehdr) = plain::from_bytes::<Elf64Ehdr>(bytes) {
if let Some(symtab) = ehdr.symtab(bytes) {
return symtab;
}
}
return symtab;
}
&[]
});
@ -375,6 +374,7 @@ impl Elf64Sym {
_ => panic!("Invalid bind value"),
}
}
fn kind(&self) -> Elf64SymType {
match self.info & 0xf {
0 => Elf64SymType::Notype,

View file

@ -539,11 +539,15 @@ pub const PF_STACK: u8 = 3;
const STACK_SIZE: usize = super::PAGE_SIZE * 5;
#[repr(align(4096))]
pub struct Stack(MaybeUninit<[u8; STACK_SIZE]>);
pub struct Stack {
_bytes: MaybeUninit<[u8; STACK_SIZE]>,
}
impl Stack {
const fn new() -> Self {
Stack(MaybeUninit::uninit())
Stack {
_bytes: MaybeUninit::uninit(),
}
}
fn top(&self) -> *const u8 {
unsafe {