2. slab分配器核心数据结构:kmem_cache、slab、object的关系,缓存着色(cache coloring)原理
slab分配器的核心设计围绕三个层次的数据结构展开:kmem_cache(缓存描述符)、slab( slabs 块)和 object(对象)。这三者构成了从“缓存管理”到“物理页框”再到“具体内存对象”的完整层级。理解它们之间的关系,以及缓存着色(Cache Coloring)的优化原理,是掌握 slab 分配器的关键。
2.1 三层数据结构概览
下图展示了 kmem_cache、slab 和 object 之间的层次关系与核心字段:
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 链表 |
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链表。
inuse 计数和空闲链表实现 O(1) 时间复杂度,远优于通用伙伴分配器。
2.4 object:最小分配单元
object 是 slab 分配器最终返回给调用者的内存块。每个 object 的大小由 kmem_cache 的 object_size 决定,并且会进行对齐处理。object 之间通过空闲对象链表连接,空闲链表的头指针存储在 slab 的 free 字段中。
object 的状态转换如下:
- 空闲:object 在空闲链表中,可被分配。
- 已分配:object 被调用者使用,从空闲链表移除。
- 释放:调用者调用
kmem_cache_free(),object 重新加入空闲链表。
2.5 缓存着色(Cache Coloring)原理
缓存着色是 slab 分配器的一项重要优化技术,旨在解决CPU缓存行冲突(Cache Line Conflict)问题。当多个 slab 中的对象在物理内存中的偏移量相同时,它们会映射到 CPU 缓存的同一组缓存行中,导致频繁的缓存失效(Cache Miss)。
缓存着色的核心思想是:为不同的 slab 分配不同的起始偏移量(颜色),使得不同 slab 中的对象在物理内存中的位置错开,从而均匀分布在 CPU 缓存的不同组中。
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; /* 加上着色偏移 */
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,释放物理页框 | 释放占用的连续页框 | 所有对象被销毁 |