跳转到主要内容

为什么可扩展 BPF 调度器要升级 pahole 后才能加载?

简短回答: 因为 pahole 是内核构建工具链的一部分,不只是查看结构体的工具。启用 CONFIG_DEBUG_INFO_BTF=y 时,内核构建会用 pahole 把 DWARF 调试信息转换成嵌入 vmlinux 的 BTF。如果旧版编码器不能正确描述 sched_ext 使用的某个类型,那么即使重新编译了内核,生成的 BTF 仍可能不完整或不准确。继续用同一版编码器重编只会复现同一份元数据;升级编码器并重新构建,才会改变 verifier 与 libbpf 实际使用的内核 BTF。

这并不表示所有 sched_ext 加载失败都是 pahole 的问题。缺少内核配置、调度器源码与运行内核的 sched_ext 版本不一致、kfunc 不受支持,或普通 verifier 拒绝,都会表现为“调度器加载不了”。正确的排查方式是把运行内核、它的 BTF 与 BPF 对象视为同一个有版本的整体。

为什么编码器版本会改变结果

BTF 用紧凑格式描述内核类型和函数签名。libbpf 使用运行内核的 BTF 完成 CO-RE 重定位;verifier 在检查带类型的 kfunc 与 struct_ops 接口时也依赖 BTF。sched_ext 同时高度依赖两者:调度器需要实现 struct sched_ext_ops,并调用必须与运行内核类型一致的调度器 kfunc。

内核文档把 pahole 描述为 DWARF 到 BTF 的转换器。pahole 1.31 修复了多个 BTF 编码边界,包括:带特殊对齐的结构体通过栈传参时应如何选择可写入 BTF 的函数、零长度数组与位域后数组的对齐推断,以及通过更新 libbpf 修复 BTF 去重。这些都是元数据生成问题。它们不会修改调度器源码,却可能改变哪些函数与布局能被内核 BTF 安全、准确地表达。

因此关键链路是:

  1. 编译器在构建内核时生成 DWARF;
  2. pahole 把 DWARF 转换为 .BTF
  3. 构建出的内核通过 /sys/kernel/btf/vmlinux 暴露 BTF;
  4. libbpf 根据这份运行时 BTF 重定位调度器对象;
  5. verifier 检查最终的 struct_ops 程序与 kfunc 调用。

旧编码器造成的根因发生在第 2 步,但错误可能直到第 5 步才显现。

区分不同根因的排查顺序

修改环境前,先记录准确的构建与运行输入:

$ uname -r
$ pahole --version
$ grep -E 'CONFIG_(DEBUG_INFO_BTF|SCHED_CLASS_EXT)=' /boot/config-$(uname -r)
$ test -r /sys/kernel/btf/vmlinux && echo runtime-BTF-present

还要确认 pahole --version 指向的正是构建内核时使用的二进制,而不是内核构建完成后才安装的新版本。构建日志和构建环境比当前 shell 的 PATH 更可靠。

接着检查构建产物与运行产物:

$ readelf -S ./vmlinux | grep -E '[.]BTF([.]ext)?'
$ bpftool btf dump file /sys/kernel/btf/vmlinux format raw > /tmp/runtime.btf.txt
$ bpftool btf dump file ./vmlinux format raw > /tmp/built.btf.txt

两份 dump 应该对应同一个内核镜像。如果调度器就是为运行内核构建的,应从运行时 BTF 重新生成 vmlinux.h,再编译 BPF 对象。随后先加载来自同一 scx 与内核版本的最小调度器,再测试复杂策略。这样可以区分基础接口损坏与策略本身触发的 verifier 错误。

最后保留完整 verifier 日志,并按类型归因:

  • 缺少 CONFIG_SCHED_CLASS_EXT 或运行时 BTF,属于内核配置问题;
  • 缺少 kfunc 或 struct sched_ext_ops 字段发生变化,通常是源码/API 版本不一致;
  • CO-RE 重定位失败,说明对象与运行时 BTF 不一致;
  • 参数、状态或控制流非法,可能只是调度器本身的普通错误;
  • 同一份内核源码只有改用新版 pahole 重构后才能通过,强烈说明问题在 BTF 生成阶段,但仍需比较 BTF 才能定位具体编码缺陷。

不要把另一台机器的 vmlinux 或 BTF 复制过来当作修复。正确做法是用目标工具链重建内核,启动那一个确切镜像,再确认 /sys/kernel/btf/vmlinux 确实属于它。

适用边界

sched_ext 文档明确说明,其面向 BPF 调度器的 ABI 不保证跨内核版本稳定。修复编码器只消除了一类错误元数据,并不会让调度器自动兼容任意内核或 scx 版本。发行版内核测试仍然必要,因为编译器、配置、回移补丁、BTF 生成方式和调度器接口会一起变化。上游 scx 针对多个发行版内核运行 verifier 测试的工作,正是部署前发现这类组合问题的实用方式。

参考资料

今日社区讨论

今天通过普通可见界面检查了 6 个获准社区中的 15 个 allowlist 频道或公开页面,全部可访问。本摘要删除了身份、频道名、消息链接、精确时间和具体部署信息。多个项目频道只有自动开发通知,另有一些讨论频道在 24 小时窗口内没有实质技术内容;这些安静频道计入覆盖范围,但不被描述为社区互动。

内核 BTF 是构建产物,不是静态前置条件

最明确的实践问题是:尝试多个新构建的内核后,调度器仍然失败;升级 BTF 编码器并再次构建后才恢复。机制正是上文的构建链路:只改内核源码或配置,并不能修复同一编码器持续生成的错误元数据。直接的处理路径是记录构建时真正使用的编码器、比较构建与运行 BTF、重新生成 BPF 侧类型头,并用完整 verifier 日志把加载尝试缩减到最小调度器。没有产物级 diff 时,仍不能确定究竟是哪一个类型触发了错误;1.31 发布说明给出了多个相关的对齐与去重修复,但不能单凭时间先后断言某一个修复就是本次根因。

更广泛的运维结论是:内核兼容矩阵必须包含工具链。上游发行版 verifier 测试验证完整组合,而不是假设一个内核版本号就足以确定行为。对于 BPF 接口可能随内核变化的 sched_ext,这一点尤其重要。

BPF 类型表达能力仍在演进

公开内核开发归档中,一组活跃补丁在支持 kfunc 返回最多 16 字节的聚合值:它使用一对寄存器,并同时扩展 verifier 精度回溯、活跃性分析、JIT、BTF 可靠性检查与自测。开发者会遇到的实际边界是:一个在 C 中看起来普通的函数签名,并不一定已在所有架构和程序上下文中具备合法的 BPF 调用约定。排查时应检查目标内核和架构是否支持,再同时查看 BTF 签名与 verifier 日志,而不是把 C 原型当成完整契约。仍未覆盖的边界是 callback;该系列在开放一般 kfunc 场景的同时,仍明确拒绝返回超过 8 字节的 callback。

相关 arena 工作还涉及为 kfunc 与 struct_ops 传递带类型的 arena 参数,以及 arena 页面缺页时如何遵守内存上限。这再次说明与 pahole 问题相同的原则:BTF 必须先表达地址空间和类型契约,verifier 与 JIT 才能执行它。遇到 arena 故障时,应区分类型/重定位拒绝与运行时内存压力,检查 cgroup 内存上限与 fault 路径,再把程序缩减到相关的类型参数或分配操作。公开线程中的架构覆盖与最终合入状态仍在变化,不能当作已部署内核的承诺。

多目标 tracing 需要事务式失败处理

另一组活跃上游补丁提出了可一次绑定多个目标的 tracing link,并为 cookie、link 信息、tail call、失败场景和 rollback 添加自测。具体运维风险是部分 attach:如果多目标操作中途失败却没有事务语义,观测程序可能只覆盖预期范围的一部分。验证时应枚举最终 link 信息,故意提供一个非法目标,并确认 rollback 后没有残留 attachment。在接口进入目标内核和 libbpf 版本之前,应用仍应保留原有的逐目标 attach 与清理逻辑,不能提前假设提案中的 multi-link 语义已经存在。

公开论坛在当日窗口内没有新帖,最近的实践内容来自本周更早时候的性能分析和 verifier 示例讨论。两个可观测性频道与通用 eBPF 聊天在窗口内也较安静。它们均已检查并按“安静”计数,没有被用作今天问题的证据。

继续阅读

最后更新
2026年8月9日
首次发布
2026年8月9日
贡献者
Littlefisher619

这个页面有帮助吗?