axalloc/lib.rs
1//! [ArceOS](https://github.com/arceos-org/arceos) global memory allocator.
2//!
3//! It provides [`GlobalAllocator`], which implements the trait
4//! [`core::alloc::GlobalAlloc`]. A static global variable of type
5//! [`GlobalAllocator`] is defined with the `#[global_allocator]` attribute, to
6//! be registered as the standard library’s default allocator.
7
8#![no_std]
9
10#[macro_use]
11extern crate log;
12extern crate alloc;
13
14mod page;
15
16use allocator::{AllocResult, BaseAllocator, BitmapPageAllocator, ByteAllocator, PageAllocator};
17use core::alloc::{GlobalAlloc, Layout};
18use core::ptr::NonNull;
19use kspin::SpinNoIrq;
20
21const PAGE_SIZE: usize = 0x1000;
22const MIN_HEAP_SIZE: usize = 0x8000; // 32 K
23
24pub use page::GlobalPage;
25
26cfg_if::cfg_if! {
27 if #[cfg(feature = "slab")] {
28 /// The default byte allocator.
29 pub type DefaultByteAllocator = allocator::SlabByteAllocator;
30 } else if #[cfg(feature = "buddy")] {
31 /// The default byte allocator.
32 pub type DefaultByteAllocator = allocator::BuddyByteAllocator;
33 } else if #[cfg(feature = "tlsf")] {
34 /// The default byte allocator.
35 pub type DefaultByteAllocator = allocator::TlsfByteAllocator;
36 }
37}
38
39/// The global allocator used by ArceOS.
40///
41/// It combines a [`ByteAllocator`] and a [`PageAllocator`] into a simple
42/// two-level allocator: firstly tries allocate from the byte allocator, if
43/// there is no memory, asks the page allocator for more memory and adds it to
44/// the byte allocator.
45///
46/// Currently, [`TlsfByteAllocator`] is used as the byte allocator, while
47/// [`BitmapPageAllocator`] is used as the page allocator.
48///
49/// [`TlsfByteAllocator`]: allocator::TlsfByteAllocator
50pub struct GlobalAllocator {
51 balloc: SpinNoIrq<DefaultByteAllocator>,
52 palloc: SpinNoIrq<BitmapPageAllocator<PAGE_SIZE>>,
53}
54
55impl GlobalAllocator {
56 /// Creates an empty [`GlobalAllocator`].
57 pub const fn new() -> Self {
58 Self {
59 balloc: SpinNoIrq::new(DefaultByteAllocator::new()),
60 palloc: SpinNoIrq::new(BitmapPageAllocator::new()),
61 }
62 }
63
64 /// Returns the name of the allocator.
65 pub const fn name(&self) -> &'static str {
66 cfg_if::cfg_if! {
67 if #[cfg(feature = "slab")] {
68 "slab"
69 } else if #[cfg(feature = "buddy")] {
70 "buddy"
71 } else if #[cfg(feature = "tlsf")] {
72 "TLSF"
73 }
74 }
75 }
76
77 /// Initializes the allocator with the given region.
78 ///
79 /// It firstly adds the whole region to the page allocator, then allocates
80 /// a small region (32 KB) to initialize the byte allocator. Therefore,
81 /// the given region must be larger than 32 KB.
82 pub fn init(&self, start_vaddr: usize, size: usize) {
83 assert!(size > MIN_HEAP_SIZE);
84 let init_heap_size = MIN_HEAP_SIZE;
85 self.palloc.lock().init(start_vaddr, size);
86 let heap_ptr = self
87 .alloc_pages(init_heap_size / PAGE_SIZE, PAGE_SIZE)
88 .unwrap();
89 self.balloc.lock().init(heap_ptr, init_heap_size);
90 }
91
92 /// Add the given region to the allocator.
93 ///
94 /// It will add the whole region to the byte allocator.
95 pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult {
96 self.balloc.lock().add_memory(start_vaddr, size)
97 }
98
99 /// Allocate arbitrary number of bytes. Returns the left bound of the
100 /// allocated region.
101 ///
102 /// It firstly tries to allocate from the byte allocator. If there is no
103 /// memory, it asks the page allocator for more memory and adds it to the
104 /// byte allocator.
105 pub fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>> {
106 // simple two-level allocator: if no heap memory, allocate from the page allocator.
107 let mut balloc = self.balloc.lock();
108 loop {
109 if let Ok(ptr) = balloc.alloc(layout) {
110 return Ok(ptr);
111 } else {
112 let old_size = balloc.total_bytes();
113 let expand_size = old_size
114 .max(layout.size())
115 .next_power_of_two()
116 .max(PAGE_SIZE);
117 let heap_ptr = self.alloc_pages(expand_size / PAGE_SIZE, PAGE_SIZE)?;
118 debug!(
119 "expand heap memory: [{:#x}, {:#x})",
120 heap_ptr,
121 heap_ptr + expand_size
122 );
123 balloc.add_memory(heap_ptr, expand_size)?;
124 }
125 }
126 }
127
128 /// Gives back the allocated region to the byte allocator.
129 ///
130 /// The region should be allocated by [`alloc`], and `align_pow2` should be
131 /// the same as the one used in [`alloc`]. Otherwise, the behavior is
132 /// undefined.
133 ///
134 /// [`alloc`]: GlobalAllocator::alloc
135 pub fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
136 self.balloc.lock().dealloc(pos, layout)
137 }
138
139 /// Allocates contiguous pages.
140 ///
141 /// It allocates `num_pages` pages from the page allocator.
142 ///
143 /// `align_pow2` must be a power of 2, and the returned region bound will be
144 /// aligned to it.
145 pub fn alloc_pages(&self, num_pages: usize, align_pow2: usize) -> AllocResult<usize> {
146 self.palloc.lock().alloc_pages(num_pages, align_pow2)
147 }
148
149 /// Allocates contiguous pages starting from the given address.
150 ///
151 /// It allocates `num_pages` pages from the page allocator starting from the
152 /// given address.
153 ///
154 /// `align_pow2` must be a power of 2, and the returned region bound will be
155 /// aligned to it.
156 pub fn alloc_pages_at(
157 &self,
158 start: usize,
159 num_pages: usize,
160 align_pow2: usize,
161 ) -> AllocResult<usize> {
162 self.palloc
163 .lock()
164 .alloc_pages_at(start, num_pages, align_pow2)
165 }
166
167 /// Gives back the allocated pages starts from `pos` to the page allocator.
168 ///
169 /// The pages should be allocated by [`alloc_pages`], and `align_pow2`
170 /// should be the same as the one used in [`alloc_pages`]. Otherwise, the
171 /// behavior is undefined.
172 ///
173 /// [`alloc_pages`]: GlobalAllocator::alloc_pages
174 pub fn dealloc_pages(&self, pos: usize, num_pages: usize) {
175 self.palloc.lock().dealloc_pages(pos, num_pages)
176 }
177
178 /// Returns the number of allocated bytes in the byte allocator.
179 pub fn used_bytes(&self) -> usize {
180 self.balloc.lock().used_bytes()
181 }
182
183 /// Returns the number of available bytes in the byte allocator.
184 pub fn available_bytes(&self) -> usize {
185 self.balloc.lock().available_bytes()
186 }
187
188 /// Returns the number of allocated pages in the page allocator.
189 pub fn used_pages(&self) -> usize {
190 self.palloc.lock().used_pages()
191 }
192
193 /// Returns the number of available pages in the page allocator.
194 pub fn available_pages(&self) -> usize {
195 self.palloc.lock().available_pages()
196 }
197}
198
199unsafe impl GlobalAlloc for GlobalAllocator {
200 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
201 if let Ok(ptr) = GlobalAllocator::alloc(self, layout) {
202 ptr.as_ptr()
203 } else {
204 alloc::alloc::handle_alloc_error(layout)
205 }
206 }
207
208 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
209 GlobalAllocator::dealloc(self, NonNull::new(ptr).expect("dealloc null ptr"), layout)
210 }
211}
212
213#[cfg_attr(all(target_os = "none", not(test)), global_allocator)]
214static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new();
215
216/// Returns the reference to the global allocator.
217pub fn global_allocator() -> &'static GlobalAllocator {
218 &GLOBAL_ALLOCATOR
219}
220
221/// Initializes the global allocator with the given memory region.
222///
223/// Note that the memory region bounds are just numbers, and the allocator
224/// does not actually access the region. Users should ensure that the region
225/// is valid and not being used by others, so that the allocated memory is also
226/// valid.
227///
228/// This function should be called only once, and before any allocation.
229pub fn global_init(start_vaddr: usize, size: usize) {
230 debug!(
231 "initialize global allocator at: [{:#x}, {:#x})",
232 start_vaddr,
233 start_vaddr + size
234 );
235 GLOBAL_ALLOCATOR.init(start_vaddr, size);
236}
237
238/// Add the given memory region to the global allocator.
239///
240/// Users should ensure that the region is valid and not being used by others,
241/// so that the allocated memory is also valid.
242///
243/// It's similar to [`global_init`], but can be called multiple times.
244pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult {
245 debug!(
246 "add a memory region to global allocator: [{:#x}, {:#x})",
247 start_vaddr,
248 start_vaddr + size
249 );
250 GLOBAL_ALLOCATOR.add_memory(start_vaddr, size)
251}