0/2 已展开

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.ckernel/sched/ext/sub.c
  • 代码统计:纯文件内搬迁,插入行与删除行数量基本一致(差值 4 行为 #else /* !CONFIG_EXT_SUB_SCHED */__bpf_kfunc_start_defs();__bpf_kfunc_end_defs();#endif 这几行条件编译包裹)
  • Message-ID1f50a8edb9b37d10adb80552b08f8636@kernel.org097b036b0f12f4475ebe36183ff77278@kernel.org
  • 完整性:完整。首封 patch + 一封 maintainer 自回复(Applied 通知),没有其他评审意见

补丁目的

sched_ext 子系统里有一组“sub-cap” BPF kfunc(scx_bpf_sub_grantscx_bpf_sub_revokescx_bpf_sub_capsscx_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 里带来两个问题:

  1. 职责错位:桩和真实实现分处两个文件,修改一个 kfunc 的签名要在两个文件里同步改,违反“single source of truth”。
  2. 维护成本:未来给 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 */

注意两处细节:

  1. ext.c 端那一段对应的 __bpf_kfunc_end_defs(); 仍在原位(因为 BTF_KFUNCS_START(scx_kfunc_ids_any) 紧接其下),所以补丁只是把“sub-cap 桩”这几行从 ext.c 抠掉,没有动 ext.c 里其他 kfunc 的注册结构。
  2. 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:风险与注意点

  1. 桩签名同步:搬迁本身不修改签名,但如果以后改真实实现的签名,必须连同 sub.c#else 分支的桩一起改;这是最容易踩坑的地方——!CONFIG_EXT_SUB_SCHED 构建出的二进制不常被 CI 跑,bug 容易潜伏。
  2. __bpf_kfunc_start_defs() / end_defs() 配对:桩分支必须自己包一对 start/end_defs(),因为外层 sub.c#if 真分支已经消费了一对 end_defs;写错会导致 BTF 段里出现“未闭合的 kfunc”,pahole / verifier 行为未定义。
  3. build flavor 覆盖率:本改动不会触发 defconfig 上的差异,但建议 CI 同时跑 allnoconfig / !CONFIG_EXT_SUB_SCHED 配置以确认桩仍然能让 BPF 程序正常链接。
  4. 没有外部评审意见:本线程只有作者本人的 “Applied to sched_ext/for-7.3” 自回复,社区尚未对“是否真的 no functional change”做独立验证,但改动属于机械搬迁,风险极低。
  5. 未来工作:观察 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_SCHEDscx_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 |
   +-------------------------------------------+