0/2 已展开

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.hscx_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___v2scx_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 头;真正的 ___v2 kfunc 在内核侧早已存在(Fixes: 860683763ebf "sched_ext: Add enq_flags to scx_bpf_dsq_move_to_local()"),所以只需要修 BPF 用户态兼容层。
  • scx_qmapneeds_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:风险与注意点

  1. 真正的修复点是用户态头:内核侧的 ___v2 kfunc 是对的,Fixes: 860683763ebf 引入的是上游版;v7.1 之后的 stable 用户都应该带上这个 compat 修正,否则 enq_flags 仍然被静默吞掉。
  2. 静默丢标志比直接报错更危险:宏没有 BUILD_BUG_ON、也没有运行时警告,bpf_ksym_exists() 返回 false 就会回退到旧路径,行为变化只发生在语义边缘(例如 SCX_ENQ_IMMED 不再生效),调试时极易被误判为调度策略问题。
  3. 应用版本与原文描述存在偏差:原文提到 scx_qmapneeds_immed() 影响,但 Tejun 应用时把它换成通用 impact statement,因为 for-7.2-fixes 分支里 needs_immed() 不存在——回看 patch 时应以 Tejun 应用版本为准。
  4. stable 回溯要求 v7.1+:因为 ___v2 是在 v7.1 引入的,更早的内核不应回溯这个 compat 修正,否则 extern 会找不到对应 kfunc。
  5. 同类风险可推广到其他 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。