0/2 已展开

LLM 分析

tools/sched_ext:无 CONFIG_FUNCTION_TRACER 时跳过 scx_lib_init_probe

系列概况

  • 标题:[PATCH sched_ext/for-7.4] tools/sched_ext: Skip scx_lib_init_probe() without CONFIG_FUNCTION_TRACER
  • 作者:Cheng-Yang Chou yphbchou0911@gmail.com
  • 版本 / 目标分支:单封 patch,目标分支 sched_ext/for-7.4,无独立版本号
  • 规模:1 file changed, 15 insertions(+), 1 deletion(-)
  • 修改文件tools/sched_ext/include/scx/compat.h
  • Message-ID20260824155051.20841-1-yphbchou0911@gmail.com
  • 完整性:单封 patch + 一封 AI(Sashiko)审阅回复,无 maintainer 跟帖
  • 关联来源:与 GitHub sched-ext/scx#3758 的修复互相同步

补丁目的

解决"内核没有打开 CONFIG_FUNCTION_TRACER 时,所有 sched_ext 用户态调度器加载就一起失败"的问题。

scx_lib_init_probe 是每个调度器都会通过 SCX_OPS_LOAD() / SCX_OPS_ATTACH() 自动挂上的 fentry 探针。当内核关掉 function tracer 时,fentry 没法 attach,而 skel__attach() 是 all-or-nothing 语义:只要这一个探针挂不上,整个 BPF skeleton 就一起回滚,调度器根本起不来。补丁在用户态侧加一层"是否能用 fentry"的兼容性兜底,让没有 fentry 支持的内核也能正常加载调度器。

旧流程的问题

SCX_OPS_LOAD() 宏里无条件地用 fentry 挂 scx_lib_init_probe,没有可用的运行时检测:

  • CONFIG_FUNCTION_TRACER=n 的 vng 复现环境里,attach 阶段直接返回失败;
  • skel__attach() 整体回滚,连带整套调度器都无法 attach 到 struct_ops;
  • 用户层面表现为"换一个 BPF skeleton 就直接崩溃或拒绝运行",并不是单个调度器的实现 bug,而是 tools/ 层的兼容性 bug。

新流程

引入运行时检测,按可用性分支处理:

  1. /proc/sys/kernel/ftrace_enabled 这个 sysfs 文件的"存在性"作为 CONFIG_FUNCTION_TRACER 是否开启的廉价代理(只在编译进内核时才存在);
  2. 不可用时立刻 bpf_program__set_autoload(scx_lib_init_probe, false),让 libbpf 跳过它;
  3. attach 后的 struct_ops 关联循环里,把判定从"只看类型"放宽为"struct_ops 类型,或者 autoload 未关闭",确保已经禁用的程序不会再被强行关联到一个根本没有 FD 的程序。

Patch 概览

单封 patch,无 patch 系列结构。改动集中在 tools/sched_ext/include/scx/compat.h

  • 新增内联函数 __COMPAT_function_tracer_available()
  • SCX_OPS_LOAD 宏开头插入 autoload 关闭分支;
  • 宏尾部 bpf_object__for_each_program 循环的关联条件增加"autoload 已关闭就跳过"。

关键实现

/*
 * Whether the kernel supports function tracing (CONFIG_FUNCTION_TRACER),
 * needed for fentry/fexit BPF programs to load or attach.
 * /proc/sys/kernel/ftrace_enabled only exists when it's compiled in,
 * so its presence is a cheap proxy.
 */
static inline bool __COMPAT_function_tracer_available(void)
{
    return access("/proc/sys/kernel/ftrace_enabled", F_OK) == 0;
}

SCX_OPS_LOAD() 关键片段:

#define SCX_OPS_LOAD(__skel, __ops_name, __scx_name, __uei_name) ({ \
    struct bpf_program *__prog;                                                \
    UEI_SET_SIZE(__skel, __ops_name, __uei_name);                              \
    if (!__COMPAT_function_tracer_available())                                \
        bpf_program__set_autoload((__skel)->progs.scx_lib_init_probe, false);  \
    SCX_BUG_ON(__scx_name##__load((__skel)), "Failed to load skel");           \
    bpf_object__for_each_program(__prog, (__skel)->obj) {                       \
        if (bpf_program__type(__prog) == BPF_PROG_TYPE_STRUCT_OPS ||           \
            !bpf_program__autoload(__prog))                                    \
            continue;                                                          \
        __scx_ops_assoc_prog(__prog, (__skel)->maps.__ops_name, #__ops_name);  \
    }                                                                          \
})

两处改动的含义:

  • set_autoload(false) 让 libbpf 跳过 fentry 程序生成,等价于"这个探针这次不出席";
  • !bpf_program__autoload(__prog) 让循环不要去碰那些本来就不打算加载的程序,避免出现"无 FD 程序被强行塞进 ops map"的非法关联。

整体流程如下:

Old flow (CONFIG_FUNCTION_TRACER=n)
+--------------------------------------+
| SCX_OPS_LOAD()                       |
|   call fentry attach                 |
|   scx_lib_init_probe                 |
+--------------------------------------+
                  |
                  v
+--------------------------------------+
| attach fails                         |
+--------------------------------------+
                  |
                  v
+--------------------------------------+
| skel__attach() all-or-nothing        |
| rollback                             |
+--------------------------------------+
                  |
                  v
+--------------------------------------+
| whole scheduler fails to load        |
+--------------------------------------+New flow
+---------------------------------------------+
| SCX_OPS_LOAD()                              |
|   check __COMPAT_function_tracer_available()|
| false: set_autoload(scx_lib_init_probe, 0)|
|     true : normal fentry attach path |
+---------------------------------------------+
                  |
                  v
+---------------------------------------------+
| skel__attach() post-load struct_ops loop |
|   for each program __prog:                  |
|     if type == BPF_PROG_TYPE_STRUCT_OPS     |
|        || autoload == false                 |
| continue                              |
|     else                                    |
|       __scx_ops_assoc_prog(...) |
+---------------------------------------------+

类比

把一支 sched_ext 调度器套件想成一支剧团,每天开演前都要先报到:

  • scx_lib_init_probe 是开演前必到的"灯光师 fentry",剧团本来无条件要求他到场;
  • 但有些"小剧场"根本没有 fentry 灯光台(CONFIG_FUNCTION_TRACER=n 的内核),灯光师硬要上台只会当场翻车;
  • 补丁的做法是:报到时先看一眼剧场有没有灯光台,没有就告诉灯光师这次请假(set_autoload(false));后台登记表(struct_ops 关联循环)同时增加一条规则:请假的人不强制填登记表,否则会因为找不到他而把整场戏取消。

另一个生活类比:插件化软件启动时总会按配置加载若干可选模块;这次相当于在配置里加了一个"该模块依赖的运行时特性不存在则跳过"的开关,让系统能在精简环境里继续启动。

Highlight:风险与注意点

  • 硬编译期依赖(Sashiko Medium)SCX_OPS_LOAD 宏直接写死 scx_lib_init_probe 这个名字,调用方的 BPF skeleton 中若没有定义该探针,会在编译期就失败。任何移除或重命名该探针的调度器都需要同步修改这个宏。
  • 运行时与文件存在性的脱节/proc/sys/kernel/ftrace_enabled 文件存在只代表"编译进内核",并不能反映它是否被临时写成 0 或被 tracing 关停;属于一个粗糙但廉价的代理,不是精确的 fentry 可用性检测。
  • 临时兜底而非终态:注释明确写到"一旦不再支持 6.18 之前的内核就可以删掉",上游要记得在合适的时间窗口清理这段 compat 代码。
  • 同步来源漂移:本补丁 sync 自 GitHub PR sched-ext/scx#3758,后续与 GitHub 分叉的修复要持续对齐,避免双线分叉。

版本变化

单封 patch,无 vN 到 vN+1 演进。

与其他相关 patch 系列的关联

  • 直接同步自 GitHub sched-ext/scx 仓库的 PR #3758;目前主线仓库和 GitHub fork 是双线维护,保持单向 sync 即可。
  • 行为上属于 tools/sched_ext 兼容层的工作,目标是更老的内核版本(pre-6.18 缺 fentry 默认开启);后续若 Linux 上游把默认配置改成 always-on,此 patch 自然失效。

一句话总结

SCX_OPS_LOAD() 里加一道"内核是否支持 function tracer"的运行时检查,不可用就跳过 scx_lib_init_probe 的 autoload,并在 struct_ops 关联循环里放过已禁用的程序,从而让没有 CONFIG_FUNCTION_TRACER 的内核也能正常加载 sched_ext 调度器。