背景

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 路径恢复