slab_fix()是 SLUB 调试里报告“已尝试修复”的函数,与slab_bug()类似但语义不同:它说明系统发现异常并自动做了修正,而不是单纯报错。
一、函数签名
__printf(2, 3) static void slab_fix(struct kmem_cache *s, const char *fmt, ...)
| 项 | 含义 |
|---|---|
__printf(2, 3) | 告诉编译器:第 2 个参数是格式串,第 3 个起是可变参数,便于编译期检查格式匹配 |
s | 出错的缓存 |
fmt | printf 风格格式串 |
... | 可变参数 |
二、__printf(2, 3)属性
__printf(2, 3)
这是 GCC/Clang 的
format属性封装,内核定义为__attribute__((format(printf, 2, 3)))。含义:函数第 2 个参数是 printf 格式串,第 3 个参数是可变参数的起始。
作用:编译期检查
fmt与后续参数类型是否匹配,比如:
slab_fix(s, "Fixed %d bytes", ptr); // 编译警告:%d 期望 int,给了指针
对
slab_bug()等同类函数也常加这个属性,只是你贴的这段里显式出现了。
三、struct va_format机制
struct va_format vaf; va_list args; va_start(args, fmt); vaf.fmt = fmt; vaf.va = &args; pr_err("FIX %s: %pV\n", s->name, &vaf); va_end(args);为什么用va_format而不是直接vprintk
C 语言里,...展开成va_list后,不能直接传给pr_err(pr_err是可变参数函数,不是v版本)。要输出va_list,通常有两条路:
用
vprintk(fmt, args)之类的v版本。把
va_list打包进struct va_format,再用%pV格式符在普通pr_err里输出。
slab_fix()选了第 2 种,因为想在格式串里额外加前缀("FIX %s: "),把缓存名和错误信息拼在一行:
struct va_format { const char *fmt; va_list *va; };| 字段 | 含义 |
|---|---|
fmt | 原始格式串 |
va | 指向va_list的指针 |
%pV是内核 printk 的扩展格式符,专门用于“打印一个struct va_format”,内部会展开vaf->fmt和vaf->va。
输出效果
slab_fix(s, "Fixed %d bytes", n);
输出:
FIX kmalloc-64: Fixed 8 bytes
pr_err("FIX %s: %pV\n", s->name, &vaf)中:
%s→s->name(缓存名)。%pV→ 展开vaf,即fmt+ 参数。前缀
"FIX "表明这是“修复”而非单纯报错。
四、KUnit 集成
if (slab_add_kunit_errors()) return;
与slab_bug()一样,KUnit 测试环境下静默计数、不打印,避免刷屏,让测试断言错误次数。
五、与slab_bug()的对比
| 维度 | slab_bug() | slab_fix() |
|---|---|---|
| 语义 | 报告错误 | 报告已修复 |
| 输出前缀 | BUG(在__slab_bug内) | FIX |
| 底层 | 转交__slab_bug() | 自己用pr_err+%pV |
| KUnit | 检查slab_add_kunit_errors() | 同 |
| 格式检查 | 通常也有__printf | 显式__printf(2, 3) |
| 可变参数处理 | va_start→__slab_bug | va_start→va_format→pr_err |
为什么slab_fix不转交__slab_bug
__slab_bug()的输出格式可能带分隔线、BUG前缀、dump_stack()等,适合“严重错误”。而“已修复”通常不是致命错误,只需一行日志说明修了什么,不需要调用栈和分隔线。所以slab_fix()自己用pr_err输出,更轻量。
六、使用场景
slab_fix()在 SLUB 调试里用于发现异常但能自动纠正的情况,典型如:
freelist 损坏但可修复:把损坏的 freepointer 重置。
inuse 计数不一致:重新计算并修正。
红区被轻微破坏:记录并恢复。
对象未正确初始化:补上初始化。
典型调用:
// 例如 freelist 上发现一个无效对象,跳过并修正 if (!check_valid_pointer(s, slab, p)) { slab_fix(s, "Freepointer corrupt, skipping"); // 修正 freelist }输出:
FIX kmalloc-64: Freepointer corrupt, skipping
与slab_bug()(报告致命错误)配合,形成“严重错误报 BUG,可修复问题报 FIX”的分工。
七、%pV与struct va_format的价值
统一可变参数输出:把
va_list包装后可在任意pr_*里用%pV输出。支持前缀拼接:
"FIX %s: %pV"把固定文本、缓存名、动态消息拼一行。内核标准做法:
printk家族的%pV专门为此设计,广泛用于dev_err、netdev_err等。
对比直接vprintk:
| 方式 | 优点 | 缺点 |
|---|---|---|
vprintk(fmt, args) | 直接 | 不能方便加前缀、拼接 |
struct va_format+%pV | 可拼接、可加前缀 | 多一个结构体 |
slab_fix()需要加"FIX %s: "前缀,所以用%pV更自然