背景
memcg slab shrink 会对每个 non-root memcg 各自调用已注册的 slab shrinker。superblock shrinker 通过 nr_cached_objects 钩子让文件系统回报可回收的缓存对象。此前的一次改动注意到,大多数这类钩子操作的是文件系统全局状态、并不响应 sc->memcg,于是在 per-memcg 迭代里调用它们只会白跑 percpu counter、排队重复的全局工作;为省这笔开销,它在 fs/super.c 引入 blanket 门控,只要这次回收不是全局(root)context,就一律跳过所有 nr_cached_objects 钩子。
这个 blanket 假设默认所有文件系统的钩子都不响应 memcg。但它对 XFS 是错的。
fs/super.c blanket 门控误伤 XFS
┌─────────────────────────────────────────┐
│ memcg slab shrink 对每个 non-root memcg │
│ 调用 slab shrinker │
└────────────────────┬────────────────────┘
▼
┌─────────────────────────────────────────┐
│ fs/super.c blanket 门控一律跳过所有 │
│ nr_cached_objects 钩子 │
└────────────────────┬────────────────────┘
▼
┌─────────────────────────────────────────┐ XFS 钩子有意从 per-memcg context 回
│ XFS 钩子也被跳过 │ 收 memcg-charged slab
└────────────────────┬────────────────────┘
▼
┌─────────────────────────────────────────┐
│ XFS 每 memcg inode 回收路径被 short- │
│ circuit │
└─────────────────────────────────────────┘
问题
- fs/super.c 的 blanket 门控假设所有钩子都不响应 memcg
- 该假设对 XFS 错误,XFS 钩子有意按 per-memcg 回收
- blanket 门控 short-circuit 了 XFS 每 memcg inode 回收
- XFS 的 memcg-charged slab 经此路径无法回收
方案
修复把门控从 fs/super.c 的统一拦截下放到各文件系统自己的回调:只有计数器确实与 per-memcg 回收无关的回调才在 per-memcg context 跳过,XFS 这种有意 per-memcg 的则放行。
门控下放至各文件系统回调
┌──────────────────────────────────────────────────────┐
│ 删 fs/super.c blanket 门控,直接调 nr_cached_objects │
└──────────────────────────┬───────────────────────────┘
┌────────────────────────────┴────────────────────────────┐
▼ ▼ ▼
┌───────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ btrfs 计数器全局, │ │ shmem shrinklist 全 │ │ XFS 回收按 memcg 驱 │
│ 不响应 memcg,非 root │ │ 局,不响应 memcg, │ │ 动,放行不跳过 │
│ 跳过 │ │ 非 root 跳过 │ │ │
└───────────────────────┘ └─────────────────────┘ └─────────────────────┘
删掉 blanket 门控,恢复逐钩子判断
fs/super.c 不再用统一谓词拦截,super_cache_scan 与 super_cache_count 直接调用 nr_cached_objects 钩子。判断这次是不是 per-memcg 调用、要不要跳过的责任,从 fs/super.c 转移到真正不响应 memcg 的回调自己。
给不响应 memcg 的回调各加门控
btrfs 的 nr_cached_objects(其 evictable extent map 计数器是文件系统全局的)和 shmem 的 unused huge count(per-superblock shrinklist 也是全局的)各自在开头检查:若这次不是全局或 root memcg 回收,就直接返回 0,不在 per-memcg 迭代里排队重复的全局工作。这些回调的计数器本就不随 sc->memcg 变化,门控放在它们这里语义最准,也保留了此前 blanket 门控对它们的效果。
XFS 不加门控,恢复 per-memcg 回收
XFS 的 inode 回收钩子有意从 per-memcg context 驱动,用来释放 memcg-charged 的 slab。删掉 fs/super.c 的 blanket 门控后,XFS 钩子在 per-memcg 调用时重新放行,被错误 short-circuit 的 XFS 每 memcg inode 回收路径得以恢复。
抽出公共判断 helper
为避免各文件系统回调各自 open-code sc->memcg 是否为 NULL 或 root 的判断,memcontrol.h 引入 mem_cgroup_shrink_is_root() 封装它。各文件系统以后若其计数器长出 memcg 感知,可独立解除门控,不必再动 fs/super.c。
收益
作者未提供性能数据,从代码逻辑推断的预期收益:
- XFS 的 memcg-charged slab 在 memcg 压力下重新可回收,恢复被错误 short-circuit 的回收路径
- memcg 的 pressure relief(slab shrink)对 XFS 重新生效
- 消除错误的 blanket short-circuit;btrfs 与 shmem 经下放门控保持原有跳过行为,仅 XFS 路径恢复