4. Slab分配器核心概念:Slab、Cache、Object、着色(Coloring)原理
Slab分配器是Linux内核中用于管理小对象(通常小于一页)的高效内存分配机制。它通过预分配和缓存常用数据结构,显著减少了外部碎片并提高了分配/释放性能。本章深入剖析其四大核心概念:Cache、Slab、Object 和 着色(Coloring)。
4.1 核心概念总览
在深入细节之前,先通过下表建立对这四个概念的宏观认知:
| 概念 | 英文 | 角色 | 类比(仓库管理) |
|---|---|---|---|
| Cache | kmem_cache | 对象工厂与仓库 | 专门存放“螺丝钉”的货架区 |
| Slab | slab | 物理内存容器 | 一个装满螺丝钉的纸板箱 |
| Object | object | 被管理的数据单元 | 一颗独立的螺丝钉 |
| 着色 | coloring | 缓存行对齐优化 | 在纸箱内调整螺丝钉的起始位置 |
4.2 Cache:对象工厂
Cache 是Slab分配器的顶层管理结构,在内核中由 struct kmem_cache 表示。每个Cache负责管理一种特定大小或类型的对象。例如,内核为 task_struct(进程描述符)和 inode(索引节点)分别创建了独立的Cache。
4.2.1 Cache的关键属性
- object_size:单个对象的大小(字节)。
- align:对象对齐要求(如8字节、16字节对齐)。
- flags:行为标志(如
SLAB_HWCACHE_ALIGN表示硬件缓存行对齐)。 - num:每个Slab中能容纳的对象数量。
- gfporder:每个Slab占用的连续物理页框数量(以2的幂次表示)。
关键洞察:Cache通过 kmem_cache_create() 创建,通过 kmem_cache_alloc() 分配对象。这种设计将特定类型对象的分配请求集中管理,避免了通用分配器(如伙伴系统)处理小对象时产生的内部碎片。
4.3 Slab:物理内存容器
Slab 是Cache中实际承载物理内存的单元。一个Slab由一个或多个连续的物理页框组成(通常为1页,即4KB)。Slab内部被划分为多个等长的Object。
4.3.1 Slab的三种状态
每个Cache维护三个Slab链表,用于快速定位可用内存:
| 链表名称 | 状态 | 分配策略 |
|---|---|---|
| slabs_full | 所有Object均已分配 | 不可用于分配,等待释放 |
| slabs_partial | 部分Object空闲 | 优先从此链表分配,减少碎片 |
| slabs_free | 所有Object均空闲 | 可被回收或重新填充 |
性能要点:分配器总是优先从 slabs_partial 链表中分配对象。只有当所有部分Slab都满了,才会从 slabs_free 中获取一个空Slab进行填充。这种策略最大限度地利用了已有内存,减少了向伙伴系统申请新页框的开销。
4.4 Object:被管理的数据单元
Object 是Slab分配器管理的最小单位,是内核实际使用的数据结构实例。每个Object的大小在Cache创建时固定。
4.4.1 Object的布局
在一个Slab内部,Object是连续排列的。每个Object之间可能存在填充字节以满足对齐要求。Slab的起始部分通常包含一个 描述符(descriptor),用于管理该Slab的元数据。
/* Slab内部布局示意 */
+-------------------+-------------------+-------------------+-----+
| Slab Descriptor | Object 0 | Object 1 | ... |
| (管理元数据) | (数据结构实例) | (数据结构实例) | |
+-------------------+-------------------+-------------------+-----+
| <--- 对齐填充 ---> | <--- object_size ---> | <--- object_size ---> |
4.4.2 空闲对象链表
Slab分配器使用一个空闲对象链表(free list)来追踪哪些Object是可用的。这个链表通常存储在Slab描述符中,或者直接嵌入在空闲Object本身内部(当Object足够大时)。
4.5 着色(Coloring)原理
着色(Coloring) 是Slab分配器中最精妙的设计之一,其核心目标是优化CPU缓存(Cache)利用率,减少缓存行颠簸(Cache Line Thrashing)。
4.5.1 问题背景
当多个Slab中的Object在物理内存中具有相同的偏移量时,它们会映射到CPU缓存的同一组(Cache Set)中。如果这些Object被频繁交替访问,会导致缓存行频繁换入换出,性能急剧下降。
4.5.2 着色如何工作
着色通过在Slab的起始位置引入一个可变的偏移量(称为颜色),使得不同Slab中Object的起始地址在缓存行级别上错开。这个偏移量是缓存行大小(通常为64字节)的整数倍。
/* 着色偏移示意 */
Slab 0: [偏移0] [Object 0] [Object 1] ...
Slab 1: [偏移64] [Object 0] [Object 1] ... (颜色=1)
Slab 2: [偏移128] [Object 0] [Object 1] ... (颜色=2)
...
4.5.3 着色与内存开销
着色虽然带来了性能提升,但也会引入一定的内存开销,因为每个Slab的可用Object数量可能会因为偏移而减少。内核在 color_max 的计算中平衡了性能与内存效率。
注意事项:着色优化仅在 SLAB_HWCACHE_ALIGN 标志被设置时生效。对于非性能关键的小对象,内核可能选择禁用着色以节省内存。着色偏移量存储在Slab描述符的 colouroff 字段中。
4.6 核心数据结构关系
以下代码展示了Slab分配器核心数据结构之间的引用关系:
/* 简化的内核数据结构关系 */
struct kmem_cache { // Cache
const char *name; // 名称,如 "task_struct"
unsigned int object_size; // 对象大小
unsigned int align; // 对齐要求
unsigned int num; // 每个Slab的对象数
unsigned int colour; // 着色偏移
struct list_head slabs_full; // 满Slab链表
struct list_head slabs_partial; // 部分Slab链表
struct list_head slabs_free; // 空Slab链表
};
struct slab { // Slab
struct list_head list; // 链表节点
unsigned long colouroff; // 着色偏移量
void *s_mem; // 第一个Object的起始地址
unsigned int inuse; // 已分配对象数
unsigned int free; // 空闲对象数
struct kmem_cache *cache; // 所属Cache
};
4.7 本章小结
- Cache 是对象类型的工厂,每种数据结构拥有独立的Cache。
- Slab 是物理内存容器,通过三个链表(满、部分、空)管理生命周期。
- Object 是分配的最小单元,通过空闲链表高效追踪。
- 着色 通过引入可变偏移量,将不同Slab的Object映射到不同的CPU缓存行,有效减少缓存行颠簸,是Slab分配器性能优化的关键手段。
实践要点:在分析内存碎片时,检查 /proc/slabinfo 可以查看每个Cache的Slab使用情况。如果某个Cache的 slabs_partial 数量远大于 slabs_full 和 slabs_free,说明该类型对象的内存使用较为分散,可能存在内部碎片问题。