Linux怎么使用BPFTrace监控内核 Linux高级动态追踪实战详解
使用bpftrace监控内核时,常因权限不足、debugfs未挂载或追踪点未启用导致脚本无输出。需注意kprobe与tracepoint在覆盖范围、参数访问和性能上的差异。测量函数耗时需防止时间戳覆盖或泄漏,监控内存分配应优先使用tracepoint以避免内核版本参数差异。理解探针类型、变量作用域及内核具体实现是关键。
直接上手写bpftrace脚本不难,但如果不理解探针类型和变量作用域,脚本大概率跑不起来,或者输出一片空白,让人摸不着头脑。

为什么执行命令没反应?
比如,你兴冲冲地敲下 bpftrace -e 'tracepoint:syscalls:sys_enter_open { printf("hit\n"); }',终端却一片寂静。这通常不是脚本错了,而是环境没准备好。
首要原因往往是权限不足或内核未启用对应的追踪点。不是所有系统调用的tracepoint都默认可用,尤其是在一些较老或特定配置的内核上。
- 先确认点是否存在:执行
ls /sys/kernel/debug/tracing/events/syscalls/sys_enter_open/。如果目录不存在,基本可以断定内核编译时没包含这个点。 - 必须用root权限:bpftrace需要访问内核追踪接口,记得加上
sudo。 - 检查debugfs:某些发行版(如RHEL/CentOS 8+)默认不挂载debugfs,需要手动执行:
sudo mount -t debugfs none /sys/kernel/debug。 - 缩小范围:如果只想观察特定进程(比如
curl),加上过滤条件会更可靠:/comm == "curl"/ { printf("open by %s\n", comm); }。
kprobe和tracepoint,到底用哪个?
同样是监控读操作,kprobe:vfs_read 和 tracepoint:syscalls:sys_enter_read 看起来相似,实则大不相同。前者是动态插桩,后者是内核预埋的静态点,实际观测中行为差异明显:
- 覆盖范围:
kprobe:vfs_read能捕获所有内核态的read路径(包括文件、管道、套接字),但可能被内联优化绕过;而tracepoint:syscalls:sys_enter_read只捕获从用户态发起的read()系统调用入口,虽然稳定,但覆盖范围较窄。 - 参数访问:
kprobe使用arg0–argN来获取寄存器中的参数;tracepoint则必须通过结构体字段,如args->fd、args->count。 - 性能开销:通常
tracepoint开销更低。kprobe如果挂载在高频函数上(比如schedule),很容易触发内核的采样限流机制(受perf_event_max_sample_rate控制)。
如何安全地测量函数耗时?
想测系统调用耗时,常见的思路是用map记录开始时间。但这里有个坑:如果不做配对清理和条件判断,很容易因为线程复用或异常退出,导致map键堆积,甚至时间戳被覆盖。
- 防覆盖:在记录开始时间前,必须检查键是否已存在:
kprobe:sys_write /!@start[tid]/ { @start[tid] = nsecs; }。否则,同一个线程的连续调用会覆盖掉前一次的时间戳。 - 防泄漏:在返回探针中,要带非空判断再清理:
kretprobe:sys_write /@start[tid]/ { @dur = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); }。 - 键的选择:别用
pid当key。在多线程进程里,不同线程的tid不同,但共享同一个pid,用pid会导致统计完全失真。 - 超时兜底:加一行
interval:s:10 { exit(); }是个好习惯,能防止脚本意外卡死。
监控内存分配,为什么输出全是0?
尝试用 kprobe:__kmalloc 监控内存分配大小,却发现输出的 arg0 全是0?这很可能不是你脚本的问题,而是内核版本间的“暗坑”。
__kmalloc 函数的参数顺序在不同内核版本中可能不同。在5.10+内核里,arg0 可能是分配大小,但在4.19内核里,arg0 对应的可能是 gfp_flags。硬编码参数位置极易失效。
- 优先用tracepoint:如果内核支持,优先使用
tracepoint:kmalloc:kmalloc,它提供了标准化的字段,如args->bytes_alloc。 - 查证符号:如果必须用kprobe,务必查证当前内核的符号定义:
sudo cat /proc/kallsyms | grep __kmalloc,再结合objdump -t /lib/modules/$(uname -r)/build/vmlinux | grep __kmalloc来确认参数布局。 - 注意覆盖度:有些内存分配路径(比如SLAB分配器内部)不会经过
__kmalloc,可能需要配合tracepoint:kmalloc:kmalloc_node等点来补全观测。
说到底,bpftrace真正的难点,不在于写出一行脚本,而在于搞清楚你看到的每一个 arg0、args->xxx、@map,在当前运行的内核版本里,究竟对应着什么内存布局和生命周期。不事先查好 /sys/kernel/debug/tracing/events/ 和 /proc/kallsyms 就动手,无异于蒙着眼睛调参。

































