0/5 已展开

LLM 分析

sched_ext:允许 ops.cgroup_set_weight/idle() 可睡眠

系列概况

  • 标题:[PATCH] sched_ext: Allow ops.cgroup_set_weight/idle() to be sleepable
  • 作者:Tao Cui cuitao@kylinos.cn
  • 版本:单封 PATCH(v1,无版本演进)
  • 规模:2 个文件,+23 / -4
  • 修改文件kernel/sched/ext/ext.ckernel/sched/ext/internal.h
  • 代码统计ext.c 新增 18 行;internal.h 净增 5 行、改 4 行
  • Message-ID20260825052336.46746-1-cui.tao@linux.dev
  • 完整性:完整,共 5 封邮件(1 patch + 4 reply,含 Sashiko AI 评审、Andrea Righi 反馈)

补丁目的

sched_ext 允许 BPF 调度器为每个 cgroup 注册 cgroup_set_weight() / cgroup_set_idle() 回调。当前 bpf_scx_check_member() 只把少数回调列入 sleepable 白名单,意味着 BPF 程序在这两个回调里不能调用任何可睡眠函数(如 kmallocmutex_lock)。

补丁把这两个回调加入白名单,并补充对应的 SCX_COMPAT_MARKER,使 BPF 用户态能够惰性分配、运行时按需构建 cgroup 相关的 DSQ / 数据结构。

旧流程的问题

bpf_scx_check_member() 的 switch 缺少 cgroup_set_weightcgroup_set_idle 两个 case:

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

白名单之外的字段被绑定到非 sleepable 的 BPF 子程序上下文,回调里一旦调用 kmalloc 或获取 mutex,就会被 verifier 拒绝并返回 -EINVAL。因此希望给每个 cgroup 维护独立 idle DSQ 的调度器只能在 cgroup_init()提前为全部 cgroup 创建 DSQ——一个 2000 个 cgroup 的 VM 就要常驻 2000+ 个 struct scx_dispatch_q 及对应 per-CPU 区。

新流程

补丁在 switch 的 #ifdef CONFIG_EXT_GROUP_SCHED 分支下追加:

case offsetof(struct sched_ext_ops, cgroup_set_weight):
case offsetof(struct sched_ext_ops, cgroup_set_bandwidth):
case offsetof(struct sched_ext_ops, cgroup_set_idle):

并在 __initcall(scx_init); 之后声明两个 compat marker:

#ifdef CONFIG_EXT_GROUP_SCHED
DEFINE_SCX_COMPAT_MARKER(cgroup_set_weight_may_sleep);
DEFINE_SCX_COMPAT_MARKER(cgroup_set_idle_may_sleep);
#endif

调度器由此可以在收到第一次 cpu.idle=1 写入时按需分配 DSQ;对一个只有 4 个 cgroup 进入 idle 的环境只产生 4 次分配,重复写入不再重复分配。

Patch 概览

单 patch 设计,三处改动:

  1. bpf_scx_check_member() switch 追加两个 case(白名单扩展)。
  2. ext.c 尾部追加两个 SCX_COMPAT_MARKER,让 BTF 暴露能力位(参照 cgroup_set_bandwidth_may_sleep 写法)。
  3. internal.h 注释更新为 "This operation may block",让阅读 API 头的 BPF 开发者明确这里允许分配。

关键实现

1. 白名单扩展

case offsetof(struct sched_ext_ops, cgroup_set_weight):
case offsetof(struct sched_ext_ops, cgroup_set_bandwidth):
case offsetof(struct sched_ext_ops, cgroup_set_idle):

调用端持有 percpu_down_read(&scx_cgroup_ops_rwsem),该读锁允许睡眠,所以调用点天然处于可睡眠上下文,把它列入白名单是合理的。

2. compat marker

DEFINE_SCX_COMPAT_MARKER(cgroup_set_weight_may_sleep);
DEFINE_SCX_COMPAT_MARKER(cgroup_set_idle_may_sleep);

通过 BTF 暴露的能力位让 lib/scx 等用户态库可以判断 "我的内核是否允许这里分配",避免老内核跑新 BPF prog 出现难诊断的 -EINVAL

3. 文档注释

* Update @cgrp's weight to @weight. This operation may block.
* Update @cgrp's idle state to @idle. This operation may block.

4. 调用关系

user writes cgroupfs cpu.weight / cpu.shares / cpu.idle
                  |
                  v  cgroupfs write handler (process context)
                  |
                  v
   scx_group_set_weight() / scx_group_set_idle()
                  |
    percpu_down_read(&scx_cgroup_ops_rwsem)  [may sleep]
                  |
                  v
   ops->cgroup_set_weight() / ops->cgroup_set_idle()  (BPF prog)
                  |
         now allowed: kmalloc / mutex_lock / bpf_spin_lock_*

5. 允许名单演进

allow-list BEFORE patch
+-------------------------------------------------------------+
| cgroup_init | cgroup_exit | cgroup_prep_move | cgroup_set_bw|
+-------------------------------------------------------------+
                          |
                          v  cgroup_set_weight / cgroup_set_idle: NOT sleepable
                     BPF kmalloc --> -EINVAL

allow-list AFTER patch
+---------------------------------------------------------------------+
| cgroup_init | cgroup_exit | cgroup_prep_move | cgroup_set_bw        |
| + cgroup_set_weight                                                |
| + cgroup_set_idle                                                  |
+---------------------------------------------------------------------+
                          |
                          v
                     BPF kmalloc OK --> DSQ lazily created

6. 评审分歧:并发 race

Process A: write cpu.weight=100        Process B: write cpu.weight=200
        |                                       |
        v                                       v
scx_group_set_weight(A)             scx_group_set_weight(B)
        |                                       |
        +------------- concurrent ---------------+
                          |
                          v
   core scheduler 通知 与 BPF 调度器 通知可能乱序到达
   --> state divergence / data race on SCX fields

类比

bpf_scx_check_member() 的白名单想成酒店房卡的楼层权限

  • 旧制度下,cgroup_set_weightcgroup_set_idle 这两张卡只能刷走廊灯,不能进储藏室拿物品(分配内存)。
  • 补丁之后,这两张卡升级到和 cgroup_set_bandwidth 同级,可以进储藏室取东西、用微波炉加热(mutex、sleepable API)。
  • compat marker 相当于房卡背面贴的 NFC 贴纸,客人(用户态 BPF lib)刷卡前就知道这台酒店的储藏室是否对自己开放。

2000 个 cgroup 的预分配就像租了 2000 个仓库只为其中 4 个要用的客人;按需分配后,只有真正来开门的 4 位客人各拿到一把钥匙。Andrea 提到的并发 race 则像两批客人同时刷卡开门——前台(core scheduler)和库房(BPF 调度器)各自记的进出顺序可能不一致,需要先在门禁系统上加一个排队闸机(race fix)才能彻底解决。

Highlight:风险与注意点

  1. 并发 cgroup 写入可能让 core 与 BPF 状态失序。Sashiko AI 给出 [High] 评,Andrea Righi 明确:"The serialization issue pointed out by sashiko is valid … We should probably address the race first. I may have a fix and will post it shortly"。sleepable 能力一旦开放,回调运行时间会更长,争用窗口也跟着拉大。
  2. 延迟分配 vs 失败可见性。按需分配是补丁主推的场景,但 BPF prog 不能假设分配一定成功;调度器作者仍需处理 -ENOMEM 并保证退到无 DSQ 的安全路径。
  3. BTF compat marker 的读取。两个新 marker 紧跟 cgroup_set_bandwidth_may_sleep 之后,BPF lib 必须先通过 bpf_obj_get_info 或 vmlinux BTF 检索 marker,再决定是否启用需要 sleepable 的特性。
  4. 文档同步internal.h 注释更新为 "may block",但若有人参考旧 commit 写用户文档,需要在 BPF prog 注释里同步提示。
  5. 后续补丁依赖。Andrea 暗示将先发 race fix;本补丁可能要等 race fix 落地后再合入或 rebase。

版本变化

v1(2026-08-25),无 vN→vN+1 演进记录。线程仍处初始评审阶段,作者尚未发 v2。

一句话总结

cgroup_set_weight/idle 列入 sched_ext sleepable 白名单让 BPF 调度器可以按需分配,但 Sashiko 与 Andrea 指出的并发写入 race 需要先修,建议等 race fix 发布后再合入。