K01 — Object Management After Physical Page Allocation: The SLAB/SLUB Allocator
Key Insight: Placing fixed-size objects on fixed-size pages is the core of memory allocator design.
Why SLAB Is Needed After the Buddy System
The Buddy System manages 4 KB, 8 KB, 16 KB… 4 MB physical page frames. But the kernel needs to allocate smaller objects. Task Struct (tens of bytes), inode (hundreds of bytes), dentry (hundreds of bytes).
单一对象分配的问题(直接用 buddy):
假设需要分配 100 字节对象:
- buddy 最小单位 4KB
- 分配 4KB,浪费 3900 字节
- 1000 个对象 → 浪费 3.9MB
解决方案:SLAB 分配器
- 在 buddy 分配的页上,建立对象池(object cache)
- 每个对象大小固定(2^n 字节)
- 每个 SLAB 包含多个对象(填满整个 4KB 页)
- 分配和释放都是 O(1)
4KB / 100 bytes = 40 对象/页
1000 个对象 → 只需要 25 页,而不是 1000 页SLAB Data Structure
SLAB 的结构:
struct slab {
struct list_head list; // 链表节点(连接 free/partial/full)
unsigned long colouroff; // 颜色偏移(缓存行对齐)
void *s_mem; // 第一个对象的地址
unsigned int inuse; // 已分配对象数
kmem_bufctl_t free; // 空闲对象链表头
};
每个 SLAB 管理一个或多个 4KB 页:
- 包含若干相同大小的对象
- 空闲对象通过 free 链表串联
- 已满的 SLAB 不参与分配
SLAB 状态:
- free:所有对象空闲(不在 CPU cache 中分配)
- partial:部分对象已分配
- full:所有对象已分配kmem_cache 结构(每个对象类型的缓存):
struct kmem_cache {
const char *name; // 缓存名称(如 "task_struct")
size_t object_size; // 对象大小(如 576 bytes)
size_t size; // 对齐后大小(如 640 bytes)
size_t align; // 对齐要求(如 64 bytes)
void (*ctor)(void *); // 构造函数(可选)
struct kmem_cache_cpu __percpu *cpu_slab; // per-CPU 缓存
struct list_head *node; // NUMA 节点链表(每个节点有多个 SLAB)
struct kmem_cache_order_objects size;
struct kmem_cache_order_objects order;
};
关键设计:
- 每个 CPU 有自己的 SLAB(无锁分配)
- 每个 NUMA 节点有自己的 SLAB 链表
- 分配优先从本地 CPU 的 SLAB 取SLUB Allocator (Modern Linux Default)
SLUB = SLAB with Minimal Optimization
- 简化了 SLAB 的实现
- 移除了 per-SLAB 的 free 链表
- 使用 page->freelist 直接指向空闲对象
SLUB 的优势:
1. 更少内存开销(不需要额外元数据)
2. 更好的缓存友好性
3. 更好的 NUMA 局部性
SLUB page 结构(嵌入在 struct page 中):
struct page {
union {
struct {
unsigned long flags; // 页面状态
atomic_t _refcount; // 引用计数
unsigned long:25; // 空闲对象数
unsigned long inuse:16;
void *freelist; // 第一个空闲对象
};
};
// ...
};
freelist 指向每个对象中的下一个空闲对象:
freelist[0] → 对象 A
freelist[A] → 对象 B
freelist[B] → 对象 C
freelist[C] = NULL(结束)SLUB 分配流程:
__slab_alloc(slab, flags):
// 1. 从 current CPU 的 slab 取
c = get_cpu_ptr(cachep->cpu_slab);
page = c->page;
if (!page) // CPU slab 为空
goto new_slab;
// 2. 从 page->freelist 取对象
object = page->freelist;
page->freelist = object[next]; // 移动到下一个空闲对象
page->inuse++;
return object;
new_slab:
// 3. 从 buddy 分配新页面
page = alloc_pages(flags, order);
// 4. 初始化 freelist
page->freelist = objects; // 构建空闲链表
page->inuse = 0;
// 5. 更新 cpu_slab
c->page = page;
return object;per-CPU Cache and Lock Optimization
SLUB 最大的优化:per-CPU 本地缓存
每个 CPU 维护自己的"当前 SLAB":
- 分配时不需要获取锁(无竞争)
- O(1) 时间
- 避免了跨 CPU 的锁竞争
per-CPU slab 结构:
struct kmem_cache_cpu {
void **freelist; // 指向本地 SLAB 的空闲对象
unsigned long tid; // 事务 ID(用于调试)
struct page *page; // 当前 SLAB(大部分对象空闲)
struct page *partial; // 部分空闲的 SLAB(本地)
};
CPU0 的分配:
if (cpu_cache.frelist != NULL) {
object = cpu_cache.freelist;
cpu_cache.freelist = *(void**)object;
return object; // 无锁,O(1)
}
跨 NUMA 节点的优化:
- NUMA node 的 SLAB 优先分配给本地 CPU
- 减少跨节点内存访问kmalloc: General Object Allocation
kmalloc 是 SLUB 之上的通用分配接口:
kmalloc(size, flags):
- size:分配大小(1B ~ 8KB)
- 返回:虚拟地址(对象起始地址)
kmalloc 的内部:
- 把 size 映射到对应的 kmem_cache
- kmalloc-32, kmalloc-64, kmalloc-128, ... kmalloc-8192
- 每个 size 类别有一个独立的 kmem_cache
大小类别(Power of 2):
32 / 64 / 128 / 256 / 512 / 1024 / 2048 / 4096 / 8192 bytes
实际分配示例:
kmalloc(100, GFP_KERNEL)
→ 找到最接近的 size = 128
→ 从 kmalloc-128 cache 分配
→ 实际分配 128 bytes
slabinfo 查看缓存:
$ slabtop
OBJS ACTIVE USE SIZE OBJECTS SLABS
12345 12000 97% 64 24567 1234 kmalloc-64
5678 5600 98% 128 8123 456 kmalloc-128Summary
- Buddy vs SLAB: Buddy allocates 4KB+ blocks; SLAB cuts large pages into small object pools
- SLAB Structure: slab = 1+ pages + object array + free list + state (free/partial/full)
- SLUB (modern): page->freelist directly points to free objects (no extra metadata); page->inuse counter
- per-CPU Cache: Each CPU has its own current slab; lock-free O(1) allocation
- kmalloc: General interface over SLUB; size maps to cache (power of 2); uses GFP_KERNEL flags
Next (K02): How exactly does virtual-to-physical address translation work? Multi-level page tables, TLB caching, and the complete page fault handling flow.
Comments