From 398ca5357ca365ad8125af898473f46c7f779143 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 18 Mar 2019 18:28:04 +0100 Subject: [PATCH] WIP --- src/bin/test-basic-boot.rs | 11 ++ src/bin/test-exception-breakpoint.rs | 11 ++ ...t-exception-double-fault-stack-overflow.rs | 11 ++ src/bin/test-panic.rs | 11 ++ src/lib.rs | 4 + src/main.rs | 43 +++++-- src/memory.rs | 38 ++++-- src/memory/allocator.rs | 49 ++++++++ src/memory/allocator/bucket.rs | 117 ++++++++++++++++++ src/memory/allocator/bump.rs | 38 ++++++ src/memory/allocator/dummy.rs | 16 +++ src/memory/allocator/linked_list.rs | 95 ++++++++++++++ 12 files changed, 428 insertions(+), 16 deletions(-) create mode 100644 src/memory/allocator.rs create mode 100644 src/memory/allocator/bucket.rs create mode 100644 src/memory/allocator/bump.rs create mode 100644 src/memory/allocator/dummy.rs create mode 100644 src/memory/allocator/linked_list.rs diff --git a/src/bin/test-basic-boot.rs b/src/bin/test-basic-boot.rs index f77d4a02..ac1c8a1f 100644 --- a/src/bin/test-basic-boot.rs +++ b/src/bin/test-basic-boot.rs @@ -1,8 +1,11 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] // disable all Rust-level entry points #![cfg_attr(test, allow(unused_imports))] +#![feature(alloc_error_handler)] +use blog_os::memory::allocator::DummyAllocator; use blog_os::{exit_qemu, serial_println}; +use core::alloc::Layout; use core::panic::PanicInfo; /// This function is the entry point, since the linker looks for a function @@ -31,3 +34,11 @@ fn panic(info: &PanicInfo) -> ! { } loop {} } + +#[global_allocator] +static ALLOCATOR: DummyAllocator = DummyAllocator; + +#[alloc_error_handler] +fn out_of_memory(layout: Layout) -> ! { + panic!("out of memory: allocation for {:?} failed", layout); +} diff --git a/src/bin/test-exception-breakpoint.rs b/src/bin/test-exception-breakpoint.rs index 0185aee6..63912001 100644 --- a/src/bin/test-exception-breakpoint.rs +++ b/src/bin/test-exception-breakpoint.rs @@ -1,8 +1,11 @@ #![no_std] #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] +#![feature(alloc_error_handler)] +use blog_os::memory::allocator::DummyAllocator; use blog_os::{exit_qemu, serial_println}; +use core::alloc::Layout; use core::panic::PanicInfo; #[cfg(not(test))] @@ -32,3 +35,11 @@ fn panic(info: &PanicInfo) -> ! { } loop {} } + +#[global_allocator] +static ALLOCATOR: DummyAllocator = DummyAllocator; + +#[alloc_error_handler] +fn out_of_memory(layout: Layout) -> ! { + panic!("out of memory: allocation for {:?} failed", layout); +} diff --git a/src/bin/test-exception-double-fault-stack-overflow.rs b/src/bin/test-exception-double-fault-stack-overflow.rs index 51d24755..3db1de50 100644 --- a/src/bin/test-exception-double-fault-stack-overflow.rs +++ b/src/bin/test-exception-double-fault-stack-overflow.rs @@ -2,8 +2,11 @@ #![no_std] #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] +#![feature(alloc_error_handler)] +use blog_os::memory::allocator::DummyAllocator; use blog_os::{exit_qemu, serial_println}; +use core::alloc::Layout; use core::panic::PanicInfo; use lazy_static::lazy_static; @@ -45,6 +48,14 @@ fn panic(info: &PanicInfo) -> ! { loop {} } +#[global_allocator] +static ALLOCATOR: DummyAllocator = DummyAllocator; + +#[alloc_error_handler] +fn out_of_memory(layout: Layout) -> ! { + panic!("out of memory: allocation for {:?} failed", layout); +} + use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; lazy_static! { diff --git a/src/bin/test-panic.rs b/src/bin/test-panic.rs index c68ac51d..1865395c 100644 --- a/src/bin/test-panic.rs +++ b/src/bin/test-panic.rs @@ -1,8 +1,11 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(unused_imports))] +#![feature(alloc_error_handler)] +use blog_os::memory::allocator::DummyAllocator; use blog_os::{exit_qemu, serial_println}; +use core::alloc::Layout; use core::panic::PanicInfo; #[cfg(not(test))] @@ -21,3 +24,11 @@ fn panic(_info: &PanicInfo) -> ! { } loop {} } + +#[global_allocator] +static ALLOCATOR: DummyAllocator = DummyAllocator; + +#[alloc_error_handler] +fn out_of_memory(layout: Layout) -> ! { + panic!("out of memory: allocation for {:?} failed", layout); +} diff --git a/src/lib.rs b/src/lib.rs index 5974e73c..1087db76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ #![cfg_attr(not(test), no_std)] #![feature(abi_x86_interrupt)] +#![feature(alloc)] +#![feature(const_fn)] + +extern crate alloc; pub mod gdt; pub mod interrupts; diff --git a/src/main.rs b/src/main.rs index d4d3269f..dbdf6459 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,18 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(unused_imports))] +#![feature(alloc)] +#![feature(alloc_error_handler)] -use blog_os::println; +extern crate alloc; + +use alloc::vec::Vec; +use blog_os::{ + memory::allocator::{BumpAllocator, LinkedListAllocator, LockedAllocator, BucketAllocator}, + println, +}; use bootloader::{entry_point, BootInfo}; +use core::alloc::Layout; use core::panic::PanicInfo; entry_point!(kernel_main); @@ -24,13 +33,21 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { let mut mapper = unsafe { memory::init(boot_info.physical_memory_offset) }; let mut frame_allocator = memory::init_frame_allocator(&boot_info.memory_map); - // map a previously unmapped page - let page = Page::containing_address(VirtAddr::new(0xdeadbeaf000)); - memory::create_example_mapping(page, &mut mapper, &mut frame_allocator); + let heap_start = VirtAddr::new(HEAP_START); + let heap_end = VirtAddr::new(HEAP_END); + memory::map_heap(heap_start, heap_end, &mut mapper, &mut frame_allocator) + .expect("map_heap failed"); - // write the string `New!` to the screen through the new mapping - let page_ptr: *mut u64 = page.start_address().as_mut_ptr(); - unsafe { page_ptr.offset(400).write_volatile(0x_f021_f077_f065_f04e) }; + ALLOCATOR.lock().underlying().add_memory(heap_start, HEAP_END - HEAP_START); + + //let mut x = Vec::with_capacity(1000); + let mut x = Vec::new(); + for i in 0..1000 { + x.push(i); + } + println!("{:?}", *ALLOCATOR.lock()); + println!("with vec of size {}: {}", x.len(), x.iter().sum::()); + println!("with formular: {}", 999 * 1000 / 2); println!("It did not crash!"); blog_os::hlt_loop(); @@ -43,3 +60,15 @@ fn panic(info: &PanicInfo) -> ! { println!("{}", info); blog_os::hlt_loop(); } + +const HEAP_START: u64 = 0o_001_000_000_0000; +const HEAP_END: u64 = HEAP_START + 10 * 0x1000; + +#[global_allocator] +static ALLOCATOR: LockedAllocator> = + LockedAllocator::new(BucketAllocator::new(LinkedListAllocator::empty())); + +#[alloc_error_handler] +fn out_of_memory(layout: Layout) -> ! { + panic!("out of memory: allocation for {:?} failed", layout); +} diff --git a/src/memory.rs b/src/memory.rs index 0589fdd6..7e6c245e 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,12 +1,14 @@ use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; use x86_64::{ structures::paging::{ - FrameAllocator, MappedPageTable, Mapper, MapperAllSizes, Page, PageTable, PhysFrame, - Size4KiB, + mapper, FrameAllocator, MappedPageTable, Mapper, MapperAllSizes, Page, PageTable, + PhysFrame, Size4KiB, }, PhysAddr, VirtAddr, }; +pub mod allocator; + /// Initialize a new MappedPageTable. /// /// This function is unsafe because the caller must guarantee that the @@ -59,19 +61,37 @@ unsafe fn active_level_4_table(physical_memory_offset: u64) -> &'static mut Page &mut *page_table_ptr // unsafe } -/// Creates an example mapping for the given page to frame `0xb8000`. -pub fn create_example_mapping( - page: Page, +pub fn map_heap( + heap_start: VirtAddr, + heap_end: VirtAddr, mapper: &mut impl Mapper, frame_allocator: &mut impl FrameAllocator, -) { +) -> Result<(), MapHeapError> { use x86_64::structures::paging::PageTableFlags as Flags; - let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); let flags = Flags::PRESENT | Flags::WRITABLE; + let start_page = Page::containing_address(heap_start); + let end_page = Page::containing_address(heap_end - 1u64); - let map_to_result = unsafe { mapper.map_to(page, frame, flags, frame_allocator) }; - map_to_result.expect("map_to failed").flush(); + for page in Page::range_inclusive(start_page, end_page) { + let frame = frame_allocator.allocate_frame(); + let frame = frame.ok_or(MapHeapError::FrameAllocationFailed)?; + unsafe { mapper.map_to(page, frame, flags, frame_allocator)?.flush() }; + } + + Ok(()) +} + +#[derive(Debug)] +pub enum MapHeapError { + FrameAllocationFailed, + MapToError(mapper::MapToError), +} + +impl From for MapHeapError { + fn from(err: mapper::MapToError) -> Self { + MapHeapError::MapToError(err) + } } /// A FrameAllocator that always returns `None`. diff --git a/src/memory/allocator.rs b/src/memory/allocator.rs new file mode 100644 index 00000000..63ba9cb7 --- /dev/null +++ b/src/memory/allocator.rs @@ -0,0 +1,49 @@ +pub use bump::BumpAllocator; +pub use dummy::DummyAllocator; +pub use linked_list::LinkedListAllocator; +pub use bucket::BucketAllocator; + +use core::alloc::{GlobalAlloc, Layout}; +use spin::{Mutex, MutexGuard}; + +mod bump; +mod dummy; +mod linked_list; +mod bucket; + +pub struct LockedAllocator { + allocator: Mutex, +} + +impl LockedAllocator { + pub const fn new(allocator: T) -> Self { + Self { + allocator: Mutex::new(allocator), + } + } +} + +impl LockedAllocator { + pub fn lock(&self) -> MutexGuard { + self.allocator.lock() + } +} + +unsafe impl GlobalAlloc for LockedAllocator +where + T: MutGlobalAlloc, +{ + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.allocator.lock().alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.allocator.lock().dealloc(ptr, layout) + } +} + +pub trait MutGlobalAlloc { + fn alloc(&mut self, layout: Layout) -> *mut u8; + + fn dealloc(&mut self, ptr: *mut u8, layout: Layout); +} diff --git a/src/memory/allocator/bucket.rs b/src/memory/allocator/bucket.rs new file mode 100644 index 00000000..f0338c9c --- /dev/null +++ b/src/memory/allocator/bucket.rs @@ -0,0 +1,117 @@ +use super::MutGlobalAlloc; +use core::alloc::Layout; +use core::mem::size_of; +use core::fmt::{self, Debug}; + +#[derive(Debug)] +pub struct BucketAllocator where A: MutGlobalAlloc + Debug { + underlying: A, + buckets: [Bucket; 10], +} + +impl BucketAllocator where A: MutGlobalAlloc + Debug { + pub const fn new(underlying: A) -> Self { + Self { + underlying, + buckets: [ + Bucket::new(size_of::()), + Bucket::new(16), + Bucket::new(32), + Bucket::new(64), + Bucket::new(128), + Bucket::new(256), + Bucket::new(512), + Bucket::new(1024), + Bucket::new(2048), + Bucket::new(4096), + ] + } + } + + pub fn underlying(&mut self) -> &mut A { + &mut self.underlying + } +} + +pub struct Bucket { + size: usize, + head: Option<&'static mut Region>, +} + +impl Bucket { + const fn new(size: usize) -> Self { + Bucket { + size, + head: None, + } + } +} + +impl fmt::Debug for Bucket { + fn fmt(&self, f: &mut fmt::Formatter)-> fmt::Result { + let mut regions = 0; + let mut current = &self.head; + while let Some(region) = current { + current = ®ion.next; + regions += 1; + } + f.debug_struct("Bucket").field("size", &self.size).field("regions", ®ions).finish() + } +} + +#[derive(Debug)] +struct Region { + next: Option<&'static mut Region>, +} + +impl Region { + fn new() -> Self { + Self { + next: None, + } + } + + fn as_mut_u8(&'static mut self) -> *mut u8 { + self as *mut Region as *mut u8 + } + + unsafe fn from_mut_u8(ptr: *mut u8) -> &'static mut Self { + (ptr as *mut Region).write(Region::new()); + &mut *(ptr as *mut Region) + } +} + +impl BucketAllocator where A: MutGlobalAlloc + Debug { + fn get_bucket_index(&self, size: usize) -> Option { + match self.buckets.binary_search_by(|bucket| bucket.size.cmp(&size)) { + Ok(index) => Some(index), + Err(index) if index < self.buckets.len() => Some(index), + Err(_) => None, + } + } +} + +impl MutGlobalAlloc for BucketAllocator where A: MutGlobalAlloc + Debug { + fn alloc(&mut self, layout: Layout) -> *mut u8 { + if let Some(bucket_index) = self.get_bucket_index(layout.size()) { + let bucket = &mut self.buckets[bucket_index]; + if let Some(head) = bucket.head.take() { + let next = head.next.take(); + bucket.head = next; + return head.as_mut_u8(); + } + } + self.underlying.alloc(layout) + } + + fn dealloc(&mut self, ptr: *mut u8, layout: Layout) { + if let Some(bucket_index) = self.get_bucket_index(layout.size()) { + let bucket = &mut self.buckets[bucket_index]; + let region = unsafe {Region::from_mut_u8(ptr)}; + region.next = bucket.head.take(); + bucket.head = Some(region); + } else { + self.underlying.dealloc(ptr, layout); + } + } +} \ No newline at end of file diff --git a/src/memory/allocator/bump.rs b/src/memory/allocator/bump.rs new file mode 100644 index 00000000..cb5fe750 --- /dev/null +++ b/src/memory/allocator/bump.rs @@ -0,0 +1,38 @@ +use super::MutGlobalAlloc; +use core::alloc::Layout; +use x86_64::align_up; + +pub struct BumpAllocator { + heap_start: u64, + heap_end: u64, + next: u64, +} + +impl BumpAllocator { + pub const fn new(heap_start: u64, heap_end: u64) -> Self { + Self { + heap_start, + heap_end, + next: heap_start, + } + } +} + +impl MutGlobalAlloc for BumpAllocator { + fn alloc(&mut self, layout: Layout) -> *mut u8 { + let alloc_start = align_up(self.next, layout.align() as u64); + let alloc_end = alloc_start.saturating_add(layout.size() as u64); + + if alloc_end >= self.heap_end { + // out of memory + return 0 as *mut u8; + } + + self.next = alloc_end; + alloc_start as *mut u8 + } + + fn dealloc(&mut self, _ptr: *mut u8, _layout: Layout) { + panic!("BumpAllocator::dealloc called"); + } +} diff --git a/src/memory/allocator/dummy.rs b/src/memory/allocator/dummy.rs new file mode 100644 index 00000000..e12b5ddc --- /dev/null +++ b/src/memory/allocator/dummy.rs @@ -0,0 +1,16 @@ +use core::alloc::{GlobalAlloc, Layout}; + +/// A dummy allocator that panics on every `alloc` or `dealloc` call. +pub struct DummyAllocator; + +unsafe impl GlobalAlloc for DummyAllocator { + /// Always panics. + unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { + panic!("DummyAllocator::alloc called"); + } + + /// Always panics. + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + panic!("DummyAllocator::dealloc called"); + } +} diff --git a/src/memory/allocator/linked_list.rs b/src/memory/allocator/linked_list.rs new file mode 100644 index 00000000..46d7fb32 --- /dev/null +++ b/src/memory/allocator/linked_list.rs @@ -0,0 +1,95 @@ +use super::MutGlobalAlloc; +use core::alloc::Layout; +use core::mem; +use x86_64::{align_up, VirtAddr}; + +#[derive(Debug)] +pub struct LinkedListAllocator { + head: Region, +} + +impl LinkedListAllocator { + pub const fn empty() -> Self { + let head = Region { + size: 0, + next: None, + }; + Self { head } + } + + pub unsafe fn new(heap_start: VirtAddr, heap_size: u64) -> Self { + let mut allocator = Self::empty(); + allocator.add_memory(heap_start, heap_size); + allocator + } + + pub fn add_memory(&mut self, start: VirtAddr, size: u64) { + let aligned = start.align_up(mem::size_of::() as u64); + let mut region = Region { + size: size - (aligned - start), + next: None + }; + mem::swap(&mut self.head.next, &mut region.next); + + let region_ptr: *mut Region = aligned.as_mut_ptr(); + unsafe { region_ptr.write(region) }; + self.head.next = Some(unsafe { &mut *region_ptr }); + } +} + +impl MutGlobalAlloc for LinkedListAllocator { + fn alloc(&mut self, layout: Layout) -> *mut u8 { + let size = align_up(layout.size() as u64, mem::size_of::() as u64); + + let mut current = &mut self.head; + loop { + let next = match current.next { + Some(ref mut next) => next, + None => break, + }; + let next_start = VirtAddr::new(*next as *mut Region as u64); + let next_end = next_start + next.size; + + let alloc_start = next_start.align_up(layout.align() as u64); + let alloc_end = alloc_start + size; + + // check if Region large enough + if alloc_end <= next_end { + // remove Region from list + let next_next = next.next.take(); + current.next = next_next; + // insert remaining Region to list + self.add_memory(alloc_end, next_end - alloc_end); + // return allocated memory + return alloc_start.as_mut_ptr(); + } + + // continue with next element + // + // This is basically `current = next`, but we need a new `match` expression because + // the compiler can't figure the lifetimes out when we use the `next` binding + // from above. + current = match current.next { + Some(ref mut next) => next, + None => unreachable!(), + }; + } + + // no large enough Region found + 0 as *mut u8 + } + + fn dealloc(&mut self, ptr: *mut u8, layout: Layout) { + let size = align_up(layout.size() as u64, mem::size_of::() as u64); + self.add_memory(VirtAddr::new(ptr as u64), size); + } +} + +#[derive(Debug)] +struct Region { + size: u64, + next: Option<&'static mut Region>, +} + + +// TODO recycle alignment \ No newline at end of file