0/3 已展开

LLM 分析

sched_ext:允许 ops.cgroup_set_bandwidth() 回调可睡眠

系列概况

  • 标题: [PATCH v2] sched_ext: allow ops.cgroup_set_bandwidth() to be sleepable
  • 作者: Changwoo Min changwoo@igalia.com
  • 版本: v2(单 patch形式提交)
  • 规模: 2 个文件,+36/-1 行
  • 修改文件: kernel/sched/ext/ext.c, kernel/sched/ext/internal.h
  • 代码统计: ext.c +14 行,internal.h +23/-1 行
  • Message-ID: 20260818160429.932265-1-changwoo@igalia.com
  • 完整性: 完整(1 个 patch + sashiko AI bot 自动评审回复 + Tejun Heo 的 Applied 回复)

补丁目的

本补丁解决 verifier 对 BPF scheduler 的能力限制:当 cgroup 的 cpu.max 被改写时,BPF scheduler 的 ops.cgroup_set_bandwidth() 回调明明运行在可睡眠上下文里,却被强制不能调用任何 sleepable helper(如 bpf_mem_allockmalloc。这迫使 BPF scheduler 必须为这个本可按需分配的回调预先 reserve 内存,浪费资源且不灵活。

补丁做两件事:

  1. cgroup_set_bandwidth 加入 bpf_scx_check_member() 的 sleepable 白名单,让 verifier放行。
  2. 引入 DEFINE_SCX_COMPAT_MARKER() 宏并定义首个 BTF 兼容 marker scx_compat_marker_cgroup_set_bandwidth_may_sleep,让 userspace loader 能通过 BTF 探测该能力。

旧流程的问题

调用链本来就可以睡眠(持 percpu_down_read(&scx_cgroup_ops_rwsem)),但 verifier 白名单漏掉这一项:

user writes cpu.max cgroup file
        |
        v
tg_set_bandwidth()        (process ctx, sleepable)
        |
        v
scx_group_set_bandwidth()
        |
        v
percpu_down_read(&scx_cgroup_ops_rwsem)   <-- read side may sleep
        |
        v
BPF ops.cgroup_set_bandwidth()
        |
        v
bpf_scx_check_member() -- rejects sleepable prog
        |
        v
BPF scheduler must pre-reserve; cannot allocate on demand

新流程

user writes cpu.max cgroup file
        |
        v
tg_set_bandwidth()        (process ctx, sleepable)
        |
        v
scx_group_set_bandwidth()
        |
        v
percpu_down_read(&scx_cgroup_ops_rwsem)   <-- read side may sleep
        |
        v
BPF ops.cgroup_set_bandwidth()  -- now allow-listed, may call
        | sleepable helpers
        v
bpf_mem_alloc / kmalloc / ... (on demand)
 |
        v
 userspace loader probes BTF for scx_compat_marker_cgroup_set_bandwidth_may_sleep
  -> decides at load time whether to mark sleepable

Patch 概览

v2 patch 集中在一个文件对的两处:

  • bpf_scx_check_member() 白名单追加一行。
  • 新增 DEFINE_SCX_COMPAT_MARKER() / DECLARE_SCX_COMPAT_MARKER() 宏。
  • ext.c 末尾靠近 __initcall(scx_init) 处定义首个 marker,用 #ifdef CONFIG_EXT_GROUP_SCHED 保护。
  • 更新 sched_ext_ops.cgroup_set_bandwidth 的 kdoc 注释为 "This operation may block."。

关键实现

1. verifier 白名单 (ext.c):

case offsetof(struct sched_ext_ops, cgroup_init):
case offsetof(struct sched_ext_ops, cgroup_exit):
case offsetof(struct sched_ext_ops, cgroup_prep_move):
+       case offsetof(struct sched_ext_ops, cgroup_set_bandwidth):
#endif
case offsetof(struct sched_ext_ops, cpu_online):
case offsetof(struct sched_ext_ops, cpu_offline):

2. BTF 兼容 marker 宏 (internal.h):

#define DECLARE_SCX_COMPAT_MARKER(func) \
        extern void scx_compat_marker_##func(void)

#define DEFINE_SCX_COMPAT_MARKER(func)                                       \
        DECLARE_SCX_COMPAT_MARKER(func);                                     \
        __used __retain void scx_compat_marker_##func(void) {}               \
        DECLARE_SCX_COMPAT_MARKER(func)

3. 首个 marker 的实例化 (ext.c 末尾):

#ifdef CONFIG_EXT_GROUP_SCHED
DEFINE_SCX_COMPAT_MARKER(cgroup_set_bandwidth_may_sleep);
#endif /* CONFIG_EXT_GROUP_SCHED */

为什么一定要 __used __retain

  • __used 阻止编译器认为"没人调用"而消除。
  • __retain 阻止在 CONFIG_LD_DEAD_CODE_DATA_ELIMINATION=y 下被链接器当死代码回收(参考 __bpf_kfunc)。
  • v1 漏了 __retain,这是 v2 修订的主要原因。

为什么需要 marker

verifier 白名单是 verifier 内部静态属性,没有符号可探测;BPF loader(如 libscx)必须在加载前知道内核是否支持 sleepable 的 cgroup_set_bandwidth。kernel 通过把一个空函数留在 BTF 里,让 userspace 通过 BTF 探测即可获知能力,命名统一用 scx_compat_marker_ 前缀,未来更多 capability 可集中追加。

类比

把 verifier 想成酒店前台:门卡(BPF 程序)要先在前台登记才能进哪些房间(可调用哪些 helper)。cgroup_set_bandwidth 这间客房本身是普通客房(cpu.max 写路径本身就是睡眠上下文),但前台登记表漏了它,于是哪怕持有"可睡眠门卡"的住客也没法带睡袋(allocator)进去。补丁就是把房间补登记进表,并在**大堂公告栏(BTF)**贴一张"本酒店现已支持 cgroup_set_bandwidth 房间接待睡眠客人"的标签。任何住客在登记门卡时(loader 加载 BPF),都可以从公告栏读到该标记,从而决定是否申请睡眠型门卡——这就是 marker 的契约意义。

Highlight:风险与注意点

  1. 预存在的 cgroup race(sashiko-bot标为 Medium):对 cpu.max / cpu.weight / cpu.idle 的并发写操作在更新 tg->scx 缓存时没有 exclusive lock,会导致 BPF scheduler 状态静默且永久偏离 cgroup 状态。让本回调可睡眠是否扩大这条 race 的窗口,需要后续维护者跟进(Tejun Heo 已直接合入而未置评,需关注后续是否引发回归)。
  2. marker 的 ABI 契约:未来绝对不能改名、删除、重定义参数,否则 userspace 探测会失败或误判。
  3. 静态决策点:scheduler 必须在 load 时决定是否标 cgroup_set_bandwidth 为 sleepable,回调内部无法动态切换。
  4. sleepable helper 边界:即便 bpf_mem_alloc 等允许 sleepable,bpf_cpumask_* 等 RCU-protected helper 仍受 verifier 限制,不能任意调用。
  5. markers 集中放置:所有 marker 集中放在 ext.c 末尾模块 init 代码附近,避免散落;后续 PR 增加 marker 时应遵守同一规范。

版本变化

v1 → v2

  • 给 capability marker 加上 __retain,避免 CONFIG_LD_DEAD_CODE_DATA_ELIMINATION 把空函数当死代码回收(参考 __bpf_kfunc)。
  • 抽出 DEFINE_SCX_COMPAT_MARKER() 宏,给 marker 统一 scx_compat_marker_ 前缀,集中放在 ext.c 末尾的模块 init 代码附近,方便未来追加。

一句话总结

ops.cgroup_set_bandwidth() 加入 verifier sleepable 白名单,并通过 __used __retain 保护的 BTF marker scx_compat_marker_cgroup_set_bandwidth_may_sleep 让 userspace loader探测该能力,从而允许 BPF scheduler 在 cpu.max 写路径上按需分配内存。