xref: /DragonOS/kernel/crates/rust-slabmalloc/src/sc.rs (revision 59a6bcf6aee15a11a16431bdf875905c5ecf9157)
1 //! A SCAllocator that can allocate fixed size objects.
2 
3 use core::mem;
4 
5 use crate::*;
6 
7 /// A genius(?) const min()
8 ///
9 /// # What this does
10 /// * create an array of the two elements you want to choose between
11 /// * create an arbitrary boolean expression
12 /// * cast said expresison to a usize
13 /// * use that value to index into the array created above
14 ///
15 /// # Source
16 /// https://stackoverflow.com/questions/53619695/calculating-maximum-value-of-a-set-of-constant-expressions-at-compile-time
17 #[cfg(feature = "unstable")]
18 const fn cmin(a: usize, b: usize) -> usize {
19     [a, b][(a > b) as usize]
20 }
21 
22 /// The boring variant of min (not const).
23 #[cfg(not(feature = "unstable"))]
24 fn cmin(a: usize, b: usize) -> usize {
25     core::cmp::min(a, b)
26 }
27 
28 /// A slab allocator allocates elements of a fixed size.
29 ///
30 /// It maintains three internal lists of objects that implement `AllocablePage`
31 /// from which it can allocate memory.
32 ///
33 ///  * `empty_slabs`: Is a list of pages that the SCAllocator maintains, but
34 ///    has 0 allocations in them, these can be given back to a requestor in case
35 ///    of reclamation.
36 ///  * `slabs`: A list of pages partially allocated and still have room for more.
37 ///  * `full_slabs`: A list of pages that are completely allocated.
38 ///
39 /// On allocation we allocate memory from `slabs`, however if the list is empty
40 /// we try to reclaim a page from `empty_slabs` before we return with an out-of-memory
41 /// error. If a page becomes full after the allocation we move it from `slabs` to
42 /// `full_slabs`.
43 ///
44 /// Similarly, on dealloaction we might move a page from `full_slabs` to `slabs`
45 /// or from `slabs` to `empty_slabs` after we deallocated an object.
46 ///
47 /// If an allocation returns `OutOfMemory` a client using SCAllocator can refill
48 /// it using the `refill` function.
49 pub struct SCAllocator<'a, P: AllocablePage> {
50     /// Maximum possible allocation size for this `SCAllocator`.
51     pub(crate) size: usize,
52     /// Keeps track of succeeded allocations.
53     pub(crate) allocation_count: usize,
54     /// max objects per page
55     pub(crate) obj_per_page: usize,
56     /// List of empty ObjectPages (nothing allocated in these).
57     pub(crate) empty_slabs: PageList<'a, P>,
58     /// List of partially used ObjectPage (some objects allocated but pages are not full).
59     pub(crate) slabs: PageList<'a, P>,
60     /// List of full ObjectPages (everything allocated in these don't need to search them).
61     pub(crate) full_slabs: PageList<'a, P>,
62 }
63 
64 /// Creates an instance of a scallocator, we do this in a macro because we
65 /// re-use the code in const and non-const functions
66 macro_rules! new_sc_allocator {
67     ($size:expr) => {
68         SCAllocator {
69             size: $size,
70             allocation_count: 0,
71             obj_per_page: cmin((P::SIZE - OBJECT_PAGE_METADATA_OVERHEAD) / $size, 8 * 64),
72             empty_slabs: PageList::new(),
73             slabs: PageList::new(),
74             full_slabs: PageList::new(),
75         }
76     };
77 }
78 
79 impl<'a, P: AllocablePage> SCAllocator<'a, P> {
80     const REBALANCE_COUNT: usize = 64;
81 
82     /// Create a new SCAllocator.
83     #[cfg(feature = "unstable")]
84     pub const fn new(size: usize) -> SCAllocator<'a, P> {
85         new_sc_allocator!(size)
86     }
87 
88     #[cfg(not(feature = "unstable"))]
89     pub fn new(size: usize) -> SCAllocator<'a, P> {
90         new_sc_allocator!(size)
91     }
92 
93     /// Returns the maximum supported object size of this allocator.
94     pub fn size(&self) -> usize {
95         self.size
96     }
97 
98     /// Add a new ObjectPage.
99     fn insert_partial_slab(&mut self, new_head: &'a mut P) {
100         self.slabs.insert_front(new_head);
101     }
102 
103     /// Add page to empty list.
104     fn insert_empty(&mut self, new_head: &'a mut P) {
105         assert_eq!(
106             new_head as *const P as usize % P::SIZE,
107             0,
108             "Inserted page is not aligned to page-size."
109         );
110         self.empty_slabs.insert_front(new_head);
111     }
112 
113     /// Since `dealloc` can not reassign pages without requiring a lock
114     /// we check slabs and full slabs periodically as part of `alloc`
115     /// and move them to the empty or partially allocated slab lists.
116     pub(crate) fn check_page_assignments(&mut self) {
117         for slab_page in self.full_slabs.iter_mut() {
118             if !slab_page.is_full() {
119                 // We need to move it from self.full_slabs -> self.slabs
120                 trace!("move {:p} full -> partial", slab_page);
121                 self.move_full_to_partial(slab_page);
122             }
123         }
124 
125         for slab_page in self.slabs.iter_mut() {
126             if slab_page.is_empty(self.obj_per_page) {
127                 // We need to move it from self.slabs -> self.empty_slabs
128                 trace!("move {:p} partial -> empty", slab_page);
129                 self.move_to_empty(slab_page);
130             }
131         }
132     }
133 
134     /// Move a page from `slabs` to `empty_slabs`.
135     fn move_to_empty(&mut self, page: &'a mut P) {
136         let page_ptr = page as *const P;
137 
138         debug_assert!(self.slabs.contains(page_ptr));
139         debug_assert!(
140             !self.empty_slabs.contains(page_ptr),
141             "Page {:p} already in emtpy_slabs",
142             page_ptr
143         );
144 
145         self.slabs.remove_from_list(page);
146         self.empty_slabs.insert_front(page);
147 
148         debug_assert!(!self.slabs.contains(page_ptr));
149         debug_assert!(self.empty_slabs.contains(page_ptr));
150     }
151 
152     /// Move a page from `full_slabs` to `slab`.
153     fn move_partial_to_full(&mut self, page: &'a mut P) {
154         let page_ptr = page as *const P;
155 
156         debug_assert!(self.slabs.contains(page_ptr));
157         debug_assert!(!self.full_slabs.contains(page_ptr));
158 
159         self.slabs.remove_from_list(page);
160         self.full_slabs.insert_front(page);
161 
162         debug_assert!(!self.slabs.contains(page_ptr));
163         debug_assert!(self.full_slabs.contains(page_ptr));
164     }
165 
166     /// Move a page from `full_slabs` to `slab`.
167     fn move_full_to_partial(&mut self, page: &'a mut P) {
168         let page_ptr = page as *const P;
169 
170         debug_assert!(!self.slabs.contains(page_ptr));
171         debug_assert!(self.full_slabs.contains(page_ptr));
172 
173         self.full_slabs.remove_from_list(page);
174         self.slabs.insert_front(page);
175 
176         debug_assert!(self.slabs.contains(page_ptr));
177         debug_assert!(!self.full_slabs.contains(page_ptr));
178     }
179 
180     /// Tries to allocate a block of memory with respect to the `layout`.
181     /// Searches within already allocated slab pages, if no suitable spot is found
182     /// will try to use a page from the empty page list.
183     ///
184     /// # Arguments
185     ///  * `sc_layout`: This is not the original layout but adjusted for the
186     ///     SCAllocator size (>= original).
187     fn try_allocate_from_pagelist(&mut self, sc_layout: Layout) -> *mut u8 {
188         // TODO: Do we really need to check multiple slab pages (due to alignment)
189         // If not we can get away with a singly-linked list and have 8 more bytes
190         // for the bitfield in an ObjectPage.
191 
192         for slab_page in self.slabs.iter_mut() {
193             let ptr = slab_page.allocate(sc_layout);
194             if !ptr.is_null() {
195                 if slab_page.is_full() {
196                     trace!("move {:p} partial -> full", slab_page);
197                     self.move_partial_to_full(slab_page);
198                 }
199                 self.allocation_count += 1;
200                 return ptr;
201             } else {
202                 continue;
203             }
204         }
205 
206         // Periodically rebalance page-lists (since dealloc can't do it for us)
207         if self.allocation_count > SCAllocator::<P>::REBALANCE_COUNT {
208             self.check_page_assignments();
209             self.allocation_count = 0;
210         }
211 
212         ptr::null_mut()
213     }
214 
215     pub fn try_reclaim_pages<F>(&mut self, to_reclaim: usize, dealloc: &mut F) -> usize
216     where
217         F: FnMut(*mut P),
218     {
219         self.check_page_assignments();
220         let mut reclaimed = 0;
221         while reclaimed < to_reclaim {
222             if let Some(page) = self.empty_slabs.pop() {
223                 dealloc(page as *mut P);
224                 reclaimed += 1;
225             } else {
226                 break;
227             }
228         }
229 
230         reclaimed
231     }
232 
233     /// Refill the SCAllocator
234     ///
235     /// # Safety
236     /// ObjectPage needs to be empty etc.
237     pub unsafe fn refill(&mut self, page: &'a mut P) {
238         page.bitfield_mut()
239             .initialize(self.size, P::SIZE - OBJECT_PAGE_METADATA_OVERHEAD);
240         *page.prev() = Rawlink::none();
241         *page.next() = Rawlink::none();
242         trace!("adding page to SCAllocator {:p}", page);
243         self.insert_empty(page);
244     }
245 
246     /// Allocates a block of memory descriped by `layout`.
247     ///
248     /// Returns a pointer to a valid region of memory or an
249     /// AllocationError.
250     ///
251     /// The function may also move around pages between lists
252     /// (empty -> partial or partial -> full).
253     pub fn allocate(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocationError> {
254         trace!(
255             "SCAllocator({}) is trying to allocate {:?}",
256             self.size,
257             layout
258         );
259         assert!(layout.size() <= self.size);
260         assert!(self.size <= (P::SIZE - OBJECT_PAGE_METADATA_OVERHEAD));
261         let new_layout = unsafe { Layout::from_size_align_unchecked(self.size, layout.align()) };
262         assert!(new_layout.size() >= layout.size());
263 
264         let ptr = {
265             // Try to allocate from partial slabs,
266             // if we fail check if we have empty pages and allocate from there
267             let ptr = self.try_allocate_from_pagelist(new_layout);
268             if ptr.is_null() && self.empty_slabs.head.is_some() {
269                 // Re-try allocation in empty page
270                 let empty_page = self.empty_slabs.pop().expect("We checked head.is_some()");
271                 debug_assert!(!self.empty_slabs.contains(empty_page));
272 
273                 let ptr = empty_page.allocate(layout);
274                 debug_assert!(!ptr.is_null(), "Allocation must have succeeded here.");
275 
276                 trace!(
277                     "move {:p} empty -> partial empty count {}",
278                     empty_page,
279                     self.empty_slabs.elements
280                 );
281                 // Move empty page to partial pages
282                 self.insert_partial_slab(empty_page);
283                 ptr
284             } else {
285                 ptr
286             }
287         };
288 
289         let res = NonNull::new(ptr).ok_or(AllocationError::OutOfMemory);
290 
291         if !ptr.is_null() {
292             trace!(
293                 "SCAllocator({}) allocated ptr=0x{:x}",
294                 self.size,
295                 ptr as usize
296             );
297         }
298 
299         res
300     }
301 
302     /// Deallocates a previously allocated `ptr` described by `Layout`.
303     ///
304     /// May return an error in case an invalid `layout` is provided.
305     /// The function may also move internal slab pages between lists partial -> empty
306     /// or full -> partial lists.
307     pub fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) -> Result<(), AllocationError> {
308         assert!(layout.size() <= self.size);
309         assert!(self.size <= (P::SIZE - OBJECT_PAGE_METADATA_OVERHEAD));
310         trace!(
311             "SCAllocator({}) is trying to deallocate ptr = {:p} layout={:?} P.size= {}",
312             self.size,
313             ptr,
314             layout,
315             P::SIZE
316         );
317 
318         let page = (ptr.as_ptr() as usize) & !(P::SIZE - 1);
319 
320         // Figure out which page we are on and construct a reference to it
321         // TODO: The linked list will have another &mut reference
322         let slab_page = unsafe { mem::transmute::<VAddr, &'a mut P>(page) };
323         let new_layout = unsafe { Layout::from_size_align_unchecked(self.size, layout.align()) };
324 
325         let ret = slab_page.deallocate(ptr, new_layout);
326         debug_assert!(ret.is_ok(), "Slab page deallocate won't fail at the moment");
327         ret
328     }
329 }
330