Linux 内核 Slab 分配器核心组件详解
Slab 分配器是 Linux 内核中用于高效管理内存的机制,其核心目标是通过对象缓存减少内存碎片和分配/释放开销。以下详细解析其核心组件及其协作关系:
一、Slab 系统的核心组件
组件 描述 作用场景
Slab 描述符 每个 Slab 的管理结构(如 struct slab),记录页帧状态、空闲对象链表等。 跟踪 Slab 的元数据(如对象数量、空闲链表头、所属缓存等)。
Slab 节点 NUMA 架构下的内存节点(struct kmem_cache_node),管理本地 Slab 资源。 优化 NUMA 系统内存访问,减少跨节点访问延迟。
本地对象缓冲池 每 CPU 的缓存(struct kmem_cache_cpu),存放快速分配的空闲对象。 无锁快速分配对象,避免全局竞争。
共享对象缓冲池 节点级别的共享缓存池(struct kmem_cache_node 中的链表)。 当本地缓冲池耗尽时,从共享池批量获取对象或 Slab。
三个 Slab 链表 每个节点维护三个链表:slabs_full、slabs_partial、slabs_free。 按 Slab 的填充状态分类,优先从 slabs_partial 分配,释放时调整链表位置。
N 个 Slab 物理连续的页帧集合,划分为多个等大小的对象。 承载实际对象存储,一个 Slab 可分配多个对象。
Slab 缓存对象 每个 Slab 中划分出的内存块,用于存储特定类型的内核对象(如 inode)。 直接被内核代码分配和释放,避免频繁调用页分配器。
二、组件协作流程
1. 对象分配流程
步骤 1:尝试从 本地对象缓冲池(Per-CPU 缓存)获取空闲对象。
步骤 2:若本地池为空,从 共享对象缓冲池(节点 slabs_partial 链表)批量获取对象填充本地池。
步骤 3:若共享池耗尽,从 Slab 空闲链表(slabs_free)分配新 Slab,分割为对象后加入本地池。
步骤 4:若系统内存不足,触发页分配器(如 Buddy System)分配新页帧创建 Slab。
2. 对象释放流程
步骤 1:将对象归还到 本地对象缓冲池。
步骤 2:若本地池已满,将部分对象批量返还到 共享对象缓冲池(slabs_partial)。
步骤 3:若 Slab 中所有对象均释放,将 Slab 移至 slabs_free 链表,在内存紧张时释放回页分配器。
三、关键数据结构关系
struct kmem_cache
├── struct kmem_cache_cpu *cpu_slab // 每 CPU 的本地缓存
├── struct kmem_cache_node *node[NUM_NODES] // NUMA 节点共享缓存
│ ├── struct list_head slabs_full // 满 Slab 链表
│ ├── struct list_head slabs_partial // 部分满 Slab 链表
│ └── struct list_head slabs_free // 空闲 Slab 链表
└── unsigned int object_size // 对象大小
struct slab
├── struct list_head slab_list // 所属链表(full/partial/free)
├── void *freelist // 空闲对象链表头
├── unsigned int active_objects // 已分配对象数
└── unsigned int num_pages // 占用的页帧数
四、代码案例分析:Slab 分配器的内部操作
以下伪代码展示 Slab 分配器在对象分配时的核心逻辑:
// 分配对象
void *kmem_cache_alloc(struct kmem_cache *cache, gfp_t flags) {
struct kmem_cache_cpu *cpu_slab = get_cpu_ptr(cache->cpu_slab);
void *object;
// 1. 尝试从本地缓冲池获取对象
if (likely(cpu_slab->freelist)) {
object = cpu_slab->freelist;
cpu_slab->freelist = get_next_freepointer(object);
return object;
}
// 2. 本地池为空,从节点共享池填充
if (unlikely(!fill_local_cache(cpu_slab, cache, flags))) {
// 3. 共享池不足,分配新 Slab
struct slab *new_slab = allocate_slab(cache, flags);
if (!new_slab)
return NULL; // 内存不足
cpu_slab->freelist = new_slab->freelist;
cpu_slab->slab = new_slab;
object = cpu_slab->freelist;
cpu_slab->freelist = get_next_freepointer(object);
return object;
}
// ... 其他错误处理
}
// 填充本地缓冲池
static int fill_local_cache(struct kmem_cache_cpu *cpu_slab,
struct kmem_cache *cache,
gfp_t flags) {
struct kmem_cache_node *node = cache->node[cpu_to_node(smp_processor_id())];
struct slab *slab;
// 从节点的部分满链表获取 Slab
spin_lock(&node->list_lock);
slab = list_first_entry_or_null(&node->slabs_partial, struct slab, slab_list);
if (!slab) {
// 若无部分满 Slab,尝试从空闲链表分配
slab = list_first_entry_or_null(&node->slabs_free, struct slab, slab_list);
if (!slab) {
spin_unlock(&node->list_lock);
return 0; // 需要分配新 Slab
}
list_move(&slab->slab_list, &node->slabs_partial);
}
// 将 Slab 的空闲对象导入本地缓存
cpu_slab->freelist = slab->freelist;
slab->freelist = NULL;
slab->active_objects = cache->num_objects;
list_move(&slab->slab_list, &node->slabs_full);
spin_unlock(&node->list_lock);
cpu_slab->slab = slab;
return 1;
}
五、关键设计优势
减少内存碎片
Slab 按对象大小预分配内存块,避免频繁分割物理页,减少外部碎片。
提升分配速度
通过本地缓冲池(Per-CPU 无锁缓存)实现快速分配,避免全局锁竞争。
NUMA 优化
每个节点管理本地 Slab,减少跨节点内存访问带来的性能损耗。
内存重用
释放的对象归还到空闲链表,避免频繁调用页分配器,降低系统开销。
六、实际观察工具
查看 Slab 状态
cat /proc/slabinfo
输出示例:显示每个缓存的名称、对象数量、活动对象数、Slab 使用情况等。
内核调试工具
使用 systemtap 或 ftrace 跟踪特定缓存的对象分配/释放路径。