setup_slab_debug()是 SLUB 调试中初始化整个 slab 页内存为 poison 值的函数,在 slab 页刚分配、对象还没被初始化之前调用。
一、函数签名
static void setup_slab_debug(struct kmem_cache *s, struct slab *slab, void *addr)
| 参数 | 含义 |
|---|---|
s | 缓存 |
slab | 要初始化的 slab 页 |
addr | slab 页起始地址 |
无返回值。
二、典型实现
static void setup_slab_debug(struct kmem_cache *s, struct slab *slab, void *addr) { if (!kmem_cache_debug_flags(s, SLAB_POISON)) return; metadata_access_enable(); memset(kasan_reset_tag(addr), POISON_INUSE, slab_size(slab)); metadata_access_disable(); }逻辑:
门控:未开
SLAB_POISON直接返回。临时关闭 KASAN/KMSAN:
metadata_access_enable()。填满整页:把从
addr开始的slab_size(slab)字节填成POISON_INUSE。恢复检测:
metadata_access_disable()。
三、逐部分解析
门控条件
if (!kmem_cache_debug_flags(s, SLAB_POISON)) return;
只有开启
SLAB_POISON的缓存才做整页 poison。未开时零开销。
metadata_access_enable/disable
metadata_access_enable(); memset(...); metadata_access_disable();
memset的地址是 slab 页原始内存,在 KASAN/KMSAN 看来可能不属于任何合法对象(还没建立对象边界)。直接写入会触发 KASAN/KMSAN 误报。
用
metadata_access_enable()临时关闭检测,写入后再恢复。这是访问未建立对象边界的原始内存的标准做法。
kasan_reset_tag(addr)
memset(kasan_reset_tag(addr), POISON_INUSE, slab_size(slab));
去掉 KASAN 指针 tag,得到真实物理地址。
避免给带 tag 的指针做
memset导致写到错误地址。
slab_size(slab)
slab_size(slab)
返回该 slab 页的总字节数,即
PAGE_SIZE << oo_order(s->oo)。与前面
oo_order(s->oo)算页大小的思路一致,slab_size()是个封装。
POISON_INUSE
memset(..., POISON_INUSE, ...);
POISON_INUSE = 0x5a。表示“使用中”——但这里是初始状态,整页先填
0x5a。之后每个对象会被
setup_object_debug()→init_object()改成SLUB_RED_INACTIVE,再在分配/释放时改成POISON_INUSE/POISON_FREE。
四、与setup_object_debug()的关系
两者在 slab 页初始化时先后配合:
allocate_slab() │ ├─ setup_slab_debug(s, slab, addr) ← 整页先填 0x5a │ └─ memset(addr, POISON_INUSE, slab_size) │ └─ for_each_object(p, s, addr, objects) └─ setup_object_debug(s, p) ← 每个对象再精细初始化 ├─ init_object(s, p, SLUB_RED_INACTIVE) // 红区/数据区改 0xbb └─ init_tracking(s, p) // track 清零
| 层次 | 函数 | 填充 |
|---|---|---|
| 整页 | setup_slab_debug | POISON_INUSE(0x5a)全页 |
| 单对象 | setup_object_debug→init_object | SLUB_RED_INACTIVE(0xbb)红区/数据区 |
为什么先整页再逐对象:
setup_slab_debug快速把整页标记为“已使用 poison 填充”,覆盖伙伴系统残留数据。setup_object_debug再按对象布局精细设置红区、track。两层保证 slab 页内所有字节都处于已知状态,任何“读到非预期值”都能被检测。
五、与check_bytes_and_report()的呼应
setup_slab_debug写入的值,正是后续校验时check_bytes_and_report()检查的目标:
整页初始:
POISON_INUSE。对象红区/数据区:
SLUB_RED_INACTIVE或后续POISON_INUSE/POISON_FREE。校验时:发现不等于预期值 → 报告 +
restore_bytes()恢复。
写入端(setup_slab_debug/setup_object_debug)与校验端(check_object/slab_pad_check)形成闭环。
六、与init_object()的区别
| 函数 | 范围 | 值 | 时机 |
|---|---|---|---|
setup_slab_debug | 整页 | POISON_INUSE | slab 页刚分配 |
init_object | 单对象红区/数据区 | SLUB_RED_INACTIVE/POISON_INUSE/POISON_FREE | 对象初始化、分配、释放 |
setup_object_debug | 单对象(组合) | 调init_object+init_tracking | slab 页初始化时遍历对象 |
七、使用场景
setup_slab_debug()在allocate_slab()/new_slab()里被调用:
static struct slab *allocate_slab(...) { slab = ...; // 从伙伴系统拿页 // 调试初始化 setup_slab_debug(s, slab, slab_address(slab)); // 逐对象初始化 for_each_object(p, s, slab_address(slab), slab->objects) setup_object_debug(s, p); // 建立 freelist ... }保证新 slab 页在投入使用前,所有内存都处于已知 poison 状态。
八、与前面知识的呼应
| 前面讲过 | 这里体现 |
|---|---|
SLAB_POISON | 门控条件 |
POISON_INUSE | 填充值 |
metadata_access_enable/disable | 访问原始页内存时关闭 KASAN/KMSAN |
kasan_reset_tag() | 去 tag |
slab_size()/oo_order() | 计算页大小 |
setup_object_debug() | 逐对象精细初始化 |
init_object() | 对象级填充 |
check_bytes_and_report() | 对应的校验端 |
九、总结
setup_slab_debug(s, slab, addr)在开启SLAB_POISON时,用memset把整个 slab 页填成POISON_INUSE(0x5a),期间临时关闭 KASAN/KMSAN。