引言:为什么需要 SLAB 分配器?
在 Linux 内核中,内存管理远不止kmalloc和kfree这么简单。内核对象(如 task_struct、inode、dentry)频繁创建和销毁,若直接依赖伙伴系统(Buddy System)以页为单位分配,将产生严重的内部碎片和初始化开销。SLAB 分配器应运而生——它通过对象缓存、着色和本地 CPU 队列,将内核小对象分配性能提升至接近 O(1)。
一、SLAB 分配器的经典设计 (Linux 2.1)
1994 年,Jeff Bonwick 为 SunOS 5.4 设计了原始 SLAB 分配器,Linux 2.1 将其引入。其核心思想是"缓存着色 对象复用":
- slab :由 1~N 个连续物理页组成的内存块,是 SLAB 分配的基本单元
- 对象缓存(kmem_cache):每种内核对象专用一个缓存,包含 full/partial/partial 三个 slab 链表
- 着色(Cache Coloring):在 slab 起始位置引入 colored offset,避免多个 slab 的相同偏移对象映射到同一 cache line,减少冲突未命中
- 构造函数/析构函数:对象分配时调用构造函数初始化,释放时调用析构函数清理,避免重复初始化开销
硬件缓存行交互示意:
---------------------------
| colored offset (padding) | ← 颜色偏移,随机化起始地址
---------------------------
| object 0 | object 1 | ... | ← 对象紧密排列
---------------------------
| slab header (free list) |
---------------------------
二、SLUB:简化与高性能 (Linux 2.6.23 )
随着多核时代到来,SLAB 的复杂元数据(每个 slab 一个管理结构、三链表维护)成为扩展性瓶颈。Christoph Lameter 提出 SLUB(Unqueued Slab),自 2.6.23 起成为默认分配器。
SLUB 的核心简化:
- 移除 slab 管理结构:用 page 结构体中的
freelist和inuse字段统一管理,零额外开销 - CPU 本地缓存:每个 CPU 维护独立的
kmem_cache_cpu,包含 freelist 指针和 partial 页面指针,无锁分配 - 简化三链表为单链表:仅保留 partial 链表,full slab 不跟踪(对象全满时不参与分配决策)
- Red-Zone 检测:对象尾部放置特殊字节,溢出时触发 Kernel Panic 警报
- Poison 机制:释放时填充
0x5a5a5a5a,后续分配前清零,捕获 Use-After-Free
SLUB 分配流程
kmalloc(size)
│
├─ 1. 查找对应 size 的 kmem_cache(按 size 分级缓存)
│
├─ 2. 检查 CPU 本地 freelist
│ ├─ hit → 直接返回节点(快速路径,无锁)
│ └─ miss → 进入步骤 3
│
├─ 3. 从 CPU partial 页面补充
│ ├─ 有 partial → 切换到该页面 freelist
│ └─ 无 partial → 进入步骤 4
│
├─ 4. 从 Node partial 链表获取(NUMA 感知)
│ ├─ 有 → 移到 CPU partial
│ └─ 无 → 进入步骤 5
│
└─ 5. 向 Buddy System 申请新页面,初始化为 slab
三、SLOB:嵌入式极简主义
SLOB(Simple List Of Blocks)面向内存极度受限的嵌入式设备(如早期 ARM 嵌入式板)。其设计哲学是"代码极简、元数据极少":
- 使用首次适应(First-Fit)算法搜索空闲块,无着色、无 CPU 本地缓存
- 仅维护一个全局空闲链表(free list),分配/释放仅操作链表指针
- 代码体积仅 ~600 行(SLUB 约 5000 行),ROM 占用极小
- 代价是分配延迟不稳定和外部碎片严重,不适合大内存系统
典型的分配请求结构:
struct slob_block {
int units; // 包含头部的总单元数(1 unit = sizeof(slob_t))
struct slob_block *next; // 空闲时指向下一块
};
// slob_t 大小 = 2 * sizeof(int),最小可管理单元
slob_t *slob_alloc(size_t size, gfp_t gfp, int align)
四、性能对比:SLAB vs SLUB vs SLOB
| 维度 | SLAB | SLUB | SLOB |
|---|---|---|---|
| 元数据开销 | 高(每 slab 一个结构) | 零(复用 page 字段) | 极低(仅链表) |
| 分配延迟 | 低(O(1)) | 极低(无锁快速路径) | 不稳定(首次适应遍历) |
| 多核扩展性 | 差(全局锁) | 优秀(CPU 本地缓存) | 极差(全局链表) |
| 内存碎片 | 低(对象复用) | 低(对象复用) | 严重(不合并相邻空闲块) |
| 代码复杂度 | 高(~8000行) | 中(~5000行) | 低(~600行) |
| 适用场景 | 早期单核到多核 | 现代多核服务器(默认) | 嵌入式/MB 级内存 |
五、NUMA 感知与大页支持
SLUB 在 NUMA 架构下引入 kmem_cache_node,每个 NUMA 节点维护独立的 partial 列表。kmalloc_node(size, node) 优先在目标节点分配,减少跨节点访问延迟。
Linux 5.17 引入 kmalloc-rclm (Reclaimable) 缓存,配合 CONFIG_SLUB_TINY 为小型设备进一步压缩代码。此外,SLUB 也支持 HugeTLB 页分配大对象(kmem_cache_alloc_large),减少 TLB 压力。
六、实战调优与诊断技巧
6.1 查看当前 SLUB 分配器状态
# 查看所有 SLUB 缓存
cat /proc/slabinfo
# 示例输出:
# name

发表评论 取消回复