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 分配器四大核心数据结构之间的协作关系:
3.6 数据结构协作流程
当内核请求分配一个对象时,上述数据结构的协作流程如下:
- 定位缓存: 根据对象大小找到对应的
struct kmem_cache。 - 选择节点: 通过
kmem_cache->nodelists[nodeid]获取当前 CPU 所在 NUMA 节点的kmem_list3。 - 查找部分满 slab: 优先从
kmem_list3->slabs_partial链表中查找可用的 slab。 - 获取空闲对象: 通过
struct slab->free索引找到空闲对象,并更新inuse计数。 - 页面映射:
struct slab->s_mem指向实际的物理内存起始地址,struct page的lru字段反向关联到struct slab和struct kmem_cache。 - 状态迁移: 如果 slab 变满,则从
slabs_partial移动到slabs_full;如果 slab 变空,则移动到slabs_free等待回收。
性能要点: 分配路径优先走
slabs_partial 链表,避免频繁创建新 slab。只有当所有部分满 slab 都耗尽时,才会从 slabs_free 中获取或分配新的物理页面。