sched-ext discussion
[PATCH] sched_ext: Fix scx_bpf_dsq_move_to_local___v2 compat detection
LLM 分析
sched_ext:修复 scx_bpf_dsq_move_to_local___v2 兼容检测
系列概况
- 标题:
[PATCH] sched_ext: Fix scx_bpf_dsq_move_to_local___v2 compat detection - 作者:Qiurong Fang
<fangqiurong@kylinos.cn> - 版本:v1(单 patch)
- 规模:1 个 patch,1 个文件,3 处插入 / 3 处删除
- 修改文件:
tools/sched_ext/include/scx/compat.bpf.h - 代码统计:6 行变更(
1 file changed, 3 insertions(+), 3 deletions(-)) - Message-ID:
20260811081143.578573-1-fangqiurong@kylinos.cn - 完整性:邮件包含完整 diff 与 commit message;后续 Tejun Heo 在
d828c3b8a074293f86bf423929c0e613@kernel.org中确认已合入sched_ext/for-7.2-fixes,并微调了 Fixes tag、stable 回溯说明与影响描述
补丁目的
修复 tools/sched_ext/include/scx/compat.bpf.h 中 scx_bpf_dsq_move_to_local(dsq_id, enq_flags) 宏的版本兼容逻辑。原代码用 ___v2 作为外部 kfunc 声明后缀,但 libbpf 在解析带 flavor 后缀的 extern 时会剥掉最后一个 ___flavor,导致 ___v2 被解析成无 flavor 的 scx_bpf_dsq_move_to_local,而该符号的 BTF proto 并不带 @enq_flags 参数。结果:bpf_ksym_exists(scx_bpf_dsq_move_to_local___v2) 在所有真正暴露 ___v2 kfunc 的内核上永远返回 false,宏静默回退到 ___v1,把 enq_flags(例如 SCX_ENQ_IMMED)丢掉。
旧流程的问题
caller: scx_qmap and other BPF progs
|
v macro expand
scx_bpf_dsq_move_to_local(dsq_id, enq_flags)
|
v
bpf_ksym_exists(scx_bpf_dsq_move_to_local___v2) ???
|
+---> false (always false: suffix parsed wrong)
|
v
fallback: scx_bpf_dsq_move_to_local___v1(dsq_id)
|
v
=== enq_flags is silently dropped ===
compat.bpf.h 同一文件里其他版本化 extern(scx_bpf_dsq_insert___v2、scx_bpf_reenqueue_local___v2)都遵循 ___<flavor>___compat 命名约定,唯独 ___v2 这一行漏了 ___compat 后缀,破坏了 libbpf 的解析约定。
新流程
declaration: bool scx_bpf_dsq_move_to_local___v2___compat(...)
|
v libbpf strips ___compat, resolves to ___v2 kfunc
bpf_ksym_exists(scx_bpf_dsq_move_to_local___v2___compat) ???
|
+---> true -> call ___v2(dsq_id, enq_flags)
+---> false -> call ___v1(dsq_id) # old kernel fallback
|
v
=== enq_flags correctly passed to ___v2 impl ===
关键实现
/* tools/sched_ext/include/scx/compat.bpf.h */
-bool scx_bpf_dsq_move_to_local___v2(u64 dsq_id, u64 enq_flags) __ksym __weak;
+bool scx_bpf_dsq_move_to_local___v2___compat(u64 dsq_id, u64 enq_flags) __ksym __weak;
#define scx_bpf_dsq_move_to_local(dsq_id, enq_flags) \
- (bpf_ksym_exists(scx_bpf_dsq_move_to_local___v2) ? \
- scx_bpf_dsq_move_to_local___v2((dsq_id), (enq_flags)) : \
+ (bpf_ksym_exists(scx_bpf_dsq_move_to_local___v2___compat) ? \
+ scx_bpf_dsq_move_to_local___v2___compat((dsq_id), (enq_flags)) : \
关键点:
- libbpf 的解析规则:遇到带
___<flavor>后缀的 extern,会去掉最后一个___flavor,再去 BTF 中查找真正的 kfunc。___v2单独一个 flavor 后缀本身会被剥掉,剩下来匹配到无 flavor 的scx_bpf_dsq_move_to_local,proto 不带enq_flags,于是 extern 永远命中不到___v2。同文件里其他___v2族成员用___<flavor>___compat双段后缀稳定锚定到 BTF 的___v2符号。本 patch 把缺失的___compat补齐。 - 修复完全集中在 compat 头;真正的
___v2kfunc 在内核侧早已存在(Fixes: 860683763ebf"sched_ext: Add enq_flags to scx_bpf_dsq_move_to_local()"),所以只需要修 BPF 用户态兼容层。 scx_qmap用needs_immed()作为enq_flags,在共享 DSQ 排空时如果没有SCX_ENQ_IMMED,就可能错失立即执行的语义——这是补丁描述里点名的具体影响面,但 Tejun 在应用时把这一段换成了通用描述,因为应用分支里needs_immed()不存在。
ASCII 流程图
compat.bpf.h macro resolution
----------------------------
scx_bpf_dsq_move_to_local(dsq_id, enq_flags)
|
v
+------- bpf_ksym_exists() check -------+
| |
| ___v2___compat extern |
| | |
| v libbpf strips ___compat |
| BTF has ___v2 ? |
| | | |
| yes no |
| | | |
| v v |
| call ___v2 call ___v1 |
| (dsq_id, (dsq_id) |
| enq_flags) |
| | | |
| enq_flags enq_flags |
| preserved dropped |
+---------------------------------------+
BEFORE: ___v2 extern --strip--> (no flavor) -> never matches
AFTER : ___v2___compat extern --strip--> ___v2 -> hits the kfunc
naming-convention consistency in this header
-------------------------------------------
scx_bpf_dsq_insert___v2___compat already has ___compat tail
scx_bpf_reenqueue_local___v2___compat already has ___compat tail
scx_bpf_dsq_move_to_local___v2 BEFORE: missing ___compat
scx_bpf_dsq_move_to_local___v2___compat AFTER : tail restored
类比
把它想成酒店前台分房:前台手里有两种房卡模板——___v1(旧卡,只登记房号)和 ___v2(新卡,房号 + 加急标记 enq_flags)。compat.bpf.h 宏就像"看哪张卡能用"的判断逻辑。原代码写"看有没有标着 ___v2 的抽屉",但前台实际抽屉标签的解析约定是"剥掉最后一段标签去查"——结果这把钥匙永远对不上新卡抽屉,前台误以为"新卡系统不存在",于是退回旧卡,加急标记就这样丢了。修法是把钥匙标签改成与同模块其他新卡一致的 ___v2___compat 双段约定,对上抽屉真实标签,新卡(含加急标记)就拿出来了。
Highlight:风险与注意点
- 真正的修复点是用户态头:内核侧的
___v2kfunc 是对的,Fixes: 860683763ebf引入的是上游版;v7.1 之后的 stable 用户都应该带上这个 compat 修正,否则enq_flags仍然被静默吞掉。 - 静默丢标志比直接报错更危险:宏没有
BUILD_BUG_ON、也没有运行时警告,bpf_ksym_exists()返回 false 就会回退到旧路径,行为变化只发生在语义边缘(例如SCX_ENQ_IMMED不再生效),调试时极易被误判为调度策略问题。 - 应用版本与原文描述存在偏差:原文提到
scx_qmap的needs_immed()影响,但 Tejun 应用时把它换成通用 impact statement,因为for-7.2-fixes分支里needs_immed()不存在——回看 patch 时应以 Tejun 应用版本为准。 - stable 回溯要求 v7.1+:因为
___v2是在 v7.1 引入的,更早的内核不应回溯这个 compat 修正,否则 extern 会找不到对应 kfunc。 - 同类风险可推广到其他 compat 头:本次问题再次证明 libbpf 的 flavor 后缀解析约定必须严格遵守,任何新加的版本化 extern 都应直接套
___<flavor>___compat模板,避免再出现"声明对了、解析错"的暗坑。
版本变化
单 patch 无 vN→vN+1 演进;Tejun 在 sched_ext/for-7.2-fixes 应用时做了三处微调:
- Fixes tag 由不存在的
e01a940b4813改为上游真实的860683763ebf; - 增加
Cc: stable@vger.kernel.org # v7.1+回溯标记; - 用通用 impact 描述替换
scx_qmap的具体needs_immed()段落。
一句话总结
compat 头里 scx_bpf_dsq_move_to_local___v2 漏写 ___compat 后缀,导致 libbpf 把 ksym 解析到不带 enq_flags 的旧 BTF 实体,bpf_ksym_exists() 永远 false,宏静默回退到 ___v1 并丢掉 enq_flags;补齐 ___v2___compat 双后缀即可让 BPF 程序正确命中真正支持 enq_flags 的内核 kfunc。