#allocator #no-alloc #slab #no-std #os

nightly no-std rslab

Rust的slab分配器

5个版本

0.2.1 2023年2月21日
0.2.0 2022年10月31日
0.1.2 2022年10月30日
0.1.1 2022年10月27日
0.1.0 2022年10月27日

#365内存管理 中排名

每月 26 次下载

MIT/Apache

1MB
1K SLoC

slab分配器实现

模型结构

无标题-2022-10-20-1706.excalidraw

对外接口

pub fn init_slab_system(frame_size: usize, cache_line_size: usize) 

此函数用于初始化slab系统,用户需要告知slab系统分配的页帧大小和缓存行大小,页帧大小用于计算对象数量,缓存行大小用于着色偏移计算。slab系统会完成第一个Cache的初始化并创建多个常用大小的Cache,这些cache对象的大小从8B-8MB。

pub fn print_slab_system_info()

这个函数用于打印slab系统的使用情况。

pub struct SlabAllocator;

上述结构体已经实现了GlobalAlloc,因此外部系统可以直接声明其为#[global_allocator]以启用alloc内的大多数数据结构。

pub trait Object {
    fn construct() -> Self;
}
/// 对象分配器接口,用于专门分配用户自定义对象
pub trait ObjectAllocator<T: Object> {
    /// 分配一个对象,返回对象的可变引用,如果分配失败则返回失败原因
    fn alloc(&self) -> Result<&mut T,SlabError>;
    /// 释放一个对象,如果释放失败则返回失败原因
    fn dealloc(&self, obj: &mut T) -> Result<(), SlabError>;
    /// 销毁对象分配器
    fn destroy(&mut self);
}
pub struct SlabCache<T: Object>{..}

SlabCache是内部Cache的封装,如果用户想单独创建一个Cache,则需要使用此数据结构进行创建,用户自定义的数据需要实现object 这个trait,这样SlabCache在分配时可以进行初始化以免用户直接接触裸指针,这里为简单起见,若分配成功则返回对象的引用。

一个简单实例如下

struct TestObj {
    a: [u8;56],
}
impl Object for TestObj{
    fn construct() -> Self {
        Self{
            a:[0;56]
        }
    }
}
let mut cache = SlabCache::<TestObj>::new("mycache").unwrap();
    // alloc from your cache
let ptr = cache.alloc().unwrap();

内部接口

外部需要提供的接口:

pub fn alloc_frames(num:usize)->*mut u8
pub fn free_frames(addr: *mut u8, num: usize) 
pub fn current_cpu_id() -> usize

外部需要提供一个分配页面的接口和回收页面的接口。为了支持多核的CPU,减少核心之间的争用,定义了Per_CPU数据,因此需要一个获取当前核心的id的接口。若此分配器用户态,可简单将其设为返回0即可。

系统内部为空闲链表的设定了一个常数上限,当达到上限将触发回收页帧。

使用方式

  1. 首先实现外部需要提供的三个接口
#[no_mangle]
fn alloc_frames(num: usize) -> *mut u8 
#[no_mangle]
fn free_frames(addr: *mut u8, num: usize) 
#[no_mangle]
pub fn current_cpu_id() -> usize
  1. 初始化slab子系统
init_slab_system(FRAME_SIZE, 32);
  1. 在Rust中,声明全局全局分配器
#[global_allocator]
static HEAP_ALLOCATOR: SlabAllocator = SlabAllocator;

现在就可以正常使用slab子系统提供的分配和回收物理内存的功能了。

在内核的文件模块,进程模块,都可以为既定的结构体创建一个Cache,并使用此Cache分配对象。

性能测试

使用simple-chunk-allocator提供的测试基准,我们测试了几种分配器的性能,得到的结果如下:(ticks表示进行一次分配经过的stamp)

image-20221031202910990

待办事项

  • 每CPU缓存
  • 细粒度的锁
  • 其它优化

依赖项