2. Slab分配器核心概念:对象缓存、Slab、Cache、着色(Coloring)原理
Slab分配器是Linux内核中用于管理小对象(通常小于一页)的高效内存分配机制。它通过引入对象缓存、Slab、Cache和着色(Coloring)四个核心概念,解决了传统伙伴系统(Buddy System)在小对象分配时产生的内部碎片和性能瓶颈问题。本章将逐一剖析这些概念,并揭示它们如何协同工作以提升系统性能。
2.1 核心概念总览
在深入细节之前,我们先通过一张架构图建立整体认知。Slab分配器位于伙伴系统之上,为内核中频繁创建和销毁的对象(如task_struct、inode)提供高速缓存。
2.2 对象缓存(Object Cache)
对象缓存是Slab分配器对外暴露的接口。内核中每种需要频繁分配的数据结构(如struct file、struct dentry)都对应一个独立的缓存。缓存负责管理该类型对象的分配、回收和复用。
- 缓存创建: 通过
kmem_cache_create()创建,指定对象大小、对齐方式、构造函数等。 - 对象分配: 通过
kmem_cache_alloc()从缓存中获取一个空闲对象。 - 对象释放: 通过
kmem_cache_free()将对象归还给缓存,而不是直接释放给伙伴系统。 - 缓存销毁: 通过
kmem_cache_destroy()销毁缓存,并释放所有Slab占用的物理页。
2.3 Slab
Slab 是Slab分配器中最基本的物理内存单元。一个Slab由一个或多个连续的物理页组成,这些页被划分为多个相同大小的对象槽位。Slab是对象缓存和物理内存之间的桥梁。
2.3.1 Slab的三种状态
每个Slab在生命周期中会处于以下三种状态之一,并由Cache中的链表管理:
| 状态 | 描述 | 管理链表 | 分配策略 |
|---|---|---|---|
| Full | Slab中所有对象均已被分配,无空闲对象。 | slabs_full |
不参与分配,仅用于回收。 |
| Partial | Slab中部分对象已被分配,仍有空闲对象。 | slabs_partial |
优先从Partial Slab中分配对象。 |
| Empty | Slab中所有对象均为空闲,未被使用。 | slabs_empty |
当内存紧张时,Empty Slab可被回收给伙伴系统。 |
2.3.2 Slab的内部结构
一个典型的Slab包含以下部分:
- Slab头部(Slab Header): 存储管理信息,如对象数量、空闲对象链表指针、着色偏移量等。
- 对象数组: 连续排列的固定大小对象。
- 着色区域(Coloring Area): 用于调整对象起始地址的偏移量,以优化CPU缓存行对齐。
2.4 Cache(缓存描述符)
Cache 是Slab分配器的顶层管理结构,它描述了一类对象的分配行为。每个Cache包含以下关键信息:
- 对象大小(obj_size): 缓存管理的对象字节数。
- Slab大小(slab_size): 每个Slab占用的物理页数。
- 三个Slab链表:
slabs_full、slabs_partial、slabs_empty。 - 着色参数: 着色偏移量、着色范围、着色方向。
- 构造函数/析构函数: 用于对象初始化和清理的回调函数。
- 统计信息: 分配次数、释放次数、活跃对象数等。
slabs_partial链表。当内存回收时,Empty Slab会被销毁,物理页归还给伙伴系统。
2.5 着色(Coloring)原理
着色(Coloring) 是Slab分配器中最精妙的设计之一,其核心目标是优化CPU缓存(Cache Line)利用率,减少因对象起始地址对齐导致的缓存行冲突(Cache Line Conflict)。
2.5.1 问题背景
在默认情况下,Slab中的对象是连续排列的。如果所有Slab中的对象都从相同的页内偏移开始,那么不同Slab中相同索引的对象将映射到CPU缓存的同一组(Cache Set)中。当CPU频繁访问这些对象时,会导致缓存行颠簸(Cache Thrashing),即频繁的缓存行替换,降低性能。
2.5.2 着色机制
着色通过在Slab头部和第一个对象之间插入一个可变的偏移量(着色偏移),使得不同Slab中对象的起始地址在页内偏移不同。这个偏移量就是“颜色”。
- 着色范围: 从0到 (L1缓存行大小 - 1) 的整数倍。通常L1缓存行大小为64字节。
- 着色方向: 偏移量可以向前(在Slab头部后增加填充)或向后(减少Slab头部大小)。
- 着色分配: 每个新创建的Slab会从着色范围中选取一个尚未使用的颜色值,作为该Slab的着色偏移。
2.5.3 着色效果对比
| 场景 | 对象起始地址 | CPU缓存映射 | 性能影响 |
|---|---|---|---|
| 未着色 | 所有Slab中对象0均位于页内偏移0x80 | 映射到同一缓存组 | 高冲突,缓存命中率低 |
| 已着色 | Slab1对象0偏移0x80;Slab2对象0偏移0xC0 | 分散到不同缓存组 | 低冲突,缓存命中率高 |
2.6 着色参数计算示例
假设系统配置如下:
- L1缓存行大小:64字节
- 对象大小:128字节
- Slab大小:4096字节(1页)
- Slab头部大小:64字节
着色计算过程:
- 可用对象空间: 4096 - 64 (头部) = 4032字节
- 最大对象数量: floor(4032 / 128) = 31个
- 着色范围: 0 到 (64 - 1) = 63字节
- 着色步长: 通常为缓存行大小,即64字节
- 可用颜色数量: floor(4032 - 31*128) / 64 = floor(64 / 64) = 1种颜色
2.7 核心概念总结
下表总结了Slab分配器四个核心概念的作用和相互关系:
| 概念 | 作用 | 管理粒度 | 关键属性 |
|---|---|---|---|
| 对象缓存 | 对外接口,管理一类对象的分配与回收 | 对象类型 | 对象大小、构造函数、统计信息 |
| Slab | 物理内存单元,承载具体对象 | 物理页 | 状态(full/partial/empty)、着色偏移 |
| Cache | 组织和管理多个Slab | Slab集合 | 三个Slab链表、着色参数 |
| 着色 | 优化CPU缓存行利用率,减少冲突 | 字节偏移 | 着色范围、着色步长、颜色值 |