Skip to main content

obkrnl/malloc/
mod.rs

1use self::vm::VmHeap;
2use crate::context::current_thread;
3use crate::lock::Mutex;
4use crate::vm::Vm;
5use alloc::boxed::Box;
6use core::alloc::{GlobalAlloc, Layout};
7use core::cell::{RefCell, UnsafeCell};
8use core::hint::unreachable_unchecked;
9use core::ptr::{NonNull, null_mut};
10use talc::{ClaimOnOom, Span, Talc};
11
12mod vm;
13
14/// Implementation of [`GlobalAlloc`] for objects belong to kernel space.
15///
16/// This allocator has 2 stages. The first stage will allocate a memory from a static buffer (AKA
17/// arena). This stage will be primary used for bootstrapping the kernel. The second stage will be
18/// activated once the required subsystems has been initialized.
19///
20/// The first stage is **not** thread safe so stage 2 must be activated before start a new CPU.
21pub struct KernelHeap {
22    stage: UnsafeCell<Stage>,
23    primitive_ptr: *const u8,
24    primitive_end: *const u8,
25}
26
27impl KernelHeap {
28    /// # Safety
29    /// The specified memory must be valid for reads and writes and it must be exclusively available
30    /// to [`KernelHeap`].
31    pub const unsafe fn new<const L: usize>(primitive: *mut [u8; L]) -> Self {
32        let primitive_ptr = primitive.cast();
33
34        // SAFETY: The safety requirement of our function satify the safety requirement of
35        // ClaimOnOom::new().
36        let primitive = unsafe { Talc::new(ClaimOnOom::new(Span::from_array(primitive))) };
37
38        Self {
39            stage: UnsafeCell::new(Stage::One(RefCell::new(primitive))),
40            primitive_ptr,
41            // SAFETY: L is a length of primitive_ptr so the resulting pointer is valid.
42            primitive_end: unsafe { primitive_ptr.add(L) },
43        }
44    }
45
46    /// # Safety
47    /// This must be called by main CPU and can be called only once.
48    pub unsafe fn activate_stage2(&self, vm: &'static Vm) {
49        // Setup VM  heap using primitive heap.
50        let vm = Box::new(VmHeap::new(vm));
51
52        // What we are doing here is highly unsafe. Do not edit the code after this unless you know
53        // what you are doing!
54        let stage = self.stage.get();
55        let primitive = match unsafe { stage.read() } {
56            Stage::One(v) => Mutex::new(v.into_inner()),
57            // SAFETY: The safety requirement of our function make this unreachable.
58            Stage::Two(_, _) => unsafe { unreachable_unchecked() },
59        };
60
61        // Switch to stage 2 WITHOUT dropping the value contained in Stage::One. What we did here is
62        // moving the value from Stage::One to Stage::Two.
63        unsafe { stage.write(Stage::Two(vm, primitive)) };
64    }
65}
66
67unsafe impl GlobalAlloc for KernelHeap {
68    #[inline(never)]
69    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
70        // If stage 2 has not activated yet then this function is not allowed to access the CPU
71        // context due to it can be called before the context has been activated.
72
73        // SAFETY: GlobalAlloc::alloc required layout to be non-zero.
74        match unsafe { &*self.stage.get() } {
75            Stage::One(primitive) => unsafe {
76                primitive
77                    .borrow_mut()
78                    .malloc(layout)
79                    .map_or(null_mut(), |v| v.as_ptr())
80            },
81            Stage::Two(vm, primitive) => match current_thread().active_heap_guard() {
82                0 => unsafe { vm.alloc(layout) },
83                _ => unsafe {
84                    primitive
85                        .lock()
86                        .malloc(layout)
87                        .map_or(null_mut(), |v| v.as_ptr())
88                },
89            },
90        }
91    }
92
93    #[inline(never)]
94    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
95        // If stage 2 has not activated yet then this function is not allowed to access the CPU
96        // context due to it can be called before the context has been activated.
97
98        // SAFETY: GlobalAlloc::dealloc required ptr to be the same one that returned
99        // from our GlobalAlloc::alloc and layout to be the same one that passed to it.
100        match unsafe { &*self.stage.get() } {
101            Stage::One(primitive) => unsafe {
102                primitive
103                    .borrow_mut()
104                    .free(NonNull::new_unchecked(ptr), layout)
105            },
106            Stage::Two(vm, primitive) => {
107                if ptr.cast_const() >= self.primitive_ptr && ptr.cast_const() < self.primitive_end {
108                    unsafe { primitive.lock().free(NonNull::new_unchecked(ptr), layout) }
109                } else {
110                    // SAFETY: ptr is not owned by primitive heap so with the requirements of
111                    // GlobalAlloc::dealloc the ptr will be owned by VM heap for sure.
112                    unsafe { vm.dealloc(ptr, layout) };
113                }
114            }
115        }
116    }
117}
118
119// We impose restriction on the user to activate stage 2 before going multi-threaded.
120unsafe impl Send for KernelHeap {}
121unsafe impl Sync for KernelHeap {}
122
123/// Stage of [KernelHeap].
124enum Stage {
125    One(RefCell<Talc<ClaimOnOom>>),
126    Two(Box<VmHeap>, Mutex<Talc<ClaimOnOom>>),
127}