基本原理

用 Rust 写 eBPF 程序本质上还是依赖于 libbpf 的能力。因为 Rust 的 FFI 可以使用 C 的 ABI,所以 libbpf 所暴露的 API 可以以 Library 的形式直接给 Rust 调用。

实际上,libbpf-sys 这个项目就给 libbpf 包了一层 Rust 的 API,目前几个用 Rust 写 eBPF 程序的项目都是基于这个项目再实现更上层的 API。

我们可以直接用 libbpf-rs 这个项目,这个项目是 libbpf 项目开发的一个未成熟的项目。这个项目其实是由两个子项目组成(通过 Cargo Workspace 来组织):

  • libbpf-cargo Cargo 的子命令 libbpf,编译将生成一个 binary。这个子命令主要用来编译 *.bpf.c 和自动生成 skeleton 代码;

  • libbpf-rs 基于 libbpf-sys 开发的更上层的接口。

用 Rust 写 eBPF 程序其实只是用更上层和抽象的 API 来写用户层代码,内核加载的 eBPF 程序还是用 C 语言编写(一般这些代码比较短,逻辑相对简单很多)。

工作流程

备注:内核要支持 CO-RE 特性。

1. 安装 Cargo 的子命令 libbpf-cargo

Cargo 提供了子命令扩展机制。扩展的子命令本质上也是一个独立的 binary,也可以托管在 crates.io 上:

1
$ cargo install libbpf-cargo

安装完之后,我们可以执行:

1
$ cargo --list

看是否有 libbpf 这个命令。

2. 创建一个 Rust 项目

1
2
$ cargo new runqslower-rs
$ mkdir -p runqslower-rs/src/bpf

bpf/ 下创建 runqslower.bpf.c ,并补充对应的 headers(可参考 examples),最终的目录结构如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
runqslower-rs/
├── Cargo.lock
├── Cargo.toml
└── src
    ├── bpf
    │   ├── runqslower.bpf.c
    │   ├── runqslower.h
    │   ├── vmlinux.h
    │   └── vmlinux_505.h
    └── main.rs

注意 *.bpf.c 的后缀是必须的,表示这是一个将要被内核加载的 eBPF 程序

3. 使用 libbpf 子命令来编译 eBPF 程序并生成 skeleton 代码

1
2
3
4
5
6
7
8
9
# 编译 runqslower.bpf.c
$ cargo libbpf build 

# 编译生成的 *.o
$ ls -all target/bpf/runqslower.bpf.o

# 生成 skeleton 代码
# 将在 src/bpf/ 下生成 mod.rs 和 runqslower.skel.rs
$ cargo libbpf gen

4. 编译用户层代码

main.rs 添加对应的逻辑,可参考 main.rs

5. 编译完整代码并执行

1
$ cargo build

此时完整的命令行 runqslower-rs 已经通过 skeleton 代码将对应的 object 嵌入了,所以直接执行就完成加载和运行的动作:

1
2
3
$ sudo ./target/debug/runqslower-rs
Tracing run queue latency higher than 10000 us
TIME     COMM             TID     LAT(us)