sched-ext discussion
[PATCH] sched_ext: Allow ops.cgroup_set_weight/idle() to be sleepable
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.c、kernel/sched/ext/internal.h - 代码统计:
ext.c新增 18 行;internal.h净增 5 行、改 4 行 - Message-ID:
20260825052336.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 程序在这两个回调里不能调用任何可睡眠函数(如 kmalloc、mutex_lock)。
补丁把这两个回调加入白名单,并补充对应的 SCX_COMPAT_MARKER,使 BPF 用户态能够惰性分配、运行时按需构建 cgroup 相关的 DSQ / 数据结构。
旧流程的问题
bpf_scx_check_member() 的 switch 缺少 cgroup_set_weight 和 cgroup_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 设计,三处改动:
bpf_scx_check_member()switch 追加两个 case(白名单扩展)。ext.c尾部追加两个SCX_COMPAT_MARKER,让 BTF 暴露能力位(参照cgroup_set_bandwidth_may_sleep写法)。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_weight和cgroup_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:风险与注意点
- 并发 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 能力一旦开放,回调运行时间会更长,争用窗口也跟着拉大。 - 延迟分配 vs 失败可见性。按需分配是补丁主推的场景,但 BPF prog 不能假设分配一定成功;调度器作者仍需处理
-ENOMEM并保证退到无 DSQ 的安全路径。 - BTF compat marker 的读取。两个新 marker 紧跟
cgroup_set_bandwidth_may_sleep之后,BPF lib 必须先通过bpf_obj_get_info或 vmlinux BTF 检索 marker,再决定是否启用需要 sleepable 的特性。 - 文档同步。
internal.h注释更新为 "may block",但若有人参考旧 commit 写用户文档,需要在 BPF prog 注释里同步提示。 - 后续补丁依赖。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 发布后再合入。