feat(userspace): add KosAllocator — free-list allocator backed by RequestPages syscall
This commit is contained in:
140
crates/userspace/src/allocator/mod.rs
Normal file
140
crates/userspace/src/allocator/mod.rs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
use core::alloc::{GlobalAlloc, Layout};
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
const PAGE_SIZE: usize = 4096;
|
||||||
|
const MAX_PAGES: usize = 64;
|
||||||
|
|
||||||
|
struct FreeBlock {
|
||||||
|
size: usize,
|
||||||
|
next: *mut FreeBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KosAllocator {
|
||||||
|
inner: UnsafeCell<AllocatorInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AllocatorInner {
|
||||||
|
free_list: *mut FreeBlock,
|
||||||
|
pages: [usize; MAX_PAGES],
|
||||||
|
page_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for KosAllocator {}
|
||||||
|
|
||||||
|
impl KosAllocator {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: UnsafeCell::new(AllocatorInner {
|
||||||
|
free_list: ptr::null_mut(),
|
||||||
|
pages: [0; MAX_PAGES],
|
||||||
|
page_count: 0,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_mut(&self) -> &mut AllocatorInner {
|
||||||
|
unsafe { &mut *self.inner.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AllocatorInner {
|
||||||
|
fn request_page(&mut self) -> *mut u8 {
|
||||||
|
if self.page_count >= MAX_PAGES {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let page_addr = crate::syscall::request_pages(1);
|
||||||
|
if page_addr == usize::MAX {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pages[self.page_count] = page_addr;
|
||||||
|
self.page_count += 1;
|
||||||
|
page_addr as *mut u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_from_free_list(&mut self, size: usize, align: usize) -> *mut u8 {
|
||||||
|
let mut prev: *mut FreeBlock = ptr::null_mut();
|
||||||
|
let mut current = self.free_list;
|
||||||
|
|
||||||
|
while !current.is_null() {
|
||||||
|
let block = unsafe { &mut *current };
|
||||||
|
let addr = current as usize;
|
||||||
|
let aligned = (addr + align - 1) & !(align - 1);
|
||||||
|
let padding = aligned - addr;
|
||||||
|
|
||||||
|
if block.size >= size + padding {
|
||||||
|
let remaining = block.size - size - padding;
|
||||||
|
|
||||||
|
if remaining >= core::mem::size_of::<FreeBlock>() {
|
||||||
|
let new_block = (aligned + size) as *mut FreeBlock;
|
||||||
|
unsafe {
|
||||||
|
(*new_block).size = remaining;
|
||||||
|
(*new_block).next = block.next;
|
||||||
|
}
|
||||||
|
if prev.is_null() {
|
||||||
|
self.free_list = new_block;
|
||||||
|
} else {
|
||||||
|
unsafe { (*prev).next = new_block; }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if prev.is_null() {
|
||||||
|
self.free_list = block.next;
|
||||||
|
} else {
|
||||||
|
unsafe { (*prev).next = block.next; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aligned as *mut u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = current;
|
||||||
|
current = block.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_to_free_list(&mut self, ptr: *mut u8, size: usize) {
|
||||||
|
let block = ptr as *mut FreeBlock;
|
||||||
|
unsafe {
|
||||||
|
(*block).size = size;
|
||||||
|
(*block).next = self.free_list;
|
||||||
|
}
|
||||||
|
self.free_list = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl GlobalAlloc for KosAllocator {
|
||||||
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
|
let inner = self.inner_mut();
|
||||||
|
let size = layout.size().max(core::mem::size_of::<FreeBlock>());
|
||||||
|
let align = layout.align();
|
||||||
|
|
||||||
|
let ptr = inner.alloc_from_free_list(size, align);
|
||||||
|
if !ptr.is_null() {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pages_needed = (size + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||||
|
for _ in 0..pages_needed {
|
||||||
|
let page = inner.request_page();
|
||||||
|
if page.is_null() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
inner.add_to_free_list(page, PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.alloc_from_free_list(size, align)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||||
|
let inner = self.inner_mut();
|
||||||
|
let size = layout.size().max(core::mem::size_of::<FreeBlock>());
|
||||||
|
inner.add_to_free_list(ptr, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
crates/userspace/src/allocator/tests.rs
Normal file
81
crates/userspace/src/allocator/tests.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
use super::*;
|
||||||
|
use core::alloc::Layout;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn allocator_creates_successfully() {
|
||||||
|
let _alloc = KosAllocator::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn free_list_operations() {
|
||||||
|
let alloc = KosAllocator::new();
|
||||||
|
let inner = alloc.inner_mut();
|
||||||
|
|
||||||
|
// Simulate a page by using a stack buffer
|
||||||
|
let mut page = [0u8; 4096];
|
||||||
|
let page_ptr = page.as_mut_ptr();
|
||||||
|
|
||||||
|
inner.add_to_free_list(page_ptr, 4096);
|
||||||
|
assert!(!inner.free_list.is_null());
|
||||||
|
|
||||||
|
let ptr = inner.alloc_from_free_list(64, 8);
|
||||||
|
assert!(!ptr.is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alloc_dealloc_roundtrip() {
|
||||||
|
let alloc = KosAllocator::new();
|
||||||
|
let inner = alloc.inner_mut();
|
||||||
|
|
||||||
|
let mut page = [0u8; 4096];
|
||||||
|
inner.add_to_free_list(page.as_mut_ptr(), 4096);
|
||||||
|
|
||||||
|
let layout = Layout::from_size_align(128, 8).unwrap();
|
||||||
|
let ptr = inner.alloc_from_free_list(128, 8);
|
||||||
|
assert!(!ptr.is_null());
|
||||||
|
|
||||||
|
inner.add_to_free_list(ptr, 128);
|
||||||
|
let ptr2 = inner.alloc_from_free_list(64, 8);
|
||||||
|
assert!(!ptr2.is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_allocations_from_one_page() {
|
||||||
|
let alloc = KosAllocator::new();
|
||||||
|
let inner = alloc.inner_mut();
|
||||||
|
|
||||||
|
let mut page = [0u8; 4096];
|
||||||
|
inner.add_to_free_list(page.as_mut_ptr(), 4096);
|
||||||
|
|
||||||
|
let p1 = inner.alloc_from_free_list(256, 8);
|
||||||
|
let p2 = inner.alloc_from_free_list(256, 8);
|
||||||
|
let p3 = inner.alloc_from_free_list(256, 8);
|
||||||
|
|
||||||
|
assert!(!p1.is_null());
|
||||||
|
assert!(!p2.is_null());
|
||||||
|
assert!(!p3.is_null());
|
||||||
|
assert_ne!(p1, p2);
|
||||||
|
assert_ne!(p2, p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alignment_respected() {
|
||||||
|
let alloc = KosAllocator::new();
|
||||||
|
let inner = alloc.inner_mut();
|
||||||
|
|
||||||
|
let mut page = [0u8; 4096];
|
||||||
|
inner.add_to_free_list(page.as_mut_ptr(), 4096);
|
||||||
|
|
||||||
|
let ptr = inner.alloc_from_free_list(64, 64);
|
||||||
|
assert!(!ptr.is_null());
|
||||||
|
assert_eq!(ptr as usize % 64, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exhausted_free_list_returns_null() {
|
||||||
|
let alloc = KosAllocator::new();
|
||||||
|
let inner = alloc.inner_mut();
|
||||||
|
|
||||||
|
let ptr = inner.alloc_from_free_list(64, 8);
|
||||||
|
assert!(ptr.is_null());
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
pub mod allocator;
|
||||||
mod arch;
|
mod arch;
|
||||||
pub mod syscall;
|
pub mod syscall;
|
||||||
|
|||||||
Reference in New Issue
Block a user