为什么可扩展 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 安全、准确地表达。
因此关键链路是:
- 编译器在构建内核时生成 DWARF;
pahole把 DWARF 转换为.BTF;- 构建出的内核通过
/sys/kernel/btf/vmlinux暴露 BTF; - libbpf 根据这份运行时 BTF 重定位调度器对象;
- 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 测试的工作,正是部署前发现这类组合问题的实用方式。
参考资料
paholev1.31 发布说明- Linux 内核文档:BPF Type Format
- Linux 内核文档:
bpftool-btf - Linux 内核文档:
sched_ext scxPR:在发行版内核上运行 verifier 测试- BPF 邮件列表:最多 16 字节的聚合返回值
- BPF 邮件列表:arena 缺页时遵守
memory.max - BPF 邮件列表:kfunc 与
struct_ops的 arena 参数 - BPF 邮件列表:
tracing_multilink 支持
今日社区讨论
今天通过普通可见界面检查了 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 聊天在窗口内也较安静。它们均已检查并按“安静”计数,没有被用作今天问题的证据。
继续阅读
返回索引
eBPF 问答
这里每天收录一个关于 eBPF、Linux 可观测性、性能分析、运行时扩展或安全问题的回答。每篇回答都以公开的一手资料为依据,并在末尾附上匿名化的当日社区讨论摘要。
上一篇 / 上一页
eBPF 能否识别网络流量中的密钥,同时不采集密钥本身?
挂载在 TC、XDP 或数据包套接字上的 eBPF 程序看到的是线上传输的字节。对于 HTTPS,这些字节通常已经加密,因此程序可以判断连接类型、统计流量,却无法从密文中识别 API key。OpenTelemetry eBPF Instrumentation(OBI)也区分网络可观测性和应用可观测性;它对 TLS 请求的支持依赖用户态 uprobe,并可能需要额外权限,而不是只靠普通的数据包检查。
下一篇 / 下一页
为什么在 TC egress 中把 socket 放入 SOCKHASH 会导致内核 soft lock?
简短回答: 不要把 TC 数据包 hook 当成把活动 TCP socket 加入 BPFMAPTYPESOCKHASH 的控制路径。sockhash 不是一个保存借用 struct sock 指针的普通哈希表。插入 socket 时,内核会持有引用、创建或复用 skpsock、替换 socket callback,并让 socket 继承 map 上挂载的程序。在同一个 socket 已经处于发送路径时执行这次状态转换,可能进入
- 最后更新
- 2026年8月9日
- 首次发布
- 2026年8月9日
- 贡献者
- Littlefisher619
这个页面有帮助吗?