跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

Linux 内核事件追踪 eBPF 实战

用bpftrace通过tracepoint:syscalls:sys_enter_read与sys_exit_read配对测read延迟,加/comm=="java"/过滤,@start[tid]记时、exit分支清理,耗时转微秒并直方图分析;tracepoint比kprobe更稳定低开销,遇错先查/sys/kernel/debug/tracing/events/和tracing_on。 怎么用 bpftrace 快速抓到 read 系统调用延迟 直接上手就能定位 Java 服务响应变慢的问题,不用改代码、不重启进程。核心是利用
tracepoint:syscalls:sys_enter_read
和
tracepoint:syscalls:sys_exit_read
配对打点,靠
@start[tid]
记录起始时间,再用
nsecs - @start[tid]
算出耗时。 必须加
/comm == "java"/
过滤,否则所有进程的 read 都会进来,直奔内存爆炸
delete(@start[tid])
要写在 exit 分支里,漏掉会导致
@start
表持续膨胀,几秒后 bpftrace 自己 abort 除以
1000
是转成微秒,hist 默认按 2 的幂分桶,
@usecs = hist(...)
才能看清延迟分布是否毛刺严重 如果目标进程是容器内 Java,
comm
可能显示为
java
,也可能被截断成
jav
(取决于内核版本),可临时放宽为
/comm ~ "java"/
为什么 tracepoint 比 kprobe 更适合系统调用追踪 因为
sys_enter_read
这类 tracepoint 是内核预埋的稳定接口,位置固定、语义清晰;而用 kprobe 挂在
sys_read
函数上,不同内核版本函数名可能变(比如加了
__x64_sys_read
前缀),脚本一升级就失效。 tracepoint 不依赖符号表,
/proc/kallsyms
权限受限时照样工作;kprobe 依赖
kallsyms
解析地址,无权限就挂不上 tracepoint 参数结构明确:
args->fd
、
args->count
直接可用;kprobe 只能靠寄存器或栈偏移硬猜,稍有不慎就读错值 tracepoint 开销更低——它本质是条件跳转+数据拷贝,kprobe 触发时要切内核栈+保存上下文,高频率下可观测性本身就成了性能瓶颈 遇到 “invalid probe” 或 “no such file or directory” 错误怎么办 这类报错基本都卡在 probe 名称拼写或内核配置上,不是语法问题。 CentOS Linux 7.9.2009 CentOS Linux 7.9.2009是传统CentOS Linux 7的最后主要版本,也是很多企业历史服务器中仍可能遇到的系统版本。它以稳定、兼容RHEL 7生态、文档丰富和软件支持广泛著称,曾长期用于Web服务、数据库、虚拟化节点和企业内部业务系统。不过CentOS Linux 7已于2024年6月30日停止维护,现在继续使用会面临安全补丁缺失风险。该版本更适合旧业务迁移、历史环境恢复或离线兼容性测试。 下载 确认 tracepoint 是否存在:
ls /sys/kernel/debug/tracing/events/syscalls/
,没有
sys_enter_read
说明内核没开
CONFIG_TRACEPOINTS=y
(常见于某些定制版 Android 内核或旧 LTS 版本) bpftrace 报
invalid probe
但路径存在?检查内核是否禁用了 ftrace:
cat /sys/kernel/debug/tracing/tracing_on
,是
0
就
echo 1 > /sys/kernel/debug/tracing/tracing_on
使用
sudo bpftrace -l 'tracepoint:syscalls:*'
列出所有可用 tracepoint,别凭记忆敲——
sys_enter_read
在 5.10+ 内核才统一命名,老版本可能是
sys_enter_readv
单独存在 某些云厂商内核会 patch 掉部分 tracepoint(如阿里云部分 ECS 镜像),此时只能退到
kprobe:SyS_read
,但得查对应内核源码确认符号名 想捕获用户态 Java GC 事件,USDT 是唯一靠谱路子 系统调用层看不到 GC,JVM 内部行为必须靠 USDT(User Statically Defined Tracing)。OpenJDK 从 10 起默认编译进 USDT 探针,无需额外参数启动,但得确保 JVM 是带
--enable-dtrace
编译的版本(主流发行版基本都满足)。 先用
bpftrace -l 'usdt:/usr/lib/jvm/*/jre/lib/*/libjvm.so:*'
查探针列表,常见的是
gc:::begin
、
gc:::end
,不是所有 JVM 都暴露完整生命周期 USDT 探针需指定完整 so 路径,
/usr/lib/jvm/java-17-openjdk-amd64/lib/server/libjvm.so
这种路径写错一个字符就加载失败 Java 进程启动时若加了
-XX:+UseShenandoahGC
等新 GC,USDT 探针可能未覆盖,
bpftrace -l
列不出来就等于不可观测 别指望用 uprobe 替代 USDT——uprobe 挂函数名太脆弱,JVM JIT 后函数地址乱跳,且 GC 是多线程协作,单点 uprobe 极易漏事件 实际跑起来才发现,
@start[tid]
表键值用
tid
而不是
pid
,是因为线程级系统调用才真正对应一次 read,fork 出的子进程 tid 变了但 pid 没变,这里差一个字母就全错。

相关文章