sched-ext discussion
[PATCH v2] sched_ext: allow ops.cgroup_set_bandwidth() to be sleepable
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_alloc、kmalloc)。这迫使 BPF scheduler 必须为这个本可按需分配的回调预先 reserve 内存,浪费资源且不灵活。
补丁做两件事:
- 把
cgroup_set_bandwidth加入bpf_scx_check_member()的 sleepable 白名单,让 verifier放行。 - 引入
DEFINE_SCX_COMPAT_MARKER()宏并定义首个 BTF 兼容 markerscx_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:风险与注意点
- 预存在的 cgroup race(sashiko-bot标为 Medium):对
cpu.max/cpu.weight/cpu.idle的并发写操作在更新tg->scx缓存时没有 exclusive lock,会导致 BPF scheduler 状态静默且永久偏离 cgroup 状态。让本回调可睡眠是否扩大这条 race 的窗口,需要后续维护者跟进(Tejun Heo 已直接合入而未置评,需关注后续是否引发回归)。 - marker 的 ABI 契约:未来绝对不能改名、删除、重定义参数,否则 userspace 探测会失败或误判。
- 静态决策点:scheduler 必须在 load 时决定是否标
cgroup_set_bandwidth为 sleepable,回调内部无法动态切换。 - sleepable helper 边界:即便
bpf_mem_alloc等允许 sleepable,bpf_cpumask_*等 RCU-protected helper 仍受 verifier 限制,不能任意调用。 - 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 写路径上按需分配内存。