# 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::()`, чтобы освобождённый блок мог хранить указатели списка. Гарантируется, потому что наименьший BLOCK_SIZE (8) ≥ `size_of::>` (8 байт на 64-bit). ## Locked — примитивная spinlock-обёртка ```rust pub struct Locked { inner: UnsafeCell, 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 = 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 строк кода.