2. 核心数据结构:kmem_cache、slab、object 的关系与关键字段解析
Slab 分配器的核心在于三层数据结构的协同:kmem_cache(缓存描述符)、slab( slabs 链表节点)和 object(内存对象)。理解这三者的关系以及关键字段,是调优 Slab 分配器的基础。
核心关系速记: 一个
kmem_cache 管理多个 slab,每个 slab 被划分为多个等长的 object。分配时优先从 slab 的空闲链表中取出 object,释放时归还。
2.1 三层结构总览
下图展示了 kmem_cache、slab 和 object 之间的层次关系与核心指针流向。
2.2 kmem_cache:缓存描述符
kmem_cache 是 Slab 分配器的顶层结构,每个 kmem_cache 对应一种特定大小的对象类型。它负责管理所有属于该类型的 slab 实例。
| 字段名 | 类型 | 说明 | 调优关注点 |
|---|---|---|---|
name |
const char * |
缓存名称,如 "task_struct"、"inode_cache" |
通过 /proc/slabinfo 识别缓存 |
object_size |
unsigned int |
每个对象的大小(字节),包含对齐 | 决定 slab 能容纳的对象数量 |
gfporder |
unsigned int |
每个 slab 占用的连续页框阶数(2^gfporder 页) | 影响 slab 大小和内存碎片 |
num |
unsigned int |
每个 slab 中包含的对象数量 | 由 object_size 和 gfporder 计算得出 |
flags |
unsigned long |
缓存标志,如 SLAB_HWCACHE_ALIGN、SLAB_PANIC |
控制对齐行为和错误处理 |
slabs_full |
struct list_head |
所有对象都已分配的 slab 链表 | 分配时优先跳过 |
slabs_partial |
struct list_head |
部分对象已分配的 slab 链表 | 分配时优先从此链表查找 |
slabs_free |
struct list_head |
所有对象都空闲的 slab 链表 | 回收时优先释放回页分配器 |
调优提示: 通过
/sys/kernel/slab/<cache_name>/ 下的 sysfs 接口可以动态调整 gfporder 和 object_size 的对齐方式,从而改变 slab 的密度。
2.3 slab:物理页框容器
slab 结构体描述了一段连续的物理内存(由 gfporder 决定页数),它被划分为多个等长的 object。slab 可以处于三种状态之一:全满、部分满、全空。
| 字段名 | 类型 | 说明 | 分配/释放时的作用 |
|---|---|---|---|
s_mem |
void * |
slab 的起始虚拟地址(第一个 object 的地址) | 用于计算 object 的偏移量 |
free |
unsigned int |
下一个空闲 object 的索引(指向空闲链表头) | 分配时直接返回 s_mem + free * object_size |
inuse |
unsigned int |
当前已分配的对象数量 | 当 inuse == num 时 slab 移入 slabs_full |
list |
struct list_head |
用于链接到 kmem_cache 的三个链表之一 |
决定 slab 的归属状态 |
2.4 object:最小分配单元
object 是 Slab 分配器返回给调用者的内存块。每个 object 的大小由 kmem_cache 的 object_size 字段决定,并且会进行硬件缓存行对齐(如果设置了 SLAB_HWCACHE_ALIGN 标志)。
object 的分配与释放通过 空闲链表 实现,这是一个隐式链表,利用 object 自身的前几个字节存储下一个空闲 object 的索引。
// 分配 object 的核心逻辑(简化版)
void *slab_alloc(struct kmem_cache *cachep) {
struct slab *slabp;
void *object;
// 1. 优先从 slabs_partial 链表获取 slab
slabp = list_first_entry(&cachep->slabs_partial, struct slab, list);
if (!slabp) {
// 2. 如果 slabs_partial 为空,从 slabs_free 获取
slabp = list_first_entry(&cachep->slabs_free, struct slab, list);
if (!slabp) {
// 3. 如果都为空,分配新的 slab
slabp = kmem_cache_grow(cachep);
}
}
// 4. 从 slab 的空闲链表中取出 object
object = slabp->s_mem + slabp->free * cachep->object_size;
slabp->free = *(unsigned int *)object; // 更新空闲链表头
slabp->inuse++;
// 5. 如果 slab 已满,移入 slabs_full 链表
if (slabp->inuse == cachep->num) {
list_move(&slabp->list, &cachep->slabs_full);
}
return object;
}
2.5 三层结构的协同工作流程
- 初始化: 调用
kmem_cache_create()创建kmem_cache,指定object_size、gfporder等参数。 - 分配: 调用
kmem_cache_alloc(),从kmem_cache的slabs_partial或slabs_free链表中找到合适的slab,然后从该slab的空闲链表中取出一个object。 - 释放: 调用
kmem_cache_free(),将object归还到所属slab的空闲链表,并更新inuse计数。 - 回收: 当
slab的所有object都释放后(inuse == 0),该slab被移入slabs_free链表,并可能被回收给页分配器。
性能陷阱: 如果
slabs_partial 链表为空,分配器必须从 slabs_free 获取 slab,这会导致 slab 从全空变为部分满,增加内存占用。频繁的 kmem_cache_grow() 调用会引入页分配器的延迟。
2.6 关键字段对性能的影响
| 字段 | 值过大 | 值过小 | 最佳实践 |
|---|---|---|---|
gfporder |
slab 过大,容易导致内存碎片,分配大块连续内存失败率升高 | slab 过小,管理开销占比增大,slabs_partial 链表频繁变动 |
对于常用对象(如 task_struct),使用 gfporder=1(2页) |
object_size |
浪费内存,每个 slab 能容纳的对象数量减少 | 对象无法容纳必要数据,导致越界或需要额外分配 | 保持与硬件缓存行对齐(64字节或128字节) |
num |
slab 内对象过多,slabs_partial 状态持续时间长 |
slab 内对象过少,频繁触发 kmem_cache_grow() |
由 gfporder 和 object_size 自动计算,通常无需手动干预 |
2.7 数据结构关系总结
下图以流程图形式展示了分配一个 object 时,三层数据结构之间的交互路径。