Browse Source

feat: 释放slab中的空闲页面到buddy (#932)

* patch: 释放slab中的空闲页面到buddy

* 校验释放的slab_page的起始地址与大小 & SCAllcator增加空闲块计数器
Jomo 5 months ago
parent
commit
53629ac383

+ 11 - 1
kernel/crates/rust-slabmalloc/src/lib.rs

@@ -65,7 +65,12 @@ pub enum AllocationError {
 /// Needs to adhere to safety requirements of a rust allocator (see GlobalAlloc et. al.).
 pub unsafe trait Allocator<'a> {
     fn allocate(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocationError>;
-    fn deallocate(&mut self, ptr: NonNull<u8>, layout: Layout) -> Result<(), AllocationError>;
+    unsafe fn deallocate(
+        &mut self,
+        ptr: NonNull<u8>,
+        layout: Layout,
+        slab_callback: &'static dyn CallBack,
+    ) -> Result<(), AllocationError>;
 
     /// Refill the allocator with a [`ObjectPage`].
     ///
@@ -77,3 +82,8 @@ pub unsafe trait Allocator<'a> {
         new_page: &'a mut ObjectPage<'a>,
     ) -> Result<(), AllocationError>;
 }
+
+/// 将slab_page归还Buddy的回调函数
+pub trait CallBack: Send + Sync {
+    unsafe fn free_slab_page(&self, _: *mut u8, _: usize) {}
+}

+ 14 - 0
kernel/crates/rust-slabmalloc/src/pages.rs

@@ -255,6 +255,20 @@ pub trait AllocablePage {
         self.bitfield().clear_bit(idx);
         Ok(())
     }
+
+    /// 统计page中还可以分配多少个object
+    fn free_obj_count(&self) -> usize {
+        // 统计page中还可以分配多少个object
+        let mut free_obj_count = 0;
+
+        // 遍历page中的bitfield(用来统计内存分配情况的u64数组)
+        for b in self.bitfield().iter() {
+            let bitval = b.load(Ordering::Relaxed);
+            free_obj_count += bitval.count_zeros() as usize;
+        }
+
+        free_obj_count
+    }
 }
 
 /// Holds allocated data within a 4 KiB page.

+ 30 - 4
kernel/crates/rust-slabmalloc/src/sc.rs

@@ -59,21 +59,29 @@ pub struct SCAllocator<'a, P: AllocablePage> {
     pub(crate) slabs: PageList<'a, P>,
     /// List of full ObjectPages (everything allocated in these don't need to search them).
     pub(crate) full_slabs: PageList<'a, P>,
+    /// Free objects count
+    pub(crate) free_obj_count: usize,
+    /// Maximum free objects num for this `SCAllocator`.
+    pub(crate) free_limit: usize,
 }
 
 /// Creates an instance of a scallocator, we do this in a macro because we
 /// re-use the code in const and non-const functions
 macro_rules! new_sc_allocator {
-    ($size:expr) => {
+    ($size:expr) => {{
+        let obj_per_page = cmin((P::SIZE - OBJECT_PAGE_METADATA_OVERHEAD) / $size, 8 * 64);
         SCAllocator {
             size: $size,
             allocation_count: 0,
-            obj_per_page: cmin((P::SIZE - OBJECT_PAGE_METADATA_OVERHEAD) / $size, 8 * 64),
+            obj_per_page,
             empty_slabs: PageList::new(),
             slabs: PageList::new(),
             full_slabs: PageList::new(),
+            // TODO: 优化free_limit的计算: https://bbs.dragonos.org.cn/t/topic/358
+            free_limit: 2 * obj_per_page,
+            free_obj_count: 0,
         }
-    };
+    }};
 }
 
 impl<'a, P: AllocablePage> SCAllocator<'a, P> {
@@ -241,6 +249,7 @@ impl<'a, P: AllocablePage> SCAllocator<'a, P> {
         *page.next() = Rawlink::none();
         trace!("adding page to SCAllocator {:p}", page);
         self.insert_empty(page);
+        self.free_obj_count += self.obj_per_page;
     }
 
     /// Allocates a block of memory descriped by `layout`.
@@ -294,6 +303,7 @@ impl<'a, P: AllocablePage> SCAllocator<'a, P> {
                 self.size,
                 ptr as usize
             );
+            self.free_obj_count -= 1;
         }
 
         res
@@ -304,7 +314,12 @@ impl<'a, P: AllocablePage> SCAllocator<'a, P> {
     /// May return an error in case an invalid `layout` is provided.
     /// The function may also move internal slab pages between lists partial -> empty
     /// or full -> partial lists.
-    pub fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) -> Result<(), AllocationError> {
+    pub unsafe fn deallocate(
+        &mut self,
+        ptr: NonNull<u8>,
+        layout: Layout,
+        slab_callback: &'static dyn CallBack,
+    ) -> Result<(), AllocationError> {
         assert!(layout.size() <= self.size);
         assert!(self.size <= (P::SIZE - OBJECT_PAGE_METADATA_OVERHEAD));
         trace!(
@@ -324,6 +339,17 @@ impl<'a, P: AllocablePage> SCAllocator<'a, P> {
 
         let ret = slab_page.deallocate(ptr, new_layout);
         debug_assert!(ret.is_ok(), "Slab page deallocate won't fail at the moment");
+        self.free_obj_count += 1;
+        let is_empty_after_dealloc = slab_page.is_empty(self.obj_per_page);
+
+        // 如果slab_page是空白的,且空闲块数大于free_limit,将slab_page归还buddy
+        if self.free_obj_count >= self.free_limit && is_empty_after_dealloc {
+            self.slabs.remove_from_list(slab_page);
+            // 将slab_page归还buddy
+            slab_callback.free_slab_page(slab_page as *const P as *mut u8, P::SIZE);
+        }
+        self.check_page_assignments();
+
         ret
     }
 }

+ 9 - 12
kernel/crates/rust-slabmalloc/src/zone.rs

@@ -3,7 +3,6 @@
 //! The ZoneAllocator achieves this by having many `SCAllocator`
 
 use crate::*;
-use core::sync::atomic::Ordering;
 
 /// Creates an instance of a zone, we do this in a macro because we
 /// re-use the code in const and non-const functions
@@ -139,16 +138,8 @@ impl<'a> ZoneAllocator<'a> {
 
             // 遍历scallocator中的部分分配的page(partial_page)
             for slab_page in scallocator.slabs.iter_mut() {
-                // 统计page中还可以分配多少个object
-                let mut free_obj_count = 0;
-                // 遍历page中的bitfield(用来统计内存分配情况的u64数组)
-                for b in slab_page.bitfield().iter() {
-                    let bitval = b.load(Ordering::Relaxed);
-                    let free_count = bitval.count_zeros() as usize;
-                    free_obj_count += free_count;
-                }
                 // 剩余可分配object数乘上page中规定的每个object的大小,即空闲空间
-                free += free_obj_count * scallocator.size();
+                free += slab_page.free_obj_count() * scallocator.size();
             }
             // 遍历scallocator中的empty_page,把空页空间也加上去
             free +=
@@ -178,9 +169,15 @@ unsafe impl<'a> crate::Allocator<'a> for ZoneAllocator<'a> {
     /// # Arguments
     ///  * `ptr` - Address of the memory location to free.
     ///  * `layout` - Memory layout of the block pointed to by `ptr`.
-    fn deallocate(&mut self, ptr: NonNull<u8>, layout: Layout) -> Result<(), AllocationError> {
+    ///  * `slab_callback` - The callback function to free slab_page in buddy.
+    unsafe fn deallocate(
+        &mut self,
+        ptr: NonNull<u8>,
+        layout: Layout,
+        slab_callback: &'static dyn CallBack,
+    ) -> Result<(), AllocationError> {
         match ZoneAllocator::get_slab(layout.size()) {
-            Slab::Base(idx) => self.small_slabs[idx].deallocate(ptr, layout),
+            Slab::Base(idx) => self.small_slabs[idx].deallocate(ptr, layout, slab_callback),
             Slab::Unsupported => Err(AllocationError::InvalidLayout),
         }
     }

+ 1 - 1
kernel/src/mm/allocator/kernel_allocator.rs

@@ -51,7 +51,7 @@ impl KernelAllocator {
         return Ok(NonNull::from(slice));
     }
 
-    unsafe fn free_in_buddy(&self, ptr: *mut u8, layout: Layout) {
+    pub(super) unsafe fn free_in_buddy(&self, ptr: *mut u8, layout: Layout) {
         // 由于buddy分配的页数量是2的幂,因此释放的时候也需要按照2的幂向上取整。
         let count = (page_align_up(layout.size()) / MMArch::PAGE_SIZE).next_power_of_two();
         let page_frame_count = PageFrameCount::new(count);

+ 15 - 1
kernel/src/mm/allocator/slab.rs

@@ -4,12 +4,16 @@ use alloc::boxed::Box;
 use log::debug;
 use slabmalloc::*;
 
+use crate::{arch::MMArch, mm::MemoryManagementArch, KERNEL_ALLOCATOR};
+
 // 全局slab分配器
 pub(crate) static mut SLABALLOCATOR: Option<SlabAllocator> = None;
 
 // slab初始化状态
 pub(crate) static mut SLABINITSTATE: AtomicBool = AtomicBool::new(false);
 
+static SLAB_CALLBACK: SlabCallback = SlabCallback;
+
 /// slab分配器,实际为一堆小的allocator,可以在里面装4K的page
 /// 利用这些allocator可以为对象分配不同大小的空间
 pub(crate) struct SlabAllocator {
@@ -52,7 +56,7 @@ impl SlabAllocator {
     ) -> Result<(), AllocationError> {
         if let Some(nptr) = NonNull::new(ptr) {
             self.zone
-                .deallocate(nptr, layout)
+                .deallocate(nptr, layout, &SLAB_CALLBACK)
                 .expect("Couldn't deallocate");
             return Ok(());
         } else {
@@ -80,3 +84,13 @@ pub unsafe fn slab_usage() -> SlabUsage {
         SlabUsage::new(0, 0)
     }
 }
+
+/// 归还slab_page给buddy的回调
+pub struct SlabCallback;
+impl CallBack for SlabCallback {
+    unsafe fn free_slab_page(&self, base_addr: *mut u8, size: usize) {
+        assert_eq!(base_addr as usize & (MMArch::PAGE_SIZE - 1), 0); // 确认地址4k对齐
+        assert_eq!(size, MMArch::PAGE_SIZE); // 确认释放的slab_page大小
+        KERNEL_ALLOCATOR.free_in_buddy(base_addr, Layout::from_size_align_unchecked(size, 1));
+    }
+}