4. Slab 分配与释放流程:从 kmem_cache_alloc 到 slab 分配,以及 kmem_cache_free 的完整路径
Slab 分配器的核心价值在于为内核对象提供高速、低碎片化的内存管理。理解 kmem_cache_alloc 和 kmem_cache_free 的完整执行路径,是进行性能调优和问题定位的基础。本章将深入剖析从 API 调用到物理内存操作的每一层逻辑。
4.1 分配路径:kmem_cache_alloc 的逐层穿透
当内核模块调用 kmem_cache_alloc(cachep, flags) 时,分配器会按照“局部缓存 → 共享缓存 → 伙伴系统”的优先级顺序尝试获取对象。整个路径可抽象为以下三层结构:
核心原则: 分配路径优先使用 CPU 本地缓存(L1 cache),避免锁竞争;仅在本地缓存耗尽时,才向全局共享缓存或伙伴系统申请新的 slab 页。
4.2 分配路径关键数据结构
在分配路径中,有三个核心数据结构决定了性能表现:
| 数据结构 | 作用域 | 锁粒度 | 性能特征 |
|---|---|---|---|
| array_cache | Per-CPU | 无锁 / 本地中断屏蔽 | 最快,L1 cache 友好 |
| kmem_cache_node->shared | NUMA 节点 | 自旋锁 | 中等,批量转移减少锁竞争 |
| slab 描述符 + 伙伴系统 | 全局 | 全局锁 / 页分配锁 | 最慢,涉及页分配和内存清零 |
调优要点: 通过调整
batchcount 和 limit 参数,可以控制 array_cache 与共享缓存之间的传输粒度。增大 batchcount 可减少共享缓存访问次数,但会增加本地缓存占用。
4.3 释放路径:kmem_cache_free 的逆向操作
释放路径是分配路径的逆过程,同样遵循“先本地、后全局”的策略。当调用 kmem_cache_free(cachep, objp) 时,执行以下步骤:
- 检查对象所属 slab: 通过
virt_to_head_page或 slab 内部指针,找到对象所在的 slab 描述符。 - 尝试放回本地缓存: 如果当前 CPU 的 array_cache 未满(未达到
limit),直接将对象放入空闲链表,释放完成。 - 缓存溢出处理: 如果本地缓存已满,则将本地缓存中的一批对象(batchcount 个)批量转移回共享缓存或 slab 的空闲链表。
- Slab 回收判断: 如果 slab 中所有对象都已释放,且满足回收条件(如
slab_reclaim标志),则将该 slab 页面归还给伙伴系统。
4.4 关键代码路径解析
以下代码展示了分配路径中最核心的本地缓存快速路径。理解这段逻辑有助于定位性能瓶颈:
// mm/slub.c (简化版)
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags) {
struct kmem_cache_cpu *c;
void *object;
// 1. 获取当前 CPU 的本地缓存
c = raw_cpu_ptr(s->cpu_slab);
// 2. 尝试从本地空闲链表获取对象 (快速路径)
object = c->freelist;
if (likely(object)) {
c->freelist = get_freepointer(s, object);
c->pages--;
// 统计本地命中
this_cpu_inc(s->cpu_slab->local_hits);
return object;
}
// 3. 快速路径失败,进入慢速路径
return __slab_alloc(s, gfpflags, c);
}
static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags,
struct kmem_cache_cpu *c) {
// 4. 尝试从共享缓存 (node->shared) 批量转移
// 5. 如果共享缓存也空,则从伙伴系统分配新 slab 页
// 6. 初始化 slab 并建立空闲链表
// 7. 返回第一个对象
}
性能陷阱: 如果
kmem_cache_alloc 频繁进入 __slab_alloc 慢速路径,说明本地缓存容量不足或 batchcount 设置过小。此时应检查 /proc/slabinfo 中的 cpu_objs 和 limit 字段。
4.5 分配与释放路径对比总结
| 维度 | 分配路径 (alloc) | 释放路径 (free) |
|---|---|---|
| 快速路径 | 从 array_cache->freelist 取对象 | 放入 array_cache->freelist |
| 慢速路径触发条件 | 本地缓存为空 | 本地缓存已满 (达到 limit) |
| 慢速路径操作 | 从共享缓存或伙伴系统获取 slab | 批量转移对象到共享缓存或释放 slab |
| 锁竞争风险 | 低 (快速路径无锁) | 低 (快速路径无锁) |
| 主要开销 | 缓存未命中时的页分配 | 缓存满时的批量转移和可能的页释放 |
4.6 调优指标与监控方法
通过以下指标可以评估分配/释放路径的健康状况:
- 本地命中率 (local_hits / total_alloc): 理想值应 > 95%。低于此值说明本地缓存配置不足。
- Slab 页分配速率: 通过
/proc/vmstat中的slab_alloc和slab_free监控。速率突增可能暗示内存碎片或对象泄漏。 - 平均分配延迟: 使用 ftrace 或 perf 测量
kmem_cache_alloc的延迟分布。快速路径应在纳秒级,慢速路径可能达到微秒级。
实战命令:
# 查看 slab 分配统计信息cat /proc/slabinfo | grep -E "kmalloc-|task_struct"# 使用 perf 统计分配热点perf stat -e kmem:kmem_cache_alloc,kmem:kmem_cache_free -a sleep 5