eBPF 程序的开发涉及两个部分:内核态的 eBPF 程序(C/Rust 编写,编译为字节码)和用户态的加载器(管理程序生命周期、读取 Map 数据)。不同的开发框架提供了不同的抽象层次和开发体验——从 libbpf 的底层控制到 bpftrace 的一行命令,从 C 的性能到 Rust 的安全,从 Go 的生态到 Aya 的纯 Rust。
本章对比四大 eBPF 开发框架,帮你做出正确的技术选型。
一、框架全景对比
1.1 四大框架
| 框架 | 语言 | 内核态 | 用户态 | CO-RE | 复杂度 |
|---|---|---|---|---|---|
| libbpf | C | C | C | 中 | |
| cilium/ebpf | Go | C | Go | 中 | |
| Aya | Rust | Rust | Rust | 高 | |
| bpftrace | DSL | DSL | C++ | 低 |
1.2 选择决策树
1.3 BCC:Python 前端的 eBPF 工具集
BCC(BPF Compiler Collection)是决策树中”Python 前端”的选项,在运维场景中广泛使用。它的架构与 libbpf/Aya 有本质区别:
BCC 的核心特征是运行时编译——Python 脚本中嵌入的 C 代码在执行时才被 LLVM 编译为 BPF 字节码。这意味着无需预编译,但也意味着每次运行都需要 LLVM,且编译结果依赖当前内核版本(不支持 CO-RE)。
# BCC Python 示例:追踪 openat 系统调用from bcc import BPF
bpf_text = """#include <uapi/linux/ptrace.h>struct data_t { u32 pid; char comm[16]; char filename[256]; };BPF_PERF_OUTPUT(events);
TRACEPOINT_PROBE(syscalls, sys_enter_openat) { struct data_t data = {}; data.pid = bpf_get_current_pid_tgid() >> 32; bpf_get_current_comm(&data.comm, sizeof(data.comm)); bpf_probe_read_user_str(&data.filename, sizeof(data.filename), args->filename); events.perf_submit(args, &data, sizeof(data));}"""
b = BPF(text=bpf_text)
def print_event(cpu, data, size): event = b["events"].event(data) print(f"PID={event.pid} COMM={event.comm} FILE={event.filename}")
b["events"].open_perf_buffer(print_event)while True: b.perf_buffer_poll()| 优点 | 缺点 |
|---|---|
| Python 前端,开发快速 | 运行时编译,依赖 LLVM |
| 丰富的内置工具集 | 不支持 CO-RE,需目标内核头文件 |
| 适合运维脚本 | 性能不如编译型方案 |
| 社区成熟,工具丰富 | 生产部署需安装 LLVM + kernel headers |
二、libbpf:C 语言开发库
2.1 libbpf 的架构
libbpf 是 Linux 内核官方的 eBPF 用户态库,是 CO-RE 的基石:
2.2 libbpf 开发流程
# 1. 编写 eBPF 程序cat > hello.bpf.c << 'EOF'#include "vmlinux.h"#include <bpf/bpf_helpers.h>
struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 256*1024); } events SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_execve")int hello(void *ctx) { bpf_trace_printk("Hello from eBPF!", 18); return 0;}
char LICENSE[] SEC("license") = "GPL";EOF
# 2. 编译clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -c hello.bpf.c -o hello.bpf.o
# 3. 生成骨架bpftool gen skeleton hello.bpf.o > hello.skel.h
# 4. 编写用户态程序(包含 hello.skel.h)# 5. 编译用户态程序gcc -o hello hello.c -lbpf -lelf -lz
# 6. 运行sudo ./hello2.3 libbpf 骨架 API
// 骨架提供的核心 APIstruct hello_bpf *hello_bpf__open(void); // 打开struct hello_bpf *hello_bpf__open_and_load(void); // 打开+加载int hello_bpf__load(struct hello_bpf *skel); // 加载int hello_bpf__attach(struct hello_bpf *skel); // 附加void hello_bpf__destroy(struct hello_bpf *skel); // 销毁
// 访问 Map 和程序int fd = bpf_map__fd(skel->maps.my_map);int prog_fd = bpf_program__fd(skel->progs.my_prog);2.4 libbpf 的优缺点
| 优点 | 缺点 |
|---|---|
| CO-RE 原生支持 | C 语言开发体验 |
| 内核官方维护 | 需要手动管理骨架 |
| 最小依赖 | 错误处理不够友好 |
| 性能最优 | 缺乏高级抽象 |
三、cilium/ebpf:Go 语言开发库
3.1 cilium/ebpf 的架构
cilium/ebpf 是 Cilium 团队维护的 Go eBPF 库,通过 bpf2go 实现编译时代码生成:
3.2 cilium/ebpf 开发流程
package main
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -type event bpf hello.bpf.c -- -I/usr/include/bpf -D__TARGET_ARCH_x86
import ( "fmt" "os" "os/signal" "github.com/cilium/ebpf/ringbuf")
func main() { // 加载 eBPF 程序(bpf2go 生成的代码) objs := bpfObjects{} if err := loadBpfObjects(&objs, nil); err != nil { panic(err) } defer objs.Close()
// 附加到 tracepoint // ...
// 读取 Ring Buffer rd, err := ringbuf.NewReader(objs.Events) if err != nil { panic(err) } defer rd.Close()
fmt.Println("Tracing execve calls...")
sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt)
for { select { case <-sig: return case record := <-rd.Events(): event := (*bpfEvent)(unsafe.Pointer(&record.RawSample[0])) fmt.Printf("PID=%d COMM=%s\n", event.Pid, event.Comm) } }}3.3 bpf2go 代码生成
# 生成 Go 代码go generate ./...
# bpf2go 会生成以下文件:# hello_bpfel.go — Go 类型定义和加载函数# hello_bpfel.o — 编译后的 eBPF 字节码3.4 cilium/ebpf 的优缺点
| 优点 | 缺点 |
|---|---|
| Go 语言生态 | 编译时依赖 clang |
| 类型安全 | bpf2go 增加构建复杂度 |
| 丰富的 Map 操作 | 不支持所有 eBPF 特性 |
| Cilium 团队维护 | Go GC 可能影响实时性 |
四、Aya:Rust eBPF 框架
4.1 Aya 的架构
Aya 是纯 Rust 的 eBPF 框架,不依赖 libbpf:
4.2 Aya eBPF 程序
// eBPF 程序(Rust)#![no_std]#![no_main]
use aya_bpf::{ bindings::xdp_action, macros::xdp, programs::XdpContext,};use aya_log_ebpf::info;
#[xdp(name="hello_xdp")]pub fn hello_xdp(ctx: XdpContext) -> u32 { info!(&ctx, "received a packet"); xdp_action::XDP_PASS}
#[panic_handler]fn panic(_info: &core::panic::PanicInfo) -> ! { unsafe { core::hint::unreachable_unchecked() }}4.3 Aya 用户态程序
// 用户态程序(Rust)use aya::{Bpf, programs::Xdp, BpfLoader};use std::net::Interface;
fn main() -> Result<(), anyhow::Error> { // 加载 eBPF 程序 let mut bpf = BpfLoader::new().load_file("hello.o")?;
// 获取 XDP 程序 let program: &mut Xdp = bpf.program_mut("hello_xdp")?.try_into()?; program.load()?;
// 附加到网卡 let iface = Interface::from_str("eth0")?; program.attach(&iface, XdpFlags::default())?;
println!("XDP program attached to eth0");
// 事件循环 loop { std::thread::sleep(std::time::Duration::from_secs(1)); }}4.4 Aya 的优缺点
| 优点 | 缺点 |
|---|---|
| 纯 Rust,无 libbpf 依赖 | Rust 学习曲线 |
| 内存安全 | 生态不如 libbpf 成熟 |
| Cargo 集成 | 编译时间较长 |
| 宏简化开发 | 文档相对较少 |
五、bpftrace:高级追踪语言
5.1 bpftrace 的定位
bpftrace 是 eBPF 的高级前端,用类似 awk 的 DSL 编写追踪脚本:
# 一行命令追踪sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s → %s\n", comm, str(args->filename)); }'
# 脚本文件cat > trace_open.bt << 'EOF'#!/usr/bin/env bpftrace
tracepoint:syscalls:sys_enter_openat/comm == "myapp"/{ printf("PID=%d FILE=%s\n", pid, str(args->filename));}
tracepoint:syscalls:sys_exit_openat/comm == "myapp" && args->ret < 0/{ printf("FAILED: PID=%d RET=%d\n", pid, args->ret);}EOF
sudo bpftrace trace_open.bt5.2 bpftrace 的优缺点
| 优点 | 缺点 |
|---|---|
| 一行命令即可追踪 | 不适合复杂程序 |
| 无需编译 | 性能不如编译型方案 |
| 丰富的内置函数 | 不支持 CO-RE |
| 适合快速诊断 | 不适合生产级部署 |
六、框架选型指南
6.1 按场景选择
| 场景 | 推荐框架 | 原因 |
|---|---|---|
| 快速诊断/临时追踪 | bpftrace | 一行命令,无需编译 |
| 生产级网络程序 | libbpf | 性能最优,CO-RE 支持 |
| K8s/云原生开发 | cilium/ebpf | Go 生态,与 K8s 集成 |
| 安全工具开发 | Aya | Rust 内存安全 |
| Python 运维脚本 | BCC | Python 前端,快速原型 |
| 教学实验 | bpftrace | 最低学习门槛 |
6.2 按团队技能选择
| 团队技能 | 推荐框架 | 原因 |
|---|---|---|
| C 语言熟练 | libbpf | 最直接的映射 |
| Go 语言团队 | cilium/ebpf | Go 生态集成 |
| Rust 爱好者 | Aya | 纯 Rust 栈 |
| 运维工程师 | bpftrace/BCC | 脚本化,低门槛 |
框架选择不是互斥的——你可以在不同场景使用不同框架。用 bpftrace 做快速诊断,用 libbpf/cilium/ebpf 做生产级开发,这是最常见的组合。
七、动手实践
7.1 使用 libbpf 开发 eBPF 程序
# 使用 libbpf-bootstrap 快速开始git clone https://github.com/libbpf/libbpf-bootstrap.gitcd libbpf-bootstrap/c
# 编译make
# 运行sudo ./minimalsudo ./bootstrap7.2 使用 cilium/ebpf 开发 eBPF 程序
# 使用 ebpf-go-bootstrap 快速开始git clone https://github.com/cilium/ebpf-go-bootstrap.gitcd ebpf-go-bootstrap
# 编译go generate ./...go build -o app
# 运行sudo ./app7.3 使用 Aya 开发 eBPF 程序
# 安装 Aya 工具cargo install aya-tool
# 创建新项目cargo generate --name my-ebpf https://github.com/aya-rs/aya-template
cd my-ebpfcargo build --releasesudo ./target/release/my-ebpf八、开发工具链与调试
8.1 核心开发工具
| 工具 | 用途 | 安装方式 |
|---|---|---|
bpftool gen skeleton | 从 .o 文件生成骨架头文件 | apt install bpftool |
pahole | 生成 BTF 类型信息 | apt install dwarves |
llvm-objdump | 反汇编 eBPF 字节码 | 随 clang 安装 |
bpftool btf dump | 查看内核 BTF 信息 | apt install bpftool |
骨架头文件是 libbpf 开发的核心——它将 Map 定义和程序入口自动包装为结构体,避免手动管理文件描述符:
# 生成骨架头文件bpftool gen skeleton example.bpf.o > example.skel.h
# 在 C 代码中使用# #include "example.skel.h"# struct example_bpf *obj = example_bpf__open();# example_bpf__load(obj);# example_bpf__attach(obj);8.2 BCC Python 快速示例
BCC 的优势在于快速原型——几行 Python 就能实现内核追踪:
#!/usr/bin/env python3from bcc import BPF
prog = """#include <uapi/linux/ptrace.h>int trace_open(struct pt_regs *ctx, const char __user *filename) { bpf_trace_printk("open: %s\\n", filename); return 0;}"""b = BPF(text=prog)b.attach_kprobe(event="do_sys_openat2", fn_name="trace_open")b.trace_print()CO-RE 是 libbpf 和 cilium/ebpf 的基础能力。如果你的目标内核不支持 BTF(5.2 以下),只能使用 BCC 的运行时编译模式。生产环境建议内核 5.15+ 以获得完整的 CO-RE 支持。
九、本章小结
上一章剖析了Cilium 架构深入。 本章对比了四大 eBPF 开发框架:
| 主题 | 核心要点 | 关键词 |
|---|---|---|
| libbpf | C 语言,CO-RE 基石,性能最优,适合底层开发 | libbpf |
| cilium/ebpf | Go 语言,bpf2go 代码生成,适合云原生开发 | cilium/ebpf |
| Aya | Rust,纯 Rust 无 libbpf 依赖,内存安全 | Aya |
| bpftrace | DSL,一行命令追踪,适合快速诊断 | bpftrace |
选择框架的核心原则:快速诊断用 bpftrace,生产开发用 libbpf 或 cilium/ebpf,追求安全用 Aya。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






