212 lines
7.1 KiB
Markdown
212 lines
7.1 KiB
Markdown
# Slab-аллокатор: куча ядра
|
||
|
||
## Концептуальная модель
|
||
|
||
**SlabAllocator** — это глобальный аллокатор кучи для ядра.
|
||
Rust-программы используют `alloc::vec::Vec`, `alloc::boxed::Box` и т.д. —
|
||
все они в конечном счёте вызывают `GlobalAlloc::alloc()`.
|
||
|
||
### Стратегия
|
||
|
||
Для маленьких блоков (≤ 2048 байт) — **slab lists**:
|
||
предварительно нарезанные блоки фиксированного размера.
|
||
|
||
Для больших блоков (> 2048 байт) — **freelist больших блоков**:
|
||
освобождённые блоки переиспользуются.
|
||
|
||
Если ни там, ни там нет — **bump allocation**:
|
||
последовательная раздача из заранее выделенного региона.
|
||
|
||
```
|
||
Запрос alloc(32 байта):
|
||
1. list_index(32) = 2 (BLOCK_SIZES[2] = 32)
|
||
2. list_heads[2] есть свободный блок?
|
||
- Да: отдаём его, заменяем голову списка
|
||
- Нет: bump-аллокация блока размером 32 (fallback_alloc)
|
||
|
||
Запрос alloc(4096 байт):
|
||
1. list_index(4096) = None (максимум 2048)
|
||
2. large_block_free есть блок ≥ 4096?
|
||
- Да: отдаём
|
||
- Нет: bump-аллокация
|
||
```
|
||
|
||
## Структуры данных
|
||
|
||
### Slab списки — `list_heads`
|
||
|
||
```rust
|
||
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
||
|
||
struct ListNode {
|
||
next: Option<&'static mut ListNode>,
|
||
}
|
||
|
||
pub struct SlabAllocator {
|
||
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()], // 9 списков
|
||
large_block_free: Option<&'static mut LargeBlockNode>,
|
||
heap_start: usize,
|
||
heap_end: usize,
|
||
next_bump: usize,
|
||
}
|
||
```
|
||
|
||
### LargeBlockNode — для блоков > 2048 байт
|
||
|
||
```rust
|
||
struct LargeBlockNode {
|
||
size: usize,
|
||
next: Option<&'static mut LargeBlockNode>,
|
||
}
|
||
```
|
||
|
||
### Инициализация: `init(start, size)`
|
||
|
||
```rust
|
||
pub fn init(&mut self, start: usize, size: usize) {
|
||
self.heap_start = start;
|
||
self.next_bump = start;
|
||
self.heap_end = start + size;
|
||
}
|
||
```
|
||
|
||
Вызывается в kmain() после настройки page table для области `0xFFFF_9000_0000_0000`.
|
||
|
||
## GlobalAlloc — реализация
|
||
|
||
### alloc(layout)
|
||
|
||
```rust
|
||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||
let mut allocator = self.lock();
|
||
match SlabAllocator::list_index(&layout) {
|
||
Some(index) => {
|
||
// 1. Пробуем slab list
|
||
match allocator.list_heads[index].take() {
|
||
Some(node) => {
|
||
allocator.list_heads[index] = node.next.take();
|
||
node as *mut ListNode as *mut u8
|
||
}
|
||
None => {
|
||
// 2. Нет в slab — bump alloc целого блока
|
||
let block_size = BLOCK_SIZES[index];
|
||
allocator.fallback_alloc(
|
||
Layout::from_size_align(block_size, block_size).unwrap()
|
||
)
|
||
}
|
||
}
|
||
}
|
||
None => allocator.fallback_alloc(layout) // > 2048
|
||
}
|
||
}
|
||
```
|
||
|
||
### fallback_alloc(layout)
|
||
|
||
```rust
|
||
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
|
||
let size = layout.size().max(layout.align());
|
||
|
||
// 1. Пробуем large block free list (для > 2048)
|
||
if size > 2048 {
|
||
// поиск по large_block_free
|
||
while let Some(ref mut node) = *field {
|
||
if node.size >= size {
|
||
// отдаём, удаляем из списка
|
||
return node as *mut u8;
|
||
}
|
||
field = &mut node.next;
|
||
}
|
||
}
|
||
|
||
// 2. Bump alloc
|
||
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
|
||
let alloc_end = alloc_start.checked_add(layout.size())?;
|
||
if alloc_end > self.heap_end {
|
||
null_mut() // OOM
|
||
} else {
|
||
self.next_bump = alloc_end;
|
||
alloc_start as *mut u8
|
||
}
|
||
}
|
||
```
|
||
|
||
### dealloc(ptr, layout)
|
||
|
||
```rust
|
||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||
let mut allocator = self.lock();
|
||
match SlabAllocator::list_index(&layout) {
|
||
Some(index) => {
|
||
// Добавляем в slab list (переиспользование)
|
||
let new_node = ListNode { next: allocator.list_heads[index].take() };
|
||
let new_node_ptr = ptr as *mut ListNode;
|
||
unsafe { new_node_ptr.write(new_node); }
|
||
allocator.list_heads[index] = Some(&mut *new_node_ptr);
|
||
}
|
||
None => {
|
||
// Добавляем в large block free list
|
||
let new_node = LargeBlockNode {
|
||
size: layout.size().max(layout.align()),
|
||
next: allocator.large_block_free.take(),
|
||
};
|
||
let new_node_ptr = ptr as *mut LargeBlockNode;
|
||
unsafe { new_node_ptr.write(new_node); }
|
||
allocator.large_block_free = Some(&mut *new_node_ptr);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Важно:** `layout.size()` должен быть ≥ `size_of::<ListNode>()`, чтобы
|
||
освобождённый блок мог хранить указатели списка. Гарантируется, потому
|
||
что наименьший BLOCK_SIZE (8) ≥ `size_of::<Option<&'static mut ListNode>>`
|
||
(8 байт на 64-bit).
|
||
|
||
## Locked<T> — примитивная spinlock-обёртка
|
||
|
||
```rust
|
||
pub struct Locked<A> {
|
||
inner: UnsafeCell<A>,
|
||
lock: AtomicBool,
|
||
}
|
||
```
|
||
|
||
- `lock()` — spin-wait с CAS + `hint::spin_loop()`.
|
||
- `LockedGuard` — RAII guard, при Drop отпускает блокировку.
|
||
- `unsafe impl Sync` — потому что `lock()` гарантирует взаимное исключение.
|
||
|
||
Используется не только для аллокатора, но и для PMM, serial port,
|
||
VMM KERNEL_SPACE и других глобальных структур.
|
||
|
||
## Глобальный аллокатор
|
||
|
||
```rust
|
||
#[global_allocator]
|
||
pub static ALLOCATOR: Locked<SlabAllocator> = Locked::new(SlabAllocator::new());
|
||
```
|
||
|
||
Этот `static` перехватывает все вызовы `alloc::alloc::alloc()`,
|
||
делая возможным использование `Vec`, `Box`, `String` и т.д. в ядре.
|
||
|
||
## Heap init в kmain()
|
||
|
||
```rust
|
||
let heap_start = 0xFFFF_9000_0000_0000;
|
||
let heap_size = 8 * 1024 * 1024; // 8 MiB
|
||
// Предварительно map'им все страницы кучи
|
||
for i in (0..heap_size).step_by(4096) {
|
||
let frame = mem::pmm::alloc_frame().expect("OOM");
|
||
p4.map_page(VirtAddr(heap_start + i), frame, flags);
|
||
}
|
||
// Инициализируем аллокатор
|
||
allocator::ALLOCATOR.lock().init(heap_start as usize, heap_size);
|
||
```
|
||
|
||
## Почему slab?
|
||
|
||
1. **Скорость**: alloc/dealloc — O(1) для малых блоков.
|
||
2. **Нет фрагментации**: блоки фиксированного размера.
|
||
3. **Локальность**: блоки одного размера рядом в памяти.
|
||
4. **Простота**: ~180 строк кода.
|