引言:为什么需要 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

维度SLABSLUBSLOB
元数据开销高(每 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                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部