diff --git a/crates/userspace/src/allocator/mod.rs b/crates/userspace/src/allocator/mod.rs new file mode 100644 index 0000000..2e1c0fa --- /dev/null +++ b/crates/userspace/src/allocator/mod.rs @@ -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, +} + +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::() { + 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::()); + 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::()); + inner.add_to_free_list(ptr, size); + } +} diff --git a/crates/userspace/src/allocator/tests.rs b/crates/userspace/src/allocator/tests.rs new file mode 100644 index 0000000..05741b9 --- /dev/null +++ b/crates/userspace/src/allocator/tests.rs @@ -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()); +} diff --git a/crates/userspace/src/lib.rs b/crates/userspace/src/lib.rs index 90edb3e..3fee153 100644 --- a/crates/userspace/src/lib.rs +++ b/crates/userspace/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +pub mod allocator; mod arch; pub mod syscall;