3. Slab核心数据结构:kmem_cache、slab、object布局、着色与硬件缓存行对齐

Slab分配器是Linux内核中用于管理固定大小对象(如task_struct、inode)的高效内存分配机制。其核心设计围绕三个层次的数据结构展开:kmem_cache(缓存描述符)、slab(物理页帧容器)和object(实际分配对象)。本章深入剖析这三者的布局关系,并重点解释着色(coloring)技术如何与硬件缓存行对齐协同工作,以最大化CPU缓存命中率。

3.1 三层数据结构总览

Slab分配器采用“缓存→ slab → 对象”的三级架构。每个kmem_cache管理一组相同大小的对象,内部维护多个slab,每个slab占用一个或多个连续物理页帧,并在其上划分出等长的对象槽位。

关键设计原则:对象大小固定、无外部碎片、分配释放快速(O(1))、硬件缓存友好。
Slab三层数据结构关系图 kmem_cache name: "task_struct" object_size: 2176 B slab: partial/full/empty slab (partial) page: 0x1234 inuse: 5/8 free: 3 objects colouroff: 64 B slab (full) page: 0x5678 inuse: 8/8 free: 0 colouroff: 128 B 单个slab内部对象布局(物理页帧内) obj 0 obj 1 obj 2 obj 3 obj 4 obj 5 obj 6 obj 7 colouroff = 64 B (着色偏移,避免缓存行冲突) 64B cacheline 64B cacheline 64B cacheline

3.2 kmem_cache:缓存描述符

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

字段 类型 说明
name const char * 缓存名称,如"task_struct"、"mm_struct"
object_size unsigned int 每个对象的大小(字节),已对齐
align unsigned int 对象对齐要求,通常为硬件缓存行大小(64B)
size unsigned int 实际分配大小(含元数据、填充)
num unsigned int 每个slab可容纳的对象数量
gfporder unsigned int 每个slab占用的页阶(2^gfporder页)
colour unsigned int 着色范围(以缓存行为单位)
colour_off unsigned int 着色偏移步长(通常为缓存行大小)
slabp_cache struct kmem_cache * 用于分配slab描述符的缓存(大slab时独立分配)
partial struct list_head 部分空闲slab链表
full struct list_head 完全分配slab链表
工程要点:colour和colour_off是着色机制的核心参数。内核在创建缓存时根据对象大小和页帧大小计算着色范围,确保不同slab中相同索引的对象映射到不同的CPU缓存行。

3.3 slab描述符与对象布局

每个slab对应一个或多个连续物理页帧。slab描述符(struct slab)可以存储在页帧内部(小slab)或独立分配(大slab)。对象在slab内连续排列,对象之间可能因对齐要求存在填充字节。

/* 内核源码:mm/slab.h 中的 slab 结构(简化) */
struct slab {
    struct list_head slab_list;   /* 挂入 partial/full 链表 */
    unsigned long colouroff;      /* 当前slab的着色偏移(字节) */
    void *s_mem;                  /* 第一个对象的起始地址 */
    unsigned int inuse;           /* 已分配对象数 */
    unsigned int free;            /* 空闲对象数 */
    unsigned int active;          /* 活跃对象数(调试用) */
    struct kmem_cache *slab_cache; /* 所属缓存 */
    /* ... 其他字段 */
};

对象布局遵循以下规则:

  • 连续排列:对象在slab内从s_mem开始连续存放,间距为size(已对齐)。
  • 空闲链表:空闲对象通过自身前几个字节构成单向链表,指向下一个空闲对象索引。
  • 着色偏移:每个slab的colouroff不同,使得相同索引的对象在不同slab中起始地址不同。
两个slab的着色偏移对比(避免缓存行冲突) Slab A (colouroff=0) obj 0 obj 1 obj 2 obj 3 CL 0 CL 1 CL 2 CL 3 Slab B (colouroff=128 B) 着色偏移区 obj 0 obj 1 obj 2 CL 2 CL 3 CL 4 Slab B的obj 0起始于CL 2,与Slab A的obj 0(CL 0)不在同一缓存行,避免伪共享

3.4 着色(Coloring)技术原理

着色是slab分配器为减少CPU缓存行冲突而引入的优化技术。其核心思想是:让不同slab中相同索引的对象起始地址错开一定偏移量,避免它们映射到相同的CPU缓存行。

3.4.1 为什么需要着色?

假设两个slab的物理页帧起始地址相差恰好是缓存行大小的整数倍(如64B、128B),那么两个slab中相同偏移的对象会竞争同一缓存行。频繁访问这两个对象会导致缓存行颠簸(cache line bouncing),性能急剧下降。

3.4.2 着色计算过程

  1. 确定着色范围:colour = (slab_size - object_size * num) / colour_off,即slab内剩余空间能容纳多少个着色步长。
  2. 分配着色偏移:每个新创建的slab依次使用colouroff = (slab_index % colour) * colour_off。
  3. 调整对象起始地址:s_mem = slab_start + colouroff,对象从偏移后的位置开始排列。
参数 示例值 说明
slab_size 4096 B(4KB页) 单个slab占用的物理页帧总大小
object_size 256 B 每个对象大小(已对齐)
num 15 每个slab可容纳对象数(4096/256=16,预留1个对象空间给着色)
colour_off 64 B 着色步长,等于L1缓存行大小
colour 16 着色范围:(4096 - 256*15) / 64 = 16
colouroff 0, 64, 128, ..., 960 B 16种可能的着色偏移值
注意:着色偏移不能超过slab内剩余空间,否则对象会溢出到下一个页帧。内核通过colour参数严格限制偏移范围。

3.5 硬件缓存行对齐

现代CPU的L1/L2缓存行大小通常为64字节。slab分配器在对象对齐和着色偏移上都以缓存行为单位,确保:

  • 对象起始地址:按align(至少64B)对齐,避免对象跨缓存行。
  • 着色偏移:是colour_off(64B)的整数倍,保证偏移后的对象仍对齐缓存行。
  • 对象大小:向上取整到缓存行大小的倍数,防止对象尾部与下一个对象头部共享缓存行。
/* 内核源码:mm/slab.c 中的对齐与着色计算(简化) */
static int calculate_slab_order(struct kmem_cache *cachep,
                                size_t size, size_t align,
                                unsigned long flags)
{
    unsigned long offslab_limit;
    unsigned int gfporder;
    unsigned int num;
    size_t left_over;

    for (gfporder = 0; gfporder <= MAX_GFP_ORDER; gfporder++) {
        unsigned int num;
        size_t slab_size = PAGE_SIZE << gfporder;

        /* 计算每个slab可容纳的对象数 */
        num = (slab_size - sizeof(struct slab)) / (size + align);
        num = min(num, (slab_size - sizeof(struct slab)) / size);

        /* 计算剩余空间,用于着色 */
        left_over = slab_size - num * size;

        /* 设置着色参数 */
        cachep->colour = left_over / cachep->colour_off;
        cachep->colour_off = cache_line_size(); /* 64B */
        cachep->num = num;
        cachep->gfporder = gfporder;

        if (left_over * 2 <= slab_size)
            break; /* 剩余空间不超过一半,接受此阶 */
    }
    return 0;
}

3.6 着色与缓存行对齐的协同效果

着色与缓存行对齐共同作用,带来以下性能收益:

场景 无着色/无对齐 有着色+对齐 性能提升
多核访问相同对象索引 缓存行颠簸,大量cache miss 对象分散在不同缓存行,cache hit率高 3x ~ 10x
顺序遍历slab对象 跨缓存行访问,预取失效 对象对齐缓存行,硬件预取有效 1.5x ~ 2x
对象频繁分配释放 伪共享导致缓存一致性协议开销 不同slab对象隔离,伪共享消除 2x ~ 5x
着色与缓存行对齐协同效果示意图 无着色:缓存行冲突 Slab A obj 0 Slab B obj 0 Slab C obj 0 全部映射到同一缓存行 → 颠簸 有着色:缓存行分散 Slab A obj 0 Slab B obj 0 Slab C obj 0 分散到不同缓存行 → 无冲突 CPU缓存行映射 CL 0 CL 1 CL 2 CL 3 CL 4 CL 5 CL 6 CL 7 ← 上一章 📖 返回目录 下一章 →