1. Slab 分配器概述:内存管理核心机制
1.1 Slab 分配器在 Linux 内存管理中的角色
Linux 内核内存管理分为多个层级,Slab 分配器位于伙伴系统(Buddy System)之上,专门负责管理小块内存对象的分配与释放。它解决了伙伴系统以页(通常 4KB)为最小分配粒度带来的内部碎片问题,为内核中频繁创建和销毁的小型数据结构(如 task_struct、inode、dentry)提供高效的内存复用机制。
Slab 分配器在整个内存管理栈中的位置如下图所示:
1.2 与伙伴系统的关系
Slab 分配器与伙伴系统是“消费者-生产者”关系。伙伴系统负责管理物理页框的分配与回收,而 Slab 分配器在此基础上实现更细粒度的内存管理。两者在职责、粒度、性能特征上存在显著差异:
| 对比维度 | 伙伴系统(Buddy System) | Slab 分配器 |
|---|---|---|
| 最小分配单位 | 物理页(通常 4KB) | 任意大小对象(8B ~ 几 KB) |
| 分配粒度 | 2order 页(order=0~10) | 按对象大小对齐(cache_line 对齐) |
| 内部碎片 | 平均 50%(页内浪费) | 极低(对象大小精确匹配) |
| 分配速度 | O(log N) 合并/分裂 | O(1) 从空闲链表取对象 |
| 缓存亲和性 | 无对象级缓存管理 | 着色(coloring)优化 L1/L2 缓存 |
| 主要用途 | 大块内存、DMA、页表 | 内核数据结构、网络缓冲区、文件系统元数据 |
Slab 分配器向伙伴系统申请内存的流程如下:
- 缓存创建:调用
kmem_cache_create()时,Slab 分配器仅初始化元数据,不立即分配物理页。 - 首次分配:当第一个对象分配请求到达时,Slab 分配器调用
alloc_pages()从伙伴系统获取 2order 个连续物理页。 - 对象切分:将获取的物理页划分为多个等大小的对象,并初始化对象头部(如
kmem_bufctl_t链表)。 - 着色优化:对每个 Slab 内的对象起始地址进行偏移(着色),使不同 Slab 的对象落在不同的缓存行上,减少 CPU 缓存冲突。
- 对象回收:当 Slab 内所有对象都释放后,Slab 分配器将整组物理页归还给伙伴系统。
1.3 基本设计思想
Slab 分配器的设计围绕三个核心思想展开:对象缓存、状态复用和缓存着色。
1.3.1 对象缓存(Object Caching)
内核中许多数据结构(如 task_struct、socket、inode)被频繁创建和销毁。Slab 分配器为每种数据结构维护一个独立的缓存(kmem_cache),缓存中预分配了多个已初始化好的对象。分配时直接从缓存中取出一个“热”对象,释放时将其放回缓存,避免重复的构造/析构开销。
1.3.2 状态复用(State Reuse)
传统分配器在释放对象后,下次分配需要重新初始化。Slab 分配器通过 ctor(构造函数)和 dtor(析构函数)机制,在对象首次分配时执行一次初始化,后续分配直接复用已初始化的状态。对于不需要完整构造/析构的简单对象,Slab 分配器仅维护空闲链表,分配时从链表头部取出,释放时插入链表头部,实现 O(1) 操作。
1.3.3 缓存着色(Cache Coloring)
缓存着色是 Slab 分配器独有的优化技术。当多个 Slab 中的对象映射到同一 CPU 缓存组时,会产生缓存行冲突(cache line bouncing)。Slab 分配器通过在 Slab 起始地址上增加一个偏移量(着色偏移),使不同 Slab 的对象均匀分布在不同的缓存行上,显著降低缓存未命中率。
1.3.4 三种 Slab 状态
每个 kmem_cache 维护三个 Slab 链表,用于高效管理对象的分配与回收:
- slabs_full:所有对象都已分配出去的 Slab。当对象释放时,该 Slab 移入
slabs_partial链表。 - slabs_partial:部分对象已分配、部分空闲的 Slab。分配请求优先从该链表查找,避免创建新 Slab。
- slabs_free:所有对象都已释放的 Slab。当系统内存紧张时,这些 Slab 可以被回收并归还给伙伴系统。
slabs_free 链表过长,说明缓存中存在大量空闲 Slab 未被回收。可以通过调整 batchcount 和 limit 参数控制缓存回收的激进程度,避免内存浪费。
1.4 关键数据结构
Slab 分配器的核心数据结构包括 kmem_cache、slab 和 kmem_bufctl_t。以下是一个简化的结构定义:
// 每个 Slab 缓存描述符
struct kmem_cache {
unsigned int object_size; // 对象大小(含对齐)
unsigned int size; // 实际分配大小(含元数据)
unsigned int align; // 对齐要求
slab_flags_t flags; // 标志位(如 SLAB_HWCACHE_ALIGN)
unsigned int gfporder; // 每个 Slab 占用的页阶(2^gfporder 页)
unsigned int num; // 每个 Slab 包含的对象数
struct list_head slabs_full; // 全满 Slab 链表
struct list_head slabs_partial; // 部分空闲 Slab 链表
struct list_head slabs_free; // 全空 Slab 链表
unsigned int colour; // 着色范围(缓存行数)
unsigned int colour_off; // 着色偏移量(字节)
void (*ctor)(void *obj); // 构造函数
void (*dtor)(void *obj); // 析构函数
};
// 每个 Slab 描述符(位于 Slab 内部或单独分配)
struct slab {
struct list_head list; // 链表节点(链接到 kmem_cache 的链表)
unsigned long colouroff; // 当前 Slab 的着色偏移
void *s_mem; // 对象数据区起始地址
unsigned int inuse; // 已分配对象数
unsigned int free; // 空闲对象链表头索引
};