sched-ext discussion
[PATCH sched_ext/for-7.3] sched_ext: Move the config-off sub-cap kfunc stubs into sub.c
LLM 分析
sched_ext:把 config-off 的 sub-cap kfunc 桩搬进 sub.c
系列概况
- 标题:[PATCH sched_ext/for-7.3] sched_ext: Move the config-off sub-cap kfunc stubs into sub.c
- 作者:Tejun Heo tj@kernel.org
- 版本:单补丁,1/1,发送给
sched_ext/for-7.3分支(目标合并窗口 7.3) - 规模:2 files changed, 33 insertions(+), 29 deletions(-)
- 修改文件:
kernel/sched/ext/ext.c、kernel/sched/ext/sub.c - 代码统计:纯文件内搬迁,插入行与删除行数量基本一致(差值 4 行为
#else /* !CONFIG_EXT_SUB_SCHED */、__bpf_kfunc_start_defs();、__bpf_kfunc_end_defs();、#endif这几行条件编译包裹) - Message-ID:
1f50a8edb9b37d10adb80552b08f8636@kernel.org、097b036b0f12f4475ebe36183ff77278@kernel.org - 完整性:完整。首封 patch + 一封 maintainer 自回复(Applied 通知),没有其他评审意见
补丁目的
sched_ext 子系统里有一组“sub-cap” BPF kfunc(scx_bpf_sub_grant、scx_bpf_sub_revoke、scx_bpf_sub_caps、scx_bpf_sub_kill_bstr),它们的真实实现放在 kernel/sched/ext/sub.c 里,由 CONFIG_EXT_SUB_SCHED 这个 Kconfig 选项控制是否编译。当这个选项关闭时,BPF 调用约定要求仍然要有一组 -EOPNOTSUPP / 空函数 桩挂在 BTF_KFUNCS_START(scx_kfunc_ids_any) 列表里,以便 BPF 程序链接时不报“undefined kfunc”。原来的写法把这组桩放在 ext.c 的尾部,与 sub.c 隔着一层文件边界,读者要交叉两文件才能看懂“同一组 kfunc 在 config on/off 时的两份定义”。
本次补丁把这一组桩整体迁到 sub.c 里,靠近真实的实现,今后再加 sub-cap kfunc 时只需要改一个文件。Commit message 写明 “Pure code move, no functional change.”,属于纯代码组织 / cleanup。
旧流程的问题
ext.c 末尾出现一段 #ifndef CONFIG_EXT_SUB_SCHED 的桩实现:
#ifndef CONFIG_EXT_SUB_SCHED
__bpf_kfunc s32 scx_bpf_sub_grant(...) { return -EOPNOTSUPP; }
__bpf_kfunc void scx_bpf_sub_revoke(...) { }
__bpf_kfunc s32 scx_bpf_sub_caps(...) { return -EOPNOTSUPP; }
__bpf_kfunc s32 scx_bpf_sub_kill_bstr(...) { return -EOPNOTSUPP; }
#endif
这套桩实际上属于 sub.c 提供的 API 表面:当 CONFIG_EXT_SUB_SCHED 关闭时,sub.c 完全没有被编译进 vmlinux,BPF verifier 拿着 ELF 中的 kfunc 引用去 BTF 里查不到符号,因此必须有相同签名的桩挂载到 scx_kfunc_ids_any。把它们写在 ext.c 里带来两个问题:
- 职责错位:桩和真实实现分处两个文件,修改一个 kfunc 的签名要在两个文件里同步改,违反“single source of truth”。
- 维护成本:未来给 sub-cap 增加新 kfunc(或修改旧 kfunc 签名)时,很容易忘记在
ext.c端补桩,造成!CONFIG_EXT_SUB_SCHED构建下 BPF 程序链接失败,bug 出现在不常用 build flavor 里。
新流程
把桩整体搬到 sub.c 的 #else /* !CONFIG_EXT_SUB_SCHED */ 分支里:
#else /* !CONFIG_EXT_SUB_SCHED */
__bpf_kfunc_start_defs();
__bpf_kfunc s32 scx_bpf_sub_grant(...) { return -EOPNOTSUPP; }
__bpf_kfunc void scx_bpf_sub_revoke(...) { }
__bpf_kfunc s32 scx_bpf_sub_caps(...) { return -EOPNOTSUPP; }
__bpf_kfunc s32 scx_bpf_sub_kill_bstr(...){ return -EOPNOTSUPP; }
__bpf_kfunc_end_defs();
#endif /* CONFIG_EXT_SUB_SCHED */
注意两处细节:
ext.c端那一段对应的__bpf_kfunc_end_defs();仍在原位(因为BTF_KFUNCS_START(scx_kfunc_ids_any)紧接其下),所以补丁只是把“sub-cap 桩”这几行从ext.c抠掉,没有动ext.c里其他 kfunc 的注册结构。sub.c端__bpf_kfunc_start_defs()/__bpf_kfunc_end_defs()是新加的宏对,用来在桩分支里重新打开 BTF kfunc 段,使scx_kfunc_ids_any能够从sub.c的桩里收集到符号。
由于编译期 #ifdef 把 #else 分支互斥地选进 vmlinux,搬迁后任意一个 build flavor 看到的 kfunc 集合与之前完全一致 → “no functional change” 成立。
关键实现
下面是用文件视图来看搬迁前后桩的归属:
// 旧:ext.c 末尾
__bpf_kfunc_end_defs();
BTF_KFUNCS_START(scx_kfunc_ids_any)
...
#ifndef CONFIG_EXT_SUB_SCHED
/* 4 个 -EOPNOTSUPP / 空 stub */
#endif
// 新:ext.c 末尾
__bpf_kfunc_end_defs(); // 仍由 ext.c 持有
BTF_KFUNCS_START(scx_kfunc_ids_any)
...
// sub-cap 桩已搬走
// 新:sub.c 末尾
#if 0 /* CONFIG_EXT_SUB_SCHED 真分支保留 */
...
__bpf_kfunc_end_defs(); // 真实现的 end
#else /* !CONFIG_EXT_SUB_SCHED */
__bpf_kfunc_start_defs();
__bpf_kfunc s32 scx_bpf_sub_grant(...) { return -EOPNOTSUPP; }
__bpf_kfunc void scx_bpf_sub_revoke(...) { }
__bpf_kfunc s32 scx_bpf_sub_caps(...) { return -EOPNOTSUPP; }
__bpf_kfunc s32 scx_bpf_sub_kill_bstr(...){ return -EOPNOTSUPP; }
__bpf_kfunc_end_defs();
#endif /* CONFIG_EXT_SUB_SCHED */
__bpf_kfunc 系列宏(start_defs / end_defs)会展开成 BTF_TYPE_ID 标记 + 链接段 (.ksym) 符号导出,确保 pahole/bpftool 能枚举到这些桩。
类比
可以把它想成图书馆的“开馆/闭馆公告牌”:
- 真实实现
sub.c是图书馆正常开门时的展品目录(正常工作日)。 - 桩
EOPNOTSUPP是闭馆日贴出来的“今日闭馆,谢绝参观”告示。 - BPF verifier 是来查“哪些 kfunc 可调用”的访客;它每次都会走到告示牌前,但需要看到的“馆名 + 入口编号(签名)”必须和正常展品目录一致。
- 旧布局:闭馆告示贴在前台(
ext.c),展品目录贴在阅览室(sub.c)。前台员工每次改展品目录都得跑去阅览室确认是否要同步告示牌。 - 新布局:闭馆告示直接贴在阅览室门口,跟展品目录在一起,前台只管自己那一摊。改展品的人只要看一眼门口,就知道告示是否要同步。
Highlight:风险与注意点
- 桩签名同步:搬迁本身不修改签名,但如果以后改真实实现的签名,必须连同
sub.c里#else分支的桩一起改;这是最容易踩坑的地方——!CONFIG_EXT_SUB_SCHED构建出的二进制不常被 CI 跑,bug 容易潜伏。 __bpf_kfunc_start_defs()/end_defs()配对:桩分支必须自己包一对start/end_defs(),因为外层sub.c里#if真分支已经消费了一对 end_defs;写错会导致 BTF 段里出现“未闭合的 kfunc”,pahole/ verifier 行为未定义。- build flavor 覆盖率:本改动不会触发
defconfig上的差异,但建议 CI 同时跑allnoconfig/!CONFIG_EXT_SUB_SCHED配置以确认桩仍然能让 BPF 程序正常链接。 - 没有外部评审意见:本线程只有作者本人的 “Applied to sched_ext/for-7.3” 自回复,社区尚未对“是否真的 no functional change”做独立验证,但改动属于机械搬迁,风险极低。
- 未来工作:观察 upstream 是否会进一步把所有 sub 相关 kfunc 桩集中到
sub.c,甚至把scx_kfunc_ids_any的注册也按子系统分文件拆分。
版本变化
本线程只含单封 patch,没有 v1→v2 演进;Tejun 在第二封邮件中直接确认已合入 sched_ext/for-7.3 分支,没有要求 re-spin。
与其他相关 patch 系列的关联
sched_ext/for-7.3 是 Tejun 维护的 sched_ext 临时分支(指向 7.3 合入窗口),同期还有大量 sched_ext 的 sub-sched、tickless、idle 等子系列。本次补丁属于 kernel/sched/ext/ 内部的代码整理,与功能 patch 解耦,但为后续 sub-cap 扩展(例如新加 grant/revoke 操作)打好了“一个文件管到底”的基础。
一句话总结
把 !CONFIG_EXT_SUB_SCHED 下 scx_bpf_sub_* kfunc 的 -EOPNOTSUPP 桩从 ext.c 搬到 sub.c,让真实实现和桩集中在同一个文件,纯代码组织、无功能改动,已合入 sched_ext/for-7.3。
+----------------+ +---------------------+
| ext.c | | sub.c |
| | | |
| ... | | #if CONFIG_EXT_SUB |
| __bpf_kfunc_end| | real impls ... |
| BTF_KFUNCS_START|<--+ | __bpf_kfunc_end |
| ... | | | #else |
| #ifndef | | | __bpf_kfunc_start |
| 4 stubs -->|---+-->| 4 stubs |
| #endif | | __bpf_kfunc_end |
| | | #endif |
+----------------+ +---------------------+
| |
v v
+-------------------------------------------+
| BTF_KFUNCS_START(scx_kfunc_ids_any) |
| collects kfuncs from both #if branches |
+-------------------------------------------+