2. slab分配器核心数据结构:kmem_cache、slab、object的关系,缓存着色(cache coloring)原理

slab分配器的核心设计围绕三个层次的数据结构展开:kmem_cache(缓存描述符)、slab( slabs 块)和 object(对象)。这三者构成了从“缓存管理”到“物理页框”再到“具体内存对象”的完整层级。理解它们之间的关系,以及缓存着色(Cache Coloring)的优化原理,是掌握 slab 分配器的关键。

2.1 三层数据结构概览

下图展示了 kmem_cache、slab 和 object 之间的层次关系与核心字段:

slab分配器三层数据结构关系图 kmem_cache、slab、object 三层结构关系 kmem_cache (缓存描述符) name, object_size, align, flags gfporder, num (每slab对象数) colour (着色范围), colour_off (着色偏移) slabs_full → slabs_partial → slabs_free slab (slab块) struct page *page (物理页框) void *s_mem (对象起始地址) unsigned long inuse (已分配对象数) struct list_head list (链表节点) object 0 空闲/已用 object 1 空闲/已用 object 2 空闲/已用 object N-1 空闲/已用 每个 kmem_cache 管理多个 slab;每个 slab 从连续物理页框分配,并划分为多个等大小的 object。 slabs_full / slabs_partial / slabs_free 三个链表管理 slab 的生命周期。

2.2 kmem_cache:缓存描述符

kmem_cache 是 slab 分配器中最上层的管理结构,每个 kmem_cache 对应一种特定大小的内存对象类型。内核通过 kmem_cache_create() 创建,通过 kmem_cache_destroy() 销毁。

字段 类型 说明
name const char * 缓存名称,用于 /proc/slabinfo 标识
object_size unsigned int 每个对象的大小(字节)
align unsigned int 对象对齐要求
flags unsigned long 创建标志(如 SLAB_HWCACHE_ALIGN)
gfporder unsigned int 每个 slab 占用的连续页框阶数(2^gfporder 页)
num unsigned int 每个 slab 可容纳的对象数量
colour unsigned int 缓存着色范围(着色偏移的个数)
colour_off unsigned int 每个着色偏移的步长(通常为 cache_line_size)
slabs_full struct list_head 所有对象已分配的 slab 链表
slabs_partial struct list_head 部分对象已分配的 slab 链表
slabs_free struct list_head 所有对象空闲的 slab 链表
关键设计:slab 分配器优先从 slabs_partial 链表中分配对象,其次从 slabs_free,最后才创建新的 slab。这种策略最大限度地减少了内存碎片和 slab 的创建/销毁开销。

2.3 slab:物理页框与对象容器

slab 是 slab 分配器的中间层,它由一个或多个连续的物理页框组成(由 gfporder 决定)。每个 slab 被划分为多个等大小的 object。slab 结构体通常存储在 slab 自身的末尾或起始处,以节省内存。

slab 的核心字段包括:

  • page:指向 slab 占用的第一个 struct page 描述符。
  • s_mem:slab 中第一个对象的起始虚拟地址。
  • inuse:当前已分配出去的对象数量。
  • free:指向下一个空闲对象的指针(用于构建空闲对象链表)。
  • list:用于将 slab 挂入 slabs_full、slabs_partial 或 slabs_free 链表。
性能提示:slab 分配器在分配和释放对象时,通过 inuse 计数和空闲链表实现 O(1) 时间复杂度,远优于通用伙伴分配器。

2.4 object:最小分配单元

object 是 slab 分配器最终返回给调用者的内存块。每个 object 的大小由 kmem_cache 的 object_size 决定,并且会进行对齐处理。object 之间通过空闲对象链表连接,空闲链表的头指针存储在 slab 的 free 字段中。

object 的状态转换如下:

  1. 空闲:object 在空闲链表中,可被分配。
  2. 已分配:object 被调用者使用,从空闲链表移除。
  3. 释放:调用者调用 kmem_cache_free(),object 重新加入空闲链表。

2.5 缓存着色(Cache Coloring)原理

缓存着色是 slab 分配器的一项重要优化技术,旨在解决CPU缓存行冲突(Cache Line Conflict)问题。当多个 slab 中的对象在物理内存中的偏移量相同时,它们会映射到 CPU 缓存的同一组缓存行中,导致频繁的缓存失效(Cache Miss)。

缓存着色的核心思想是:为不同的 slab 分配不同的起始偏移量(颜色),使得不同 slab 中的对象在物理内存中的位置错开,从而均匀分布在 CPU 缓存的不同组中。

缓存着色原理示意图 缓存着色(Cache Coloring)原理 无着色:缓存行冲突 slab 0: 对象起始偏移 = 0 slab 1: 对象起始偏移 = 0 slab 2: 对象起始偏移 = 0 → 所有 slab 对象映射到同一缓存组 → 缓存行频繁冲突,性能下降 有着色:均匀分布 slab 0: 起始偏移 = 0 (颜色 0) slab 1: 起始偏移 = colour_off (颜色 1) slab 2: 起始偏移 = 2 * colour_off (颜色 2) → 对象分散到不同缓存组 → 缓存利用率提升,冲突减少 缓存着色实现细节 colour_off 通常等于 L1/L2 缓存行大小(如 64 字节) colour 范围 = (slab 总大小 - 对象占用总大小) / colour_off 每个新 slab 依次选择下一个颜色值,循环使用

2.6 着色偏移的计算

缓存着色的实现涉及以下关键计算:

/* 内核源码 mm/slab.c 中的着色计算逻辑(简化) */
static int calculate_colour_offset(size_t object_size, size_t align,
                                   unsigned int gfporder)
{
    size_t slab_size = (PAGE_SIZE << gfporder);
    size_t colour_off = cache_line_size(); /* 通常为 64 字节 */
    size_t used_space = object_size * num_objects;
    size_t free_space = slab_size - used_space;

    /* colour 表示可用的着色偏移个数 */
    unsigned int colour = free_space / colour_off;

    return colour;
}

当创建一个新的 slab 时,slab 分配器会从 kmem_cache 的 colour 范围内选择一个颜色值,并计算该 slab 的起始偏移量:

/* slab 起始地址计算 */
void *slab_start = page_address(page);  /* 物理页框的虚拟地址 */
void *s_mem = slab_start + colour * colour_off;  /* 加上着色偏移 */
注意:缓存着色并非在所有场景下都有效。对于对象大小恰好等于缓存行大小整数倍的情况,着色效果最明显。如果 slab 中剩余空间不足以容纳一个完整的着色偏移,则 colour 为 0,着色被禁用。

2.7 三层结构的协同工作流程

以下表格总结了 slab 分配器在分配和释放对象时,三层数据结构之间的协作关系:

操作 kmem_cache 角色 slab 角色 object 角色
创建缓存 初始化 name、object_size、align、colour 等参数 尚未创建 尚未存在
分配对象 从 slabs_partial 或 slabs_free 链表获取 slab 提供空闲对象链表头,更新 inuse 计数 从空闲链表取出,返回给调用者
释放对象 根据对象地址找到所属 slab,更新链表状态 将对象重新加入空闲链表,更新 inuse 状态从“已分配”变为“空闲”
销毁 slab 从 slabs_free 链表移除 slab,释放物理页框 释放占用的连续页框 所有对象被销毁
总结:kmem_cache 是“工厂”,管理同类型对象的分配策略;slab 是“车间”,承载实际物理内存;object 是“产品”,是最终交付给驱动或内核子系统的内存单元。缓存着色通过微调每个 slab 的起始偏移,巧妙地避免了 CPU 缓存行冲突,是 slab 分配器在高性能场景下的关键优化手段。