sched-ext discussion
[PATCH] sched_ext: allow ops.cgroup_set_bandwidth() to be sleepable
LLM 分析
sched_ext:放行 cgroup_set_bandwidth 为可阻塞回调
系列概况
- 标题:
[PATCH] sched_ext: allow ops.cgroup_set_bandwidth() to be sleepable - 作者:Changwoo Min changwoo@igalia.com(Igalia)
- 版本:v1(单封 patch,series 未标注版本号)
- 规模:3 个文件,+12 行 / -1 行
- 修改文件:
kernel/sched/ext/ext.c、kernel/sched/ext/ext.h、kernel/sched/ext/internal.h - Message-ID:
20260817170941.668571-1-changwoo@igalia.com - 完整性:完整——1 封 patch + 3 封回复,覆盖原作者、Sashiko AI 自动审阅、maintainer Tejun Heo 以及作者再回复
补丁目的
让 BPF scheduler 在为 sched_ext_ops.cgroup_set_bandwidth 注册程序时,可以把它声明为**可阻塞(sleepable)**的 BPF 程序,从而在 cgroup 的 cpu.max 被运行时改写的事件中按需申请内存,不再被迫提前预留。
同时新增一个 BTF marker 函数 scx_cgroup_set_bandwidth_may_sleep(),让 userspace(libbpf / scx 加载器)通过查询内核 BTF 来判断"这版内核已经允许 cgroup_set_bandwidth 睡了"。
旧流程的问题
tg_set_bandwidth() 在调用 scx_group_set_bandwidth() 时:
cpus_read_lock与cfs_constraints_mutex已被tg_set_cfs_bandwidth()释放;- 只持有
percpu_down_read(&scx_cgroup_ops_rwsem),读端可睡眠。
也就是说调用现场在睡眠语义上是安全的,但 BPF 验证器 bpf_scx_check_member() 没有把 cgroup_set_bandwidth 列入 sleepable allow-list,强制把它当非睡眠程序处理。结果:BPF scheduler 必须预先预留内存,不能在回调里调用 bpf_*_alloc 系列。
新流程
bpf_scx_check_member() 增加 case 放行 cgroup_set_bandwidth;internal.h 把文档改写为"This operation may block.";新增 BTF-only marker 函数让 userspace 通过符号是否存在来识别能力。
cgroup cpu.max write
|
v
tg_set_bandwidth() <-- process context
|
v
scx_group_set_bandwidth(tg, ...)
| (percpu_down_read held; sleepable)
v
ops.cgroup_set_bandwidth(...) <-- BPF program, sleepable
|
v
may call bpf_*_alloc() OK <-- now allowed
Patch 概览
kernel/sched/ext/ext.c:声明 marker 函数;在bpf_scx_check_member()给cgroup_set_bandwidth加 case。kernel/sched/ext/ext.h:导出 marker 函数原型。kernel/sched/ext/internal.h:把sched_ext_ops.cgroup_set_bandwidth的注释改成 "This operation may block."。
关键实现
A. 验证器侧的"放行"
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):
#endif
case offsetof(struct sched_ext_ops, cpu_online):
case offsetof(struct sched_ext_ops, cpu_offline):
+case offsetof(struct sched_ext_ops, cgroup_set_bandwidth):
放行之后,BPF verifier 允许挂在这个字段上的程序调用 bpf_kmalloc、bpf_local_storage_*_alloc 等可睡眠原语。
B. BTF capability marker
+__used void scx_cgroup_set_bandwidth_may_sleep(void) {}
这个函数纯粹作为 BTF 符号存在:userspace 查到它,就认为这版内核允许把它声明为 sleepable;查不到,就走旧的预分配路径。__used 用来阻止编译器优化丢掉。
C. 调用路径上的锁约束
pre : tg_set_cfs_bandwidth() 持有 cpus_read_lock + cfs_constraints_mutex
post: 上面两把锁都已释放
held : percpu_down_read(&scx_cgroup_ops_rwsem) <-- read-side, may sleep
=> 整条调用路径处于可睡眠上下文
类比
想象酒店礼宾台。
- 旧规则:客人临时办业务,礼宾台只能当场掏已经摆好的抽屉——预分配好的物料柜,用完就拒绝。
- 新规则:礼宾台后面打通了一扇门,可以打电话叫仓库开小推车把物料送过来;车在路上会堵车(sleep),那就让 BPF sleepable 走这条慢车道。
- 门口的告示牌:大堂挂了一块小牌子"礼宾台现可代叫仓库",这就是
scx_cgroup_set_bandwidth_may_sleep()BTF marker。客人看牌子就知道今晚能办,不必提前电话预约。 - 告示牌的脆弱性:牌子如果是大风一吹就收走的那种(只有
__used),那就只能靠编译器不吃掉它;想钉稳在墙上得用钉子——__retain。 - 牌子越来越多:将来会冒出"健身房现在 24h"、"泳池可以预约"等若干块,新规则应该统一前缀、集中挂在告示栏里,不再每层楼各贴各的。
Highlight:风险与注意点
__used不够:Tejun Heo 指出CONFIG_LD_DEAD_CODE_DATA_ELIMINATION=y时链接器会 GC 掉无 caller 的 section,仅__used保不住 BTF 中的符号;需要参考__bpf_kfunc加__retain。- 能力 marker 会累积:Tejun 建议统一前缀(如
scx_compat_marker_*),集中放在ext.c末尾靠近模块初始化代码的位置,避免全文件点缀。 - Sashiko AI 的 High 级已存问题:并发写
cpu.max时 CFS 与 SCX 带宽状态会 race、BPF 回调被乱序调用。本 patch 没去碰这条线,需要另外追补丁。 - 可睡眠回调的语义边界:allow-list 多开一条,就有新的信任面交到 BPF scheduler 作者手里——他们必须在 sleepable 程序里规避 RCU read-side、raw spinlock 等不适睡的临界区,否则会出 hard-to-debug 的栈。
- BTF-only ABI 的隐藏约定:marker 函数既无 caller 也无副作用,等价于把"ABI 信号"嵌进 BTF。任何对该 BTF 符号的重命名、删除都会破坏 userspace 检测,维护上要慎重。
- 文档同步:本 patch 修了
internal.h注释,但Documentation/scheduler/sched-ext.rst与 helpers(如scx_group_set_bandwidth上方注释)如果也描述"may block"会更一致,需要跟 review 确认。
版本变化
v1 -> v2(作者预告,尚未发出):
- 给 marker 函数补
__retain; - 重命名为
scx_compat_marker_*统一前缀; - 把所有 capability marker 集中在
ext.c末尾,靠近模块初始化代码位置; cpu.max并发 race 不在 v2 范围,作者计划另起一处处理。
一句话总结
本 v1 把 sched_ext_ops.cgroup_set_bandwidth 放行成 sleepable BPF 程序、靠 BTF marker 函数让 userspace 探测该能力,但 __used 在 CONFIG_LD_DEAD_CODE_DATA_ELIMINATION 下仍会被链接器 GC,v2 需要补 __retain 并把后续越来越多的 capability marker 统一成 scx_compat_marker_* 前缀、集中放在 ext.c 模块初始化附近。