eBPF 实战:用 bpftrace 观测 Linux 内核与排查性能

eBPF(Extended Berkeley Packet Filter)是运行在 Linux 内核中的沙箱虚拟机,它让你在不修改内核源码、不加载内核模块的前提下,安全地挂载小程序到内核钩子上,实时观测系统调用、网络、文件 IO 与调度行为。对运维和开发者来说,eBPF 意味着从「猜瓶颈」到「看瓶颈」的质变。

从技术位置看,eBPF 钩子遍布内核:系统调用进出(tracepoint / raw_tracepoint)、内核函数入口与返回(kprobe / kretprobe)、用户态函数(uprobe)、网络层(XDP 处在网卡收包最早阶段、tc 在流量控制层),甚至调度器事件。正是这种「无处不在」让它能回答传统工具答不了的问题:某次卡顿到底是 syscall 慢、是锁竞争,还是磁盘抖动?它把黑盒里的细节直接摊开给你看。

一、eBPF 到底是什么,为什么它改变了可观测性

传统排查手段要么是用户态埋点(成本高、有侵入),要么是内核模块(风险大、易崩)。eBPF 由 Linux 内核在加载前做字节码校验,保证不会死循环、不会越权访问内存,再 JIT 编译成原生指令执行。于是你可以用一段几十行的小程序,挂到系统调用入口、内核函数、网络栈或用户态函数上,采集任意你想看的数据。

更重要的是,eBPF 程序把采集到的数据通过「映射(map)」高效传回用户态,配合 bpftrace、bcc、libbpf 等工具链,几分钟就能写出一个针对性探针。它已经成为现代可观测性、网络加速与运行时安全的底层基石。

二、eBPF 程序的执行生命周期:校验、JIT 与 Map

一个 eBPF 程序从写好到跑起来,要过三关。第一步是校验(verifier):内核会静态分析字节码,证明它没有越界内存访问、没有不可达的死循环、栈使用不超限,否则直接拒绝加载。第二步是 JIT 编译:通过校验后,内核把字节码编译成原生机器指令,运行开销接近普通内核函数。第三步是数据出口:程序内部用「map」这个键值结构在内外核之间传递数据——直方图、计数、字符串都暂存在 map 里,用户态工具定时来读。

理解这三步,你就能判断一个 eBPF 脚本为什么加载失败,以及为什么它能做到「既安全又高效」。比如报错 permission denied 往往是校验没过或没开 CAP_BPF 权限;而 map 选错类型(用直方图 map 存字符串)则会直接导致采集不到数据。

三、一分钟装好 bpftrace 环境

bpftrace 是 eBPF 的「一行脚本」前端,语法类似 awk,最适合快速排查。在主流发行版直接装:

# Ubuntu / Debian
sudo apt update && sudo apt install -y bpftrace linux-headers-$(uname -r)

# RHEL / CentOS / Rocky
sudo dnf install -y bpftrace kernel-devel

# 验证内核是否支持 eBPF
bpftrace -e 'BEGIN { printf("eBPF OK\n"); }'

只要最后打印出 eBPF OK,说明内核配置(CONFIG_BPF、CONFIG_BPF_SYSCALL)就绪。建议内核 ≥ 4.9,5.x 之后探针种类最全。

四、实战一:用 bpftrace 追踪进程与系统调用

4.1 统计每个进程的系统调用次数

想知道某台机器上谁在不停地 syscall?一行搞定:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { @[comm] = count(); }'

@ 是 bpftrace 的映射变量,comm 是进程名,按 Ctrl+C 退出时会自动打印汇总。换成文件 IO 统计也只需换探针:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'

4.2 只盯某一个进程的文件访问

排查「某个 PID 到底读了哪些配置」时,用谓词 /pid == .../ 过滤:

sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat
/pid == 1234/ {
  printf("%-16s %s\n", comm, str(args->filename));
}'

str() 用来把内核里的字符串指针安全地读成文本。注意:谓词里比较的是被追踪进程的 pid,而不是 bpftrace 自己的 pid。

五、实战二:定位磁盘 IO 延迟瓶颈

磁盘慢常常不是吞吐不够,而是尾延迟高。bpftrace 自带 biolatency.bt,直接看块设备 IO 的延迟分布:

# 查看系统级块 IO 延迟直方图(微秒)
sudo bpftrace /usr/share/bpftrace/tools/biolatency.bt

# 实时列出每一次块 IO 及其延迟
sudo bpftrace /usr/share/bpftrace/tools/biosnoop.bt

下面是排查 IO 问题时最常用的几类探针:

排查目标推荐探针 / 工具能看到的信号
块设备延迟分布biolatency.bt各区间 IO 数量直方图
单次 IO 明细biosnoop.bt进程、延迟、扇区、方向
文件读写热点tracepoint:syscalls:sys_enter_read/write进程、字节数、文件描述符
谁在大量 fsynctracepoint:syscalls:sys_enter_fsync进程、调用频次

六、实战三:网络与丢包排查

连接建立慢、偶发丢包,用 eBPF 看内核网络栈比 tcpdump 更轻量、更有上下文:

# 跟踪所有 outbound TCP 连接(进程 + 目标)
sudo bpftrace /usr/share/bpftrace/tools/tcpconnect.bt

# 跟踪 TCP 连接的生命周期与耗时
sudo bpftrace /usr/share/bpftrace/tools/tcplife.bt

相比 tcpdump 在协议栈较高处抓包,eBPF 探针可以挂在更靠内核内部、开销更低的钩子上,且天然带着进程上下文——你能直接看到「是哪个进程的哪条连接」在重传,而不只是冷冰冰的报文。配合 tcplife 的耗时统计,慢连接、空闲长连接、频繁短连接一目了然。

七、eBPF 与监控、K8s、安全的关系

eBPF 不是孤立玩具。在可观测性体系里,它常被做成 exporter 喂给 Prometheus:用 eBPF 在内核采集指标,再经 Prometheus + Grafana 监控面板 统一看板;在 Kubernetes 中,基于 eBPF 的 Cilium 直接替换 kube-proxy 做网络与负载均衡(参见 K8s 入门:Pod、Deployment、Service);在安全侧,Falco 用 eBPF 做运行时入侵检测,与 服务器安全加固 的 SSH / Fail2ban 策略形成纵深防御。把 eBPF 接入持续交付也并不复杂,用 GitHub Actions 实战 里的流水线思路即可把探针脚本版本化、自动化校验。

八、生产落地清单与避坑

把 bpftrace 真正用起来,记住这几条:

  • 优先用官方自带的 /usr/share/bpftrace/tools/*.bt,覆盖 90% 场景,别重复造轮子;
  • 线上用 bpftrace -e '...' 临时探针,排查完即停,避免映射持续占内存;
  • 内核版本太低(< 4.9)或容器里没开特权 / 没挂 /sys 与 /proc,eBPF 会加载失败;
  • 生产环境建议用 bcc / libbpf 把脚本固化成服务,并通过 CI 做版本化管理;
  • 临时探针务必加时间窗口,别让 map 在无人看管时长期累积占用内核内存。

下面是一张高频一行脚本速查表:

想看什么一行命令
进程 syscall 排行bpftrace -e ‘tracepoint:syscalls:sys_enter_execve { @[comm]=count(); }’
线程创建来源bpftrace -e ‘tracepoint:syscalls:sys_enter_clone { @[comm]=count(); }’
内核函数耗时 topbpftrace -e ‘kprobe:do_sys_open { @start[tid]=nsecs; } kretprobe:do_sys_open /@start[tid]/ { @us=hist((nsecs-@start[tid])/1000); delete(@start[tid]); }’
实时新进程bpftrace -e ‘tracepoint:sched:sched_process_exec { printf(“%s %s\n”, comm, str(args->filename)); }’

九、总结

eBPF 把「观测内核」的门槛降到了写几行脚本的级别。配合 bpftrace,你能在不重启、不改代码、不装内核模块的情况下,把进程、IO、网络与调度行为尽收眼底。它是现代 Linux 可观测性与安全的底座,也是每个后端、SRE 都值得掌握的一把「手术刀」。如果只记一句话:当你遇到「监控指标正常、但用户说慢」的谜题时,eBPF 往往就是那把能打开黑盒的钥匙。

上一篇 Java 反射实战:从 Class 对象到框架底层原理
下一篇 CSS Grid 与 Flexbox:网页布局实战指南
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]