3. slab 分配器核心数据结构

slab 分配器的核心数据结构构成了其管理内存的基础骨架。理解 struct kmem_cache、struct slab、struct page 和 kmem_list3 之间的关系,是掌握 slab 分配器工作原理的关键。这些数据结构协同工作,实现了高效的对象缓存与内存复用。

3.1 struct kmem_cache —— 缓存描述符

struct kmem_cache 是 slab 分配器中最上层的结构体,它描述了一个特定类型对象的缓存池。每个 kmem_cache 实例管理着多个 slab(内存块),这些 slab 中存储着相同大小的对象。

核心作用: 作为工厂,统一管理对象的大小、对齐方式、构造函数、析构函数以及所有 slab 的分配与回收。

该结构体的主要字段如下:

字段名 类型 描述
name const char * 缓存名称,用于 /proc/slabinfo 标识
object_size unsigned int 缓存中每个对象的大小(字节)
align unsigned int 对象对齐要求
num unsigned int 每个 slab 中可容纳的对象数量
gfporder unsigned int 每个 slab 占用的页面阶数(2^gfporder 页)
ctor void (*ctor)(void *) 对象构造函数指针
dtor void (*dtor)(void *) 对象析构函数指针
nodelists struct kmem_list3 ** 指向每个 NUMA 节点的 slab 链表数组
flags unsigned long 缓存标志(如 SLAB_HWCACHE_ALIGN)
// 内核源码中的简化定义 (mm/slab.c)
struct kmem_cache {
    struct array_cache *array[NR_CPUS];  // CPU 本地对象缓存
    unsigned int batchcount;             // 批量传输对象数量
    unsigned int limit;                  // CPU 缓存最大对象数
    unsigned int shared;                 // 共享缓存数量

    unsigned int size;                   // 对象实际占用大小(含对齐)
    unsigned int object_size;            // 原始对象大小
    unsigned int offset;                 // 空闲指针偏移

    struct kmem_list3 *nodelists[MAX_NUMNODES]; // NUMA 节点链表

    const char *name;
    int obj_size;                        // 对象大小(旧版兼容)
    int num;                             // 每个 slab 对象数
    int gfporder;                        // 页面分配阶数

    unsigned int flags;
    void (*ctor)(void *obj);
    void (*dtor)(void *obj);
};

3.2 struct slab —— slab 描述符

struct slab 是 slab 分配器的核心管理单元,它描述了一个连续的物理内存块(由一个或多个连续页面组成)及其内部对象的使用状态。每个 slab 都处于三种状态之一:满(full)、部分满(partial)或空(empty)。

关键理解: struct slab 并不直接包含对象数据,而是通过 s_mem 指针指向实际的内存区域。它更像是一个“管理头”,负责跟踪该内存块中哪些对象是空闲的。

主要字段解析:

字段名 类型 描述
list struct list_head 用于链接到 kmem_list3 的满/部分满/空闲链表
colouroff unsigned int 着色偏移量,用于缓存行对齐优化
s_mem void * 指向 slab 中第一个对象的起始地址
inuse unsigned int 当前已分配的对象数量
free unsigned int 空闲对象链表的索引(指向第一个空闲对象)
// 内核源码中的简化定义 (include/linux/slab_def.h)
struct slab {
    struct list_head list;      // 链表节点
    unsigned long colouroff;    // 着色偏移
    void *s_mem;                // 对象内存起始地址
    unsigned int inuse;         // 已使用对象数
    unsigned int free;          // 空闲对象链表头索引
    unsigned short nodeid;      // NUMA 节点 ID
};

3.3 struct page —— 底层页面描述符

在 slab 分配器中,struct page 承担了双重角色。它既是内核通用页面管理的基本单位,也被 slab 分配器复用,用于存储 slab 管理信息。当页面被分配给 slab 分配器时,struct page 中的某些字段会被重新解释。

注意: slab 分配器利用 struct page 的 lru 字段(在 slab 上下文中不再用于 LRU 链表)来存储指向 struct slab 或 struct kmem_cache 的指针,这是一种典型的内存复用技巧。

slab 分配器使用的 struct page 字段:

字段名 slab 上下文含义 说明
lru.next struct slab *slab 指向该页面所属的 struct slab 描述符
lru.prev struct kmem_cache *cache 指向该页面所属的 struct kmem_cache 缓存
counters 复合字段 包含 inuse、objects 和 frozen 等信息
mapping 着色偏移 用于缓存行着色优化

3.4 kmem_list3 —— NUMA 节点管理结构

kmem_list3 是 slab 分配器中用于管理每个 NUMA 节点上 slab 链表的关键结构。它维护了三个双向链表,分别对应 slab 的三种状态,并包含用于统计和同步的字段。

设计意图: 通过 kmem_list3,slab 分配器能够感知 NUMA 拓扑,优先从本地节点分配内存,减少跨节点访问延迟。

主要字段:

字段名 类型 描述
slabs_full struct list_head 所有对象都已分配的 slab 链表
slabs_partial struct list_head 部分对象已分配的 slab 链表
slabs_free struct list_head 所有对象都空闲的 slab 链表
free_objects unsigned int 该节点上空闲对象的总数
free_limit unsigned int 空闲对象数量的上限,超过则开始回收
colour_next unsigned int 下一个 slab 使用的着色值
spinlock_t list_lock spinlock_t 保护该节点 slab 链表的自旋锁
// 内核源码中的简化定义 (mm/slab.c)
struct kmem_list3 {
    struct list_head slabs_partial;  // 部分满 slab 链表
    struct list_head slabs_full;     // 满 slab 链表
    struct list_head slabs_free;     // 空闲 slab 链表

    unsigned long free_objects;      // 空闲对象总数
    unsigned int free_limit;         // 空闲对象上限
    unsigned int colour_next;        // 下一个着色值
    spinlock_t list_lock;            // 链表自旋锁
    int nodeid;                      // NUMA 节点 ID
};

3.5 数据结构关系总览

下图展示了 slab 分配器四大核心数据结构之间的协作关系:

slab 分配器核心数据结构关系图 struct kmem_cache 缓存描述符(管理特定大小对象) kmem_list3 (NUMA 节点) slabs_full 链表 slabs_partial 链表 slabs_free 链表 free_objects / free_limit kmem_list3 (NUMA 节点) slabs_full 链表 slabs_partial 链表 slabs_free 链表 free_objects / free_limit nodelists[0] nodelists[1] struct slab s_mem / inuse / free colouroff / list struct slab s_mem / inuse / free colouroff / list struct slab s_mem / inuse / free colouroff / list struct page lru.next → struct slab lru.prev → struct kmem_cache struct page lru.next → struct slab lru.prev → struct kmem_cache struct page lru.next → struct slab lru.prev → struct kmem_cache 物理内存页面(连续页面块) 对象 1 | 对象 2 | 对象 3 | ... | 对象 N(由 s_mem 指向)

3.6 数据结构协作流程

当内核请求分配一个对象时,上述数据结构的协作流程如下:

  1. 定位缓存: 根据对象大小找到对应的 struct kmem_cache。
  2. 选择节点: 通过 kmem_cache->nodelists[nodeid] 获取当前 CPU 所在 NUMA 节点的 kmem_list3。
  3. 查找部分满 slab: 优先从 kmem_list3->slabs_partial 链表中查找可用的 slab。
  4. 获取空闲对象: 通过 struct slab->free 索引找到空闲对象,并更新 inuse 计数。
  5. 页面映射: struct slab->s_mem 指向实际的物理内存起始地址,struct page 的 lru 字段反向关联到 struct slab 和 struct kmem_cache。
  6. 状态迁移: 如果 slab 变满,则从 slabs_partial 移动到 slabs_full;如果 slab 变空,则移动到 slabs_free 等待回收。
性能要点: 分配路径优先走 slabs_partial 链表,避免频繁创建新 slab。只有当所有部分满 slab 都耗尽时,才会从 slabs_free 中获取或分配新的物理页面。