ostd/mm/heap/mod.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! Manages the kernel heap using slab or buddy allocation strategies.
4
5use core::{
6 alloc::{AllocError, GlobalAlloc, Layout},
7 ptr::NonNull,
8};
9
10use crate::mm::Vaddr;
11
12mod slab;
13mod slot;
14mod slot_list;
15
16pub use self::{
17 slab::{Slab, SlabMeta},
18 slot::{HeapSlot, SlotInfo},
19 slot_list::SlabSlotList,
20};
21
22/// The trait for the global heap allocator.
23///
24/// By providing the slab ([`Slab`]) and heap slot ([`HeapSlot`])
25/// mechanisms, OSTD allows users to implement their own kernel heap in a safe
26/// manner, as an alternative to the unsafe [`core::alloc::GlobalAlloc`].
27///
28/// To provide the global heap allocator, use [`crate::global_heap_allocator`]
29/// to mark a static variable that implements this trait. Use
30/// [`crate::global_heap_allocator_slot_map`] to specify the sizes of
31/// slots for different layouts. This latter restriction may be lifted in the
32/// future.
33pub trait GlobalHeapAllocator: Sync {
34 /// Allocates a [`HeapSlot`] according to the layout.
35 ///
36 /// OSTD calls this method to allocate memory from the global heap.
37 ///
38 /// The returned [`HeapSlot`] must be valid for the layout, i.e., the size
39 /// must be at least the size of the layout and the alignment must be at
40 /// least the alignment of the layout. Furthermore, the size of the
41 /// returned [`HeapSlot`] must match the size returned by the function
42 /// marked with [`crate::global_heap_allocator_slot_map`].
43 fn alloc(&self, layout: Layout) -> Result<HeapSlot, AllocError>;
44
45 /// Deallocates a [`HeapSlot`].
46 ///
47 /// OSTD calls this method to deallocate memory back to the global heap.
48 ///
49 /// Each deallocation must correspond to exactly one previous allocation. The provided
50 /// [`HeapSlot`] must match the one returned from the original allocation.
51 fn dealloc(&self, slot: HeapSlot) -> Result<(), AllocError>;
52}
53
54unsafe extern "Rust" {
55 /// The reference to the global heap allocator generated by the
56 /// [`crate::global_heap_allocator`] attribute.
57 static __GLOBAL_HEAP_ALLOCATOR_REF: &'static dyn GlobalHeapAllocator;
58
59 /// Gets the size and type of heap slots to serve allocations of the layout.
60 /// See [`crate::global_heap_allocator_slot_map`].
61 fn __GLOBAL_HEAP_SLOT_INFO_FROM_LAYOUT(layout: Layout) -> Option<SlotInfo>;
62}
63
64/// Gets the reference to the user-defined global heap allocator.
65fn get_global_heap_allocator() -> &'static dyn GlobalHeapAllocator {
66 // SAFETY: This up-call is redirected safely to Rust code by OSDK.
67 unsafe { __GLOBAL_HEAP_ALLOCATOR_REF }
68}
69
70/// Gets the size and type of heap slots to serve allocations of the layout.
71///
72/// This function is defined by the OSTD user and should be idempotent, as we
73/// require it to be implemented as a `const fn`.
74///
75/// See [`crate::global_heap_allocator_slot_map`].
76fn slot_size_from_layout(layout: Layout) -> Option<SlotInfo> {
77 // SAFETY: This up-call is redirected safely to Rust code by OSDK.
78 unsafe { __GLOBAL_HEAP_SLOT_INFO_FROM_LAYOUT(layout) }
79}
80
81macro_rules! abort_with_message {
82 ($($arg:tt)*) => {
83 log::error!($($arg)*);
84 crate::panic::abort();
85 };
86}
87
88#[alloc_error_handler]
89fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
90 abort_with_message!("Heap allocation error, layout = {:#x?}", layout);
91}
92
93#[global_allocator]
94static HEAP_ALLOCATOR: AllocDispatch = AllocDispatch;
95
96struct AllocDispatch;
97
98// TODO: Somehow restrict unwinding in the user-provided global allocator.
99// Panicking should be fine, but we shouldn't unwind on panics.
100unsafe impl GlobalAlloc for AllocDispatch {
101 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
102 let Some(required_slot) = slot_size_from_layout(layout) else {
103 abort_with_message!("Heap allocation size not found for layout = {:#x?}", layout);
104 };
105
106 let res = get_global_heap_allocator().alloc(layout);
107 let Ok(slot) = res else {
108 return core::ptr::null_mut();
109 };
110
111 if required_slot.size() != slot.size()
112 || slot.size() < layout.size()
113 || !(slot.as_ptr() as Vaddr).is_multiple_of(layout.align())
114 {
115 abort_with_message!(
116 "Heap allocation mismatch: slot ptr = {:p}, size = {:x}; layout = {:#x?}; required_slot = {:#x?}",
117 slot.as_ptr(),
118 slot.size(),
119 layout,
120 required_slot,
121 );
122 }
123
124 slot.as_ptr()
125 }
126
127 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
128 // Now we restore the `HeapSlot` from the pointer and the layout.
129 let Some(required_slot) = slot_size_from_layout(layout) else {
130 abort_with_message!(
131 "Heap deallocation size not found for layout = {:#x?}",
132 layout
133 );
134 };
135
136 // SAFETY: The validity of the pointer is guaranteed by the caller. The
137 // size must match the size of the slot when it was allocated, since we
138 // require `slot_size_from_layout` to be idempotent.
139 let slot = unsafe { HeapSlot::new(NonNull::new_unchecked(ptr), required_slot) };
140 let res = get_global_heap_allocator().dealloc(slot);
141
142 if res.is_err() {
143 abort_with_message!(
144 "Heap deallocation error, ptr = {:p}, layout = {:#x?}, required_slot = {:#x?}",
145 ptr,
146 layout,
147 required_slot,
148 );
149 }
150 }
151}