//! The kernel heap. //! //! 1. Pick a virtual address range for the heap (HEAP_START..+HEAP_SIZE). //! 2. Map every page in it to a fresh physical frame (`init_heap`). //! 3. Hand the range to an allocator and register it with //! `#[global_allocator]`, so `Box`, `Vec`, `Rc`, `String`... just work. //! //! Three allocator designs, from the "Allocator Designs" chapter: //! * `bump::BumpAllocator` -- simplest, can only free everything at once //! * `linked_list_allocator` -- crate: free-list, first fit, reuses memory //! * `fixed_size_block::FixedSizeBlockAllocator` (ACTIVE) -- fast lists of //! fixed-size blocks, falls back to the linked-list allocator for big ones //! //! To try another one, swap the `ALLOCATOR` line below (examples in comments). use alloc::alloc::{GlobalAlloc, Layout}; use core::ptr::null_mut; use x86_64::{ VirtAddr, structures::paging::{ FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB, mapper::MapToError, }, }; pub mod bump; pub mod fixed_size_block; /// Where the heap lives in *virtual* memory. Any unused, easy-to-recognise /// address works; this one stands out in a debugger. pub const HEAP_START: usize = 0x_4444_4444_0000; pub const HEAP_SIZE: usize = 4 * 1024 * 1024; // 4 MiB (thread stacks live here too) #[global_allocator] static ALLOCATOR: Locked = Locked::new(fixed_size_block::FixedSizeBlockAllocator::new()); // Alternatives: // static ALLOCATOR: Locked = Locked::new(bump::BumpAllocator::new()); // static ALLOCATOR: linked_list_allocator::LockedHeap = linked_list_allocator::LockedHeap::empty(); // (then in init_heap: `ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE)` works for all three) /// Map the heap's pages to physical frames, then initialise the allocator. pub fn init_heap( mapper: &mut impl Mapper, frame_allocator: &mut impl FrameAllocator, ) -> Result<(), MapToError> { let page_range = { let heap_start = VirtAddr::new(HEAP_START as u64); let heap_end = heap_start + HEAP_SIZE as u64 - 1u64; Page::range_inclusive( Page::containing_address(heap_start), Page::containing_address(heap_end), ) }; for page in page_range { let frame = frame_allocator .allocate_frame() .ok_or(MapToError::FrameAllocationFailed)?; let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; unsafe { mapper.map_to(page, frame, flags, frame_allocator)?.flush() }; } unsafe { ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE); } Ok(()) } /// A wrapper around `spin::Mutex`. `GlobalAlloc` methods take `&self`, so the /// allocator needs interior mutability; and Rust only lets us implement a /// foreign trait (`GlobalAlloc`) for a type defined in our own crate. pub struct Locked { inner: spin::Mutex, } impl Locked { pub const fn new(inner: A) -> Self { Locked { inner: spin::Mutex::new(inner) } } pub fn lock(&self) -> spin::MutexGuard<'_, A> { self.inner.lock() } } /// Round `addr` up to a multiple of `align` (which must be a power of two). /// Example: align_up(13, 8) == 16. fn align_up(addr: usize, align: usize) -> usize { (addr + align - 1) & !(align - 1) } /// The "allocator" from the start of the Heap Allocation chapter: always /// fails. Kept to show the smallest possible `GlobalAlloc`. pub struct Dummy; unsafe impl GlobalAlloc for Dummy { unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { null_mut() // null = "out of memory" } unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { panic!("dealloc should never be called") } }