4. slab分配器核心数据结构:kmem_cache、slab、object的三级结构详解

slab分配器通过三级数据结构实现高效的内存管理:kmem_cache(缓存描述符)、slab( slabs 描述符)和 object(对象)。这种分层设计将内存池化、对象复用与伙伴系统的物理页管理解耦,是 Linux 内核内存子系统性能的关键。

核心思想:kmem_cache 管理同类型对象;每个 kmem_cache 包含多个 slab;每个 slab 由连续物理页组成,内部划分为等长 object。分配时优先从部分满的 slab 获取对象,避免碎片与频繁的伙伴系统交互。

4.1 三级结构总览

下图展示了 kmem_cache → slab → object 的层次关系与核心字段:

slab分配器三级结构总览 slab分配器三级结构总览 kmem_cache (缓存描述符) name, object_size, gfporder, flags slabs_full / slabs_partial / slabs_free 链表头 slab (slab描述符) s_mem (对象起始地址) inuse / free / node list (链表节点) slab (slab描述符) s_mem (对象起始地址) inuse / free / node list (链表节点) slab (slab描述符) s_mem (对象起始地址) inuse / free / node list (链表节点) object [0] object [1] object [2] object [0] object [1] object [2] object [0] object [1] object [2] 关键说明 • kmem_cache 维护三个 slab 链表:slabs_full(全满)、slabs_partial(部分满)、slabs_free(全空) • 分配优先从 slabs_partial 获取,避免碎片;释放后若 slab 全空则归还伙伴系统 • object 在 slab 内连续排列,通过 kmem_bufctl_t 数组或内置空闲链表管理

4.2 kmem_cache:缓存描述符

每个 kmem_cache 实例管理一种特定类型或大小的对象。内核通过 kmem_cache_create() 创建,通过 kmem_cache_destroy() 销毁。其核心字段如下:

字段 类型 说明
name const char * 缓存名称,用于 /proc/slabinfo 标识
object_size unsigned int 每个对象的大小(字节),已对齐
gfporder unsigned int 每个 slab 占用的连续页阶数(2^gfporder 页)
flags unsigned long 缓存标志,如 SLAB_HWCACHE_ALIGN、SLAB_PANIC
num unsigned int 每个 slab 可容纳的对象数量
slabs_full struct list_head 所有对象已分配的 slab 链表
slabs_partial struct list_head 部分对象已分配的 slab 链表(分配首选)
slabs_free struct list_head 所有对象空闲的 slab 链表
ctor void (*)(void *) 对象构造函数(可选),分配时初始化
性能提示:kmem_cache 的 gfporder 和 num 在创建时由 slab 分配器自动计算,目标是使 slab 大小不超过一个页面(小对象)或合理利用大页。可通过 /proc/slabinfo 查看每个缓存的 object_size、num 和 active_objs。

4.3 slab:物理页与对象的管理单元

slab 是连接伙伴系统物理页与上层对象的桥梁。每个 slab 对应 2^gfporder 个连续物理页,其描述符 struct slab 可以存放在 slab 内部(小对象)或外部(大对象)。

slab 内部布局与对象管理 slab 内部布局与对象管理 slab (2^gfporder 个连续物理页) slab 描述符 (struct slab) s_mem, inuse, free, list object 0 (已分配) object 1 (空闲) object 2 (已分配) object 3 (空闲) 空闲链表指针 (free) slab 关键字段与对象管理机制 • s_mem:对象区域的起始虚拟地址,object[i] = s_mem + i * object_size • inuse:当前已分配的对象数量,用于判断 slab 状态(满/部分/空) • free:空闲对象链表的头索引(或指针),分配时从链表头取,释放时插回链表头 • list:链表节点,将 slab 挂入 kmem_cache 的 slabs_full/partial/free 链表

4.4 object:最小分配单元

object 是 slab 分配器返回给调用者的内存块。每个 object 的大小由 kmem_cache 的 object_size 决定,且经过对齐(硬件缓存行对齐可提升性能)。

空闲 object 的管理方式有两种:

  • 内置空闲链表(传统 slab):在空闲 object 的头部存储指向下一个空闲 object 的指针,形成单向链表。分配时从链表头取,释放时插回链表头。
  • kmem_bufctl_t 数组(部分架构):在 slab 描述符后附加一个索引数组,每个空闲 object 对应一个索引,指向下一个空闲对象。
注意:object 在分配时不会清零(除非指定 __GFP_ZERO)。释放后,object 的内容可能残留敏感数据,内核安全模块(如 slab_freelist_random)会随机化空闲链表顺序以降低堆喷攻击风险。

4.5 三级结构协同工作流程

以下步骤展示一次 kmem_cache_alloc() 调用如何穿越三级结构:

  1. 定位 kmem_cache:根据对象类型或大小找到对应的 kmem_cache 实例。
  2. 选择 slab:优先从 slabs_partial 链表头取一个 slab;若为空,则从 slabs_free 取;若都为空,则调用伙伴系统分配新页创建 slab。
  3. 获取 object:从 slab 的空闲链表头取出一个 object,更新 free 指针和 inuse 计数。
  4. 状态迁移:若 slab 的 inuse 达到 num(全满),将其从 slabs_partial 移到 slabs_full;若从空变为部分满,则移入 slabs_partial。
  5. 返回 object:返回 object 的虚拟地址给调用者。
kmem_cache_alloc() 分配流程 kmem_cache_alloc() 分配流程 kmem_cache_alloc() slabs_partial 非空? 是 取链表头 slab 否 检查 slabs_free 取 slabs_free 或新建 slab 从 slab 空闲链表取 object 更新 free / inuse 迁移 slab 到对应链表

4.6 三级结构的性能优势

特性 说明 性能收益
对象复用 释放的 object 直接回收到 slab 空闲链表,不归还伙伴系统 避免频繁的页分配/释放,降低延迟
部分满优先 分配优先从 slabs_partial 取,避免全满 slab 的碎片 提高内存利用率,减少外部碎片
着色 (coloring) 不同 slab 的 object 起始地址偏移不同缓存行 减少 CPU 缓存行冲突,提升多核访问性能
NUMA 感知 每个 NUMA 节点有独立的 slab 链表 分配本地内存,避免跨节点访问
批量操作 kmem_cache_alloc_bulk / free_bulk 一次操作多个 object 减少链表遍历和锁竞争
总结:kmem_cache → slab → object 三级结构是 slab 分配器的核心设计。kmem_cache 提供类型隔离与统计;slab 管理物理页与对象状态;object 实现快速分配与释放。理解这一层级关系是优化内核内存分配性能、排查 slab 相关问题的前提。