kprobe vs fentry:eBPF 追踪性能对比
kprobe vs fentry:eBPF 追踪性能对比
kprobe vs fentry:eBPF 追踪性能对比
本文介绍了 eBPF 追踪中 kprobe 和 fentry 机制的技术差异,重点解释了为什么 fentry 是 pwru 等高性能工具的首选。
1. 概述
- kprobe (Kernel Probe):传统的动态追踪机制。可以挂载到内核中几乎任何指令位置。
- fentry (Function Entry):较新内核(5.5+)引入的轻量级追踪机制,专门为函数入口追踪而优化。
2. 工作原理
kprobe:“断点”法
kprobe 依赖 CPU 异常和陷阱处理。
- 指令替换:内核将目标函数的第一条指令替换为断点指令(如 x86 上的
int3)。 - 陷阱/异常:CPU 执行到此处触发异常。
- 上下文切换:暂停执行并切换到内核异常处理程序。
- 执行 Handler:查找并执行附加的 BPF 程序。
- 单步执行:在特殊区域运行被替换的原始指令。
- 恢复:继续正常流程。
性能成本:开销主要来自上下文切换和异常处理,涉及全量寄存器的保存与恢复。
fentry:“蹦床”法
fentry 利用 BPF Trampoline 机制,将追踪转化为一次普通的函数调用。
- Nop 替换:现代内核编译时在函数入口保留了空间(通常是
nop指令)。 - 直接调用:挂载 BPF 时,内核将
nop修改为直接的call指令。 - 执行 Trampoline:
call跳转到生成的“蹦床”(一段汇编代码)。 - 执行 BPF:蹦床保存极少数必要寄存器,直接调用 BPF 程序。
- 返回:完成后恢复寄存器并返回原函数。
性能成本:开销等同于标准的 C 函数调用,完全避免了昂贵的异常机制。
3. 对比总结
| 特性 | kprobe | fentry |
|---|---|---|
| 机制 | 软件断点 (int3) | 直接调用 (call) |
| 开销 | 高 (异常 + 上下接切换) | 低 (函数调用) |
| 延迟 | 微秒级 | 纳秒级 |
| 数据访问 | 需要解析 PT_REGS | 直接访问寄存器/参数 |
| 内核版本 | 极旧版本起支持 | 较新 (Linux 5.5+) |
| 插桩位置 | 任意指令 | 仅限函数入口/出口 |
| BTF 依赖 | 可选 | 强制要求 |
4. 为什么对 pwru 很重要
pwru 通常会同时挂载成百上千个内核函数 (--all-kprobes)。
- 使用 kprobe:处理一个网络包可能触发 10 次以上探针。如果每个探针开销巨大,整机延迟会剧增,吞吐量大幅下降。
- 使用 fentry:单次调用开销几乎可忽略不计,使得在大规模路径追踪时不至于拖垮系统性能。
5. 结论
对于 pwru 这种大规模挂载工具,只要内核支持,fentry 永远是优选方案。它通过消除异常处理开销,比 kprobe 提供 5 到 10 倍的性能提升。
This post is licensed under CC BY 4.0 by the author.