背景

slab 分配器为每个对象维护一个 struct slabobj_ext,承载扩展信息:memcg 计账用的 objcg 指针,以及 allocation profiling 用的 codetag_ref。这些扩展信息按对象存在,数量与 slab 对象同阶,所以 slabobj_ext 的大小就是一笔不可忽视的每对象元数据开销。

问题在于,slabobj_ext 占多大、含哪些字段,完全由编译期 config 决定,而不是由这些字段在运行时是否真的被用决定。当 CONFIG_MEMCG 与 CONFIG_MEM_ALLOC_PROFILING 都打开时,每个 slabobj_ext 都会同时带上 objcg 与 codetag_ref 两个字段,哪怕 profiling 根本没在 boot 时启用、哪怕这个 cache 根本不做 memcg 计账。页分配器那一侧没有这种困扰,只有 slab 与 percpu 受影响。

slabobj_ext 大小只取决于 config 不取决于是否在用
┌──────────────────────────────────────────┐
│ slab 为每个对象维护 slabobj_ext 含 objcg │
│ 与 codetag_ref                           │
└────────────────────┬─────────────────────┘
                     ▼
┌──────────────────────────────────────────┐
│ 二者是否分配只由 config 决定与字段实     │
│ 际是否被用无关                           │
└────────────────────┬─────────────────────┘
                     ▼
┌──────────────────────────────────────────┐
│ profiling 未启用或 cache 不需要时字      │
│ 段闲置仍占内存                           │
└──────────────────────────────────────────┘

问题

  • slabobj_ext 的大小只取决于 config,不取决于字段是否实际被用。
  • profiling 配置启用但未 boot 启用时,codetag_ref 闲置仍占内存。
  • profiling 启用时,部分 cache 不需要 objcg 却也分配,浪费。
  • 这让 allocation profiling 不适合放进通用发行版 config。

方案

修复围绕一个核心:让 slabobj_ext 的字段按需占用,而不是 config 一开就全占。

struct slabobj_ext 被改成 union,使 objcg 与 codetag_ref 共用同一份空间,谁实际需要谁才占。

据此分情况省掉不该占的字段。当 allocation profiling 配置启用但未在 boot 时启用时,整个 codetag_ref 都不需要,于是在这种配置下不分配它(由 slab_obj_ext_has_codetag() 判定)。反过来,profiling 启用时有些 cache 根本不做 memcg 计账,对它们就不分配 objcg:像 KMALLOC_NORMAL 和明确标记 KMALLOC_NO_OBJ_EXT 的 cache,因为带 __GFP_ACCOUNT 的 kmalloc 会走 KMALLOC_CGROUP 类型(SLUB_TINY 除外),其余的能确定不需要 objcg。

真正棘手的是具名 kmem_cache。带 SLAB_ACCOUNT 创建的 cache 一定需要 objcg;但不带 SLAB_ACCOUNT 创建的 cache,其分配可能部分带 __GFP_ACCOUNT、部分不带,事先无法判断。为此引入一个内部标志 SLAB_MAY_ACCOUNT,应用到所有 cache(kmem 计账禁用的除外),只有 KMALLOC_NORMAL(除非它别名 KMALLOC_RECLAIM)和 KMALLOC_NO_OBJ_EXT 不加。这样不确定的 cache 先按「可能需要」保留 objcg 能力,后续可以把 SLAB_MAY_ACCOUNT 显式化,只对确知使用 __GFP_ACCOUNT 的 cache 生效。此外,kfence 对象不再保留 obj_ext。

slabobj_ext 改 union 按需占用字段
┌────────────────────────────────────────┐
│ struct slabobj_ext 改为 union 让 objcg │
│ 与 codetag_ref 按需占用                │
└───────────────────┬────────────────────┘
                    ▼
┌────────────────────────────────────────┐
│ profiling 禁用则省 codetag_ref 无需    │
│ objcg 的 cache 则省 objcg              │
└───────────────────┬────────────────────┘
                    ▼
┌────────────────────────────────────────┐
│ named kmem_cache 用 SLAB_MAY_ACCOUNT   │
│ 处理计账不确定性                       │
└────────────────────────────────────────┘

收益

作者未提供量化的内存节省数据,从机制推断的预期收益:

  • profiling 配置启用但未启用时,每个 slabobj_ext 不再为 codetag_ref 占用。
  • 不需要 objcg 的 cache(KMALLOC_NORMAL、KMALLOC_NO_OBJ_EXT)不再分配 objcg。
  • slabobj_ext 改 union 后按需占用字段,整体降低每对象的元数据开销。
  • 让 allocation profiling 更适合通用发行版 config,slab 侧不再有闲置字段浪费。
  • 回归检查:用 forward-port 自 slub_kunit 的微基准在 3 种 profiling 场景下测试,结果有噪声但无明显回归(仅全启用场景可能差几个百分点,预计真实负载不可见)。