bpftrace是快速抓取系统调用耗时最现实的选择,它基于eBPF但屏蔽了验证器限制、map管理等工程细节;需Linux≥5.8、unprivileged_bpf_disabled=0,用kprobe+kretprobe组合精准捕获sys_openat等调用全生命周期耗时,并按成功/失败路径分别直方图统计。

用 bpftrace 快速抓取系统调用耗时,别从 libbpf 重头写
直接上 bpftrace 是最现实的选择——它底层用 eBPF,但屏蔽了验证器限制、map 手动管理、CO-RE 适配等大量工程细节。你不需要写 C 加载器、不用处理 bpf_program__attach_tracepoint 返回值、更不用手动解析 struct pt_regs。对“查某个系统调用花了多久”这种需求,bpftrace 足够准、足够快、足够稳。
常见错误是硬啃 libbpf 示例代码,结果卡在 errno = -22 (Invalid argument)(验证器拒绝)或 failed to load program: Permission denied(没开 unprivileged_bpf_disabled)。这些和你的性能分析目标完全无关。
- 确保内核支持:Linux ≥ 5.8(推荐 ≥ 6.1),且
/proc/sys/kernel/unprivileged_bpf_disabled值为0 - 安装
bpftrace:Ubuntu/Debian 用sudo apt install bpftrace;RHEL/CentOS 用dnf install bpftrace - 不要用
sudo bpftrace -e '...'启动就跑——先确认非 root 用户能执行,否则后续集成进监控脚本会崩
用 tracepoint + kprobe 混合定位 sys_enter / sys_exit 耗时
只靠 sys_enter tracepoint 只能知道调用开始,但不知道返回时间;只靠 sys_exit 则漏掉失败路径(比如 ENOSYS 直接返回)。正确做法是用 kprobe 挂在 sys_*() 函数入口,再用 kretprobe 捕获返回,这样能覆盖所有路径,包括内核态中途跳转、early return 等。
示例:追踪 openat 的完整生命周期耗时:
#!/usr/bin/env bpftrace
kprobe:sys_openat {
@start[tid] = nsecs;
}
kretprobe:sys_openat /@start[tid]/ {
$delta = nsecs - @start[tid];
@hist_us = hist($delta / 1000);
delete(@start[tid]);
}
-
@start[tid]用线程 ID 做 key,避免多线程交叉污染 - 除以
1000转成微秒,hist()自动做对数分桶,比手写数组统计直观得多 - 必须加
/@start[tid]/过滤条件——某些路径(如 seccomp 拦截)可能根本没走到sys_openat入口,kretprobe仍会被触发,导致空指针解引用或负值
区分成功/失败路径并过滤无效调用
系统调用耗时分布里混着大量 -ENOENT、-EACCES 等快速失败路径,它们和真实 I/O 耗时不在一个量级。不分离的话,直方图全被 sub-microsecond 峰值淹没,看不出磁盘延迟毛刺。
关键点在于:从 pt_regs 提取返回值,并在 kretprobe 中判断符号位:
kretprobe:sys_openat /@start[tid]/ {
$retval = ((int)reg("ax"));
$delta = nsecs - @start[tid];
$us = $delta / 1000;
if ($retval >= 0) {
@success_us = hist($us);
} else {
@fail_us = hist($us);
}
delete(@start[tid]);
}
-
reg("ax")在 x86_64 上对应返回寄存器,值为负表示错误码(内核惯例) - 不要用
$retval > 0判断成功——0也是合法成功返回(如close()) - 避免在
kprobe里读pt_regs->di等参数:不同架构寄存器名不同(ARM 是x0),bpftrace不跨平台抽象这一层,硬写会断在 ARM 机器上
输出到文件或管道时注意缓冲与精度损失
bpftrace -f json -o trace.json 看起来方便,但默认每秒 flush 一次,高频系统调用(如 read)会导致大量事件堆积后集中写出,丢失时间序列关系。更糟的是,JSON 输出把 hist() 转成离散 bin 数组,无法还原原始分布形状。
- 实时分析用
-f text+stdbuf -oL强制行缓冲:stdbuf -oL bpftrace -f text script.bt | tee raw.log - 要保留直方图结构,改用
--unsafe+@hist_us:print()手动输出,避免 JSON 序列化截断 bin 边界 - 纳秒级时间戳在用户态解析时易受调度延迟影响,若需亚毫秒精度,必须配合
perf_event_open(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK)校准,但这已超出bpftrace能力范围——此时该切到libbpf+bpf_get_smp_processor_id()+bpf_ktime_get_ns()组合
真正难的不是怎么打点,而是确认你看到的“耗时”到底包含什么:是纯内核处理?含 page fault?含锁竞争?含 LSM hook 开销?这些边界得结合 /proc/kallsyms 符号和 kernel config(比如是否启用了 CONFIG_SECURITY)交叉验证,不能光看 eBPF 输出就下结论。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











