1. eBPF 核心架构概览
eBPF(Extended Berkeley Packet Filter)是一项革命性技术,允许在 Linux 内核中运行沙盒程序而无需修改内核源码或加载内核模块。最初设计用于网络数据包过滤的 eBPF,已发展成为一个支持追踪、网络、安全和可观测性的多用途内核子系统。
1.1 eBPF 程序生命周期
eBPF 程序的生命周期包含五个不同阶段:
- 编译:eBPF 程序使用 C(或 Rust/Python)编写,通过 LLVM/Clang 编译为 eBPF 字节码
- 校验:内核验证器执行静态分析确保程序安全性——无无限循环、无越界访问、无不可达指令
- JIT 编译:验证通过的字节码被转换为原生机器码以获得最优性能
- 挂载:程序被挂载到内核函数、追踪点、kprobes、uprobes 或 XDP 钩子
- 执行:由内核事件触发,通过 maps 或 perf events 导出结果
1.2 eBPF 虚拟机
eBPF VM 使用 11 个 64 位寄存器(R0-R10),其中 R0 存储返回值,R1-R5 保存函数参数,512 字节栈空间,并通过 maps 与用户空间通信,map 可存储任意类型的数据。maps 可被 eBPF 程序从内核态和用户态同时访问。
2. eBPF Maps:内核态与用户态的通信桥梁
eBPF maps 是键值对存储结构,实现 eBPF 程序与用户空间、或不同 eBPF 程序之间的数据交换:
| Map 类型 | 使用场景 | 键大小 | 值大小 |
|---|---|---|---|
| Hash | 键值查找、计数器 | 不定长 | 不定长 |
| Array | 固定大小索引存储 | 4 字节 | 不定长 |
| Ring Buffer | 高吞吐量流式传输 | N/A | 不定长 |
| Perf Event Array | 每 CPU 事件输出 | 4 字节 | 4 字节(fd) |
| LRU Hash | 基于淘汰的缓存 | 不定长 | 不定长 |
| LRU Per-CPU Hash | 每 CPU 缓存 | 不定长 | 不定长 |
| CPU Map | CPU 特定数据访问 | 4 字节 | 不定长 |
| Stack | LIFO 追踪存储 | 4 字节 | 不定长 |
| Bloom Filter | 概率性成员检测 | N/A | 不定长 |
2.1 Ring Buffer vs Perf Buffer
Perf Buffer(perfbuf)是传统机制,需要预分配 per-CPU 缓冲区,缓冲区满时可能丢失事件。Ring Buffer(ringbuf)是现代替代方案,具有自动淘汰、有序传递和更好的内存效率——是新程序的首选方案。
3. 辅助函数与验证机制
3.1 BTF 与跨内核兼容性
BTF(BPF Type Format)提供内核数据结构的元数据,实现 CO-RE(Compile Once, Run Everywhere,编译一次到处运行)。借助 BTF,eBPF 程序可以在运行时通过读取 BTF 信息适配不同内核版本,无需为每个内核版本重新编译。
3.2 验证器安全保证
验证器通过以下检查确保 eBPF 程序安全:有界循环(最大迭代次数通常 100 万次)、有界栈使用(512 字节)、以及仅经验证的内存访问(bpf_probe_read())。验证器使用抽象解释来追踪所有可能执行路径上的寄存器状态。
4. 追踪机制
4.1 Kprobes 与 Kretprobes
Kprobes 通过在内核函数入口点插入断点提供动态内核插桩能力。Kretprobes 拦截函数返回值。两者都因断点带来一定开销,但提供最大的灵活性。
4.2 Tracepoints 追踪点
Tracepoints 是嵌入内核源码的静态插桩钩点,未激活时零开销。它们在跨内核版本间提供稳定的签名,是生产环境追踪的优选方案。
4.3 XDP:极速数据路径
XDP 在软件栈最低层实现数据包处理,在网卡驱动接收包后立即执行。XDP 程序在内核分配 sk_buff 之前就已完成处理,实现个位数纳秒级单包延迟。返回码包括 XDP_PASS、XDP_DROP、XDP_TX 和 XDP_REDIRECT。
5. 生产环境 eBPF 可观测性工具
5.1 bpftrace
bpftrace 提供高级 eBPF 脚本语言,适合临时性追踪:
# 追踪 execve() 系统调用并打印时间戳
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s %d %s\n", comm, pid, str(args->filename)); }'
# 按进程统计 read() 调用次数
bpftrace -e 'tracepoint:syscalls:sys_enter_read { @[comm] = count(); }'
# 追踪文件打开延迟直方图
bpftrace -e 'kprobe:do_sys_openat2 { @start[tid] = nsecs; } kretprobe:do_sys_openat2 /@start[tid]/ { @latency_us = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); }'
5.2 BCC(BPF 编译器集合)
BCC 提供基于 Python 的 eBPF 前端,嵌入 C 语言代码。适合复杂追踪和性能分析:例如追踪 VFS 读取大小并输出直方图,或通过挂载 tcp_retransmit_skb 监控 TCP 重传。
5.3 libbpf CO-RE
libbpf 结合 BTF-based CO-RE 实现可移植的 eBPF 程序,适配不同内核版本。使用 clang -g -O2 -target bpf -D__TARGET_ARCH_x86 编译,加载器基于内核 BTF 在加载时应用重定位。
6. 容器与网络可观测性
6.1 基于 cgroup 的容器追踪
eBPF 程序可挂载到 cgroup 钩子来监控容器 I/O、网络和 CPU 使用。cgroup_skb 程序类型实现每 cgroup 的网络过滤,cgroup_device 控制设备访问。
6.2 Cilium:基于 eBPF 的 CNI
Cilium 利用 eBPF 为 Kubernetes 提供网络、安全和可观测性:
- 网络:直接通过套接字级负载均衡(BPF_SOCK_OPS、SK_LOOKUP)替代 kube-proxy
- 安全:在 XDP、TC 和套接字层级执行网络策略
- 可观测性:Hubble 提供流级别可见性,无需 sidecar
6.3 Istio Ambient Mesh
Istio 的 ambient 模式使用 eBPF 实现零 sidecar 的 mTLS 和 L7 策略执行,相比 sidecar 代理模式大幅降低资源开销。
7. eBPF 增强可观测性
7.1 网络延迟分析
eBPF 可通过挂载 tcp_connect 和 tcp_rcv_state_process 测量 TCP 连接延迟,以纳秒级精度追踪从 SYN 到 SYN-ACK 的耗时。这实现了无需修改应用即可识别 P99 尾延迟。
7.2 系统调用追踪与过滤
通过将 eBPF 程序挂载到 tracepoint/syscalls/sys_enter_* 和 sys_exit_*,可追踪所有系统调用的参数、返回值和延迟。结合 PID/cgroup 过滤,实现每容器的系统调用审计。
7.3 Off-CPU 分析
eBPF 可通过 sched_switch 和 sched_wakeup 追踪点追踪调度器上下文切换,构建 off-CPU 火焰图来识别阻塞 I/O、锁竞争和调度器延迟。
8. 安全:LSM eBPF
LSM(Linux Security Module)BPF 允许将程序挂载到 LSM 钩子,实现细粒度的安全策略执行。相比传统 LSM 模块,eBPF LSM 无需重新编译内核即可动态更新策略,实现运行时安全控制,如文件访问限制、网络访问控制和权限提升防护。
9. 性能基准测试
| 操作 | 原生内核模块 | eBPF | 开销 |
|---|---|---|---|
| 数据包过滤(XDP) | 820 Mpps | 790 Mpps | ~4% |
| 系统调用追踪(open) | 不支持 | 120 万事件/秒 | ~3% CPU |
| TCP 重传挂载 | 不支持 | 80 万事件/秒 | ~2% CPU |
| cgroup 网络过滤 | 不支持 | 560 Mpps | ~6% |
10. 未来方向
- eBPF 调度器:通过 sched_ext 框架实现自定义调度器,支持用户定义调度策略
- eBPF 存储:I/O 调度和块层可观测性
- DPU/SmartNIC 卸载:在网卡硬件上执行 XDP 程序实现超低延迟
- 内核级 AI 推理:在内核上下文中利用 eBPF 进行轻量级推理
- 可移植 eBPF:通过用户态执行实现跨平台 eBPF(eBPF for Windows 等)

发表评论 取消回复