alloc_tagging_slab_alloc_hook()是内存分配追踪(alloc_tag)机制在 SLUB 分配路径上的外层钩子,负责用静态键快速判断“要不要追踪”,再决定是否调用真正的记录函数__alloc_tagging_slab_alloc_hook()。
一、函数签名
static inline void alloc_tagging_slab_alloc_hook(struct kmem_cache *s, void *object, gfp_t flags)
| 参数 | 含义 |
|---|---|
s | 缓存 |
object | 刚分配的对象 |
flags | 分配标志 |
无返回值。
二、典型实现
static inline void alloc_tagging_slab_alloc_hook(struct kmem_cache *s, void *object, gfp_t flags) { #ifdef CONFIG_MEM_ALLOC_PROFILING if (mem_alloc_profiling_enabled()) __alloc_tagging_slab_alloc_hook(s, object, flags); #endif }逻辑:
条件编译:只有
CONFIG_MEM_ALLOC_PROFILING开启时才有实际代码。静态键检查:
mem_alloc_profiling_enabled()读静态键,判断追踪是否运行时开启。调用内层:开启时调
__alloc_tagging_slab_alloc_hook()做实际记录。
三、为什么分成内外两层
这是内核里“静态键检查 + noinline 实际工作”的经典模式:
| 层次 | 函数 | 属性 | 职责 |
|---|---|---|---|
| 外层 | alloc_tagging_slab_alloc_hook | static inline | 静态键检查,极轻量 |
| 内层 | __alloc_tagging_slab_alloc_hook | static noinline | 实际记录,代码较大 |
外层必须内联
static inline void alloc_tagging_slab_alloc_hook(...) { if (mem_alloc_profiling_enabled()) __alloc_tagging_slab_alloc_hook(...); }调用点在
slab_post_alloc_hook()的循环里,每次分配都调用。内联后,编译器把静态键判断编成一条
nop或jmp(jump label 机制)。追踪关闭时(默认),这条判断几乎零开销,且不会产生函数调用。
若外层不内联,每次分配都要压栈调用,即使追踪关闭也付调用开销。
内层必须 noinline
static noinline void __alloc_tagging_slab_alloc_hook(...) { // 实际记录逻辑,代码较大 }实际记录涉及读写
slabobj_ext、codetag_ref等,代码体积较大。如果内联进
slab_post_alloc_hook(),会让热路径代码膨胀,影响 icache。noinline保证它作为独立函数存在,只在追踪开启时才被调用,平时不占热路径空间。
四、静态键mem_alloc_profiling_enabled()
if (mem_alloc_profiling_enabled())
mem_alloc_profiling_enabled()读一个static_key(类似前面的slub_debug_enabled、strict_numa)。追踪默认关闭,静态键为 false,判断被编译成
nop。运行时通过 sysfs 或启动参数开启后,内核用 jump label打补丁成
jmp,跳转到内层函数。关闭时零开销,开启时才有实际成本。
这是“编译期裁剪 + 运行时零开销开关”的又一例,与USE_LOCKLESS_FAST_PATH()、slub_debug_enabled一脉相承。
五、在分配流程中的位置
slab_post_alloc_hook(s, objcg, flags, size, p) │ ├─ memcg_slab_post_alloc_hook(...) // memcg 计费 │ ├─ for (i = 0; i < size; i++) │ ├─ prepare_slab_obj_exts_hook(s, flags, p[i]) // 准备扩展向量 │ └─ alloc_tagging_slab_alloc_hook(s, p[i], flags) // ← 本函数 │ └─ __alloc_tagging_slab_alloc_hook(...) // 记录分配点 │ └─ ...
prepare_slab_obj_exts_hook()先确保slabobj_ext向量存在。alloc_tagging_slab_alloc_hook()再把对象的codetag_ref指向当前分配点。
两者配合:先有存储(扩展向量),再写内容(分配标签)。
六、与__alloc_tagging_slab_alloc_hook()的分工
| 维度 | alloc_tagging_slab_alloc_hook | __alloc_tagging_slab_alloc_hook |
|---|---|---|
| 属性 | static inline | static noinline |
| 职责 | 静态键检查 | 实际记录 |
| 开销 | 追踪关闭时零开销 | 只在开启时执行 |
| 代码体积 | 极小,可内联 | 较大,独立函数 |
| 调用频率 | 每次分配 | 仅追踪开启时 |
七、与前面知识的呼应
| 前面讲过 | 这里体现 |
|---|---|
prepare_slab_obj_exts_hook() | 准备扩展向量 |
slabobj_ext/codetag_ref | 记录分配标签的存储 |
mem_alloc_profiling_enabled() | 静态键,控制追踪开关 |
static_key/ jump label | 关闭时零开销 |
slub_debug_enabled/strict_numa | 同类静态键机制 |
slab_post_alloc_hook() | 调用点 |
__alloc_tagging_slab_alloc_hook() | 内层实际工作函数 |
八、设计要点总结
条件编译:
CONFIG_MEM_ALLOC_PROFILING未开时函数为空。静态键门控:
mem_alloc_profiling_enabled()让关闭时零开销。内外分层:外层 inline 做轻量检查,内层 noinline 做实际记录。
热路径友好:默认关闭时几乎无成本,开启时才付代价。
与扩展向量配合:依赖
prepare_slab_obj_exts_hook()先准备好存储。
九、总结
alloc_tagging_slab_alloc_hook(s, object, flags)是 SLUB 分配路径上 alloc_tag 追踪的外层钩子:用static inline+ 静态键mem_alloc_profiling_enabled()做零开销判断,追踪开启时才调用noinline的__alloc_tagging_slab_alloc_hook()记录对象的分配点。它体现了内核“编译期裁剪 + 运行时零开销开关 + 内外分层控制代码体积”的典型设计。