3. Slab 分配器引入:为什么需要 Slab 分配器

在 Linux 内核中,伙伴系统(Buddy System)负责管理物理页框的分配与回收,但其最小分配粒度为 4 KiB(一个页面)。内核中大量频繁分配和释放的小型数据结构(如 task_struct、inode、dentry、mm_struct 等)通常只有几十到几百字节。如果直接使用伙伴系统分配这些对象,将导致严重的内部碎片和性能开销。

Slab 分配器正是为了解决这一问题而设计的——它作为伙伴系统之上的通用缓存层,专门管理大小固定、频繁分配回收的内核对象。

3.1 伙伴系统的局限性

问题维度 伙伴系统直接分配小对象的缺陷
内部碎片 最小分配单元为 4 KiB,一个 64 字节的对象占用整个页框,浪费 4032 字节(约 98.4%)
分配延迟 每次分配/释放都涉及页框合并与分裂,频繁操作导致性能抖动
对象初始化 每次分配后需要重新初始化对象,无法复用已释放对象的构造状态
缓存局部性 小对象散落在不同页框中,CPU 缓存命中率低

关键数据:在典型 Linux 系统中,内核对象分配请求中超过 85% 的大小在 32 字节到 2 KiB 之间。如果这些请求全部由伙伴系统直接处理,内存浪费率将超过 60%。

3.2 Slab 分配器解决的问题

Slab 分配器从伙伴系统申请一整页或多页连续内存,然后将其切分为多个大小相同的 对象槽(object slot),并维护每个槽的分配状态。它主要解决以下四个核心问题:

  1. 消除内部碎片:将大页框切分为与对象大小精确匹配的槽,每个对象只占用其实际大小(加上少量对齐开销)。
  2. 减少分配/释放开销:对象分配和释放仅需操作 slab 内部链表,无需触发伙伴系统的页框分裂与合并。
  3. 对象缓存与复用:释放的对象不立即销毁,而是保留其构造状态(如锁、引用计数等),下次分配时直接复用,减少初始化成本。
  4. 硬件缓存友好:同一 slab 中的对象在物理上连续,且 slab 分配器通过着色(coloring)技术使不同 slab 的起始地址错开,均衡 CPU 缓存行使用。

性能对比:在 2.6 内核的基准测试中,使用 slab 分配器处理 dentry 对象分配时,分配延迟比直接使用伙伴系统降低了约 40 倍,内存利用率从 35% 提升到 92%。

3.3 Slab 分配器与伙伴系统的关系

Slab 分配器并不是伙伴系统的替代品,而是建立在伙伴系统之上的 上层缓存层。两者形成清晰的分层协作关系:

Slab 分配器与伙伴系统的分层协作关系 内核对象层 task_struct · inode · dentry · mm_struct · 文件对象 · 网络缓冲区 Slab 分配器(通用缓存层) 对象缓存 · 对象复用 · 着色 · 每 CPU 缓存 kmem_cache · slab · 空闲链表 · 构造/析构函数 伙伴系统(页框分配器) 2^0 · 2^1 · 2^2 · … · 2^10 阶页块 · 页框合并与分裂 物理内存(DRAM · 页框) 4 KiB 页框 · NUMA 节点 · ZONE_DMA / ZONE_NORMAL / ZONE_HIGHMEM 向上 提供 对象 缓存 服务 向下 申请 页框

3.4 核心协作流程

Slab 分配器与伙伴系统之间的交互遵循以下典型流程:

  1. 缓存创建:调用 kmem_cache_create() 时,slab 分配器仅初始化元数据结构,不立即从伙伴系统申请内存。
  2. 懒分配:首次对象分配时,slab 分配器调用 __get_free_pages() 从伙伴系统申请一个或多个连续页框,作为 slab 的物理载体。
  3. 对象切分:将申请的页框按对象大小切分为多个槽,初始化空闲链表。
  4. 对象分配:从空闲链表头部取出一个对象,返回给调用者。若当前 slab 已满,则申请新的页框创建新 slab。
  5. 对象释放:将对象放回空闲链表。若整个 slab 的所有对象都已释放,则调用 free_pages() 将页框归还给伙伴系统。

注意:Slab 分配器不会在每次对象释放时立即归还页框。它通常保留部分空闲 slab 以应对未来的分配请求,这种行为称为 缓存驻留(cache residency)。过度驻留可能导致内存浪费,但完全禁用驻留又会增加伙伴系统的分裂合并开销。调优时需要在这两者之间找到平衡。

3.5 关键数据结构关系

数据结构 所属层级 作用 与伙伴系统的交互
kmem_cache Slab 分配器 描述一种对象类型的缓存(如 dentry 缓存) 通过 gfp_t 标志控制页框申请行为
slab Slab 分配器 管理一组连续页框及其中的对象槽 包含指向 struct page 的指针
struct page 伙伴系统 描述一个物理页框 slab 分配器通过 alloc_pages() 获取
free_area 伙伴系统 维护不同阶的空闲页块链表 slab 释放页框时插入对应链表

3.6 为什么不能直接用伙伴系统替代 Slab

假设内核需要频繁分配 256 字节的对象:

  • 伙伴系统方案:每次分配一个 4 KiB 页框,内部碎片 3840 字节(93.75%)。1000 次分配消耗 4 MiB 物理内存,实际只使用 256 KiB。
  • Slab 方案:一个 4 KiB 页框可容纳 16 个对象(256 字节 × 16 = 4096 字节),内部碎片为 0。1000 次分配仅需 63 个页框(约 252 KiB),内存利用率提升 16 倍。

结论:Slab 分配器不是伙伴系统的替代品,而是其 必要补充。伙伴系统负责物理页框的宏观管理,Slab 分配器负责内核对象的微观缓存。两者协同工作,构成了 Linux 内核内存管理的核心骨架。