Rust 系列 01: eBPF 和 Rust 简介
本文地址:https://www.ebpf.top/post/01-intro-into-ebpf-and-rust
在本文中,我们将探讨如何使用 eBPF 和 Rust 构建高性能的 Linux 追踪工具。无论你是刚刚开始接触 eBPF,还是对如何将 eBPF 与现代 Rust 工具集成感到好奇,你都可以在本文有所收获。本文将带你了解 eBPF 基础知识、Rust 开发工具选择以及实际的系统调用追踪示例。
1. 什么是 eBPF?
eBPF 是一种允许开发者在 Linux 内核中运行安全的沙盒程序技术,而无需修改内核源代码或加载内核模块。通常这些运行程序会用于网络、可观测性和安全性。
eBPF 程序在内核中运行于一个受限的虚拟机中(类似于 WebAssembly (WASM) 或 JVM 在用户空间中的运行方式),这种运行机制确保 eBPF 程序运行时候的行为,从而保障安全性。
eBPF 技术的最大优势之一是它可以动态扩展内核行为,而无需等待上游补丁或重启系统。由于其运行在内核上下文中,因此可以最小的开销来进行系统观察和事件响应,这非常适合高性能的追踪、过滤和执行任务的场景。可以在 ebpf.io 找到更深入的解释。
2. 为什么选择 Rust?
Rust 是一种强大的现代系统编程语言,本身没有垃圾回收机制,专注于性能、内存安全和并发性。Rust 非常适合与 eBPF 技术组合使用,原因有以下两点:
- 内存安全:与 C 不同,Rust 语言可以防止常见的错误,如空指针解引用、缓冲区溢出和使用后释放,这些预发机制与低级内核接口交互时至关重要。
- 强大的工具链:像 Cargo、rust-analyzer、clippy 和 libbpf-cargo 等工具使得在 Rust 中构建和测试 eBPF 应用程序变得无缝衔接。
3. 可用的 Rust 库
3.1 libbpf-rs
-
安全且符合 Rust 习惯的包装器,底层基于广泛使用的 C 语言 libbpf 库。
-
其开发设计和维护者与 C 语言 libbpf 相同。
-
与
bpftool
和libbpf-cargo
集成良好。 -
仍然需要用 C 编写 eBPF 程序,但可用 Rust 编写用户空间加载器。
-
最佳场景:生产级系统、高兼容性和需要 CO-RE(Compile Once Run Everywhere)机制。
3.2 Aya
-
纯 Rust 的 eBPF 工具链,可基于 Rust 编写用户空间和 eBPF 程序。
-
不依赖 C 或 clang 工具链。
-
支持 XDP、tracepoint、uprobes、perf buffer、map 等。
-
仍在快速成熟中,但有强大的社区支持。
-
最佳场景:全 Rust 工具链、嵌入式场景友好,且具有更好的移植性。
链接:Aya 官网
4. 为什么选择 libbpf-rs
在深入探讨 libbpf-rs 之前,我们需要先理解 libbpf 本身的作用和重要性。
4.1 什么是 libbpf?
当你编写和编译 eBPF 程序时,通常会依赖内核数据类型的内部结构。然而,这些内核数据结构可能在不同 Linux 版本之间发生变化——例如,Linux 内核版本 5.6 中的某个结构体可能在 Linux 6.1 版本中增加了一个字段或调整了布局。不同内核版本数据结构调整可能导致编写的 eBPF 程序行为异常,甚至无法通过新内核的验证。
为了解决这个版本兼容性问题,libbpf 库(通常被称为 eBPF 加载器)引入了 CO-RE(Compile Once, Run Everywhere)能力。CO-RE 允许编译一次 eBPF 程序,然后在不同内核版本上运行,而无需重新编译。
4.2 CO-RE 的工作原理
CO-RE 底层依赖 vmlinux.h
,这是一个包含当前运行内核所有类型定义的头文件,该头文件可通过工具 bpftool 生成:
|
|
vmlinux.h
文件依据内核的 BTF(BPF Type Format)数据生成,我们可在 .bpf.c
程序中安全且可移植地访问内核结构。
当 eBPF 程序加载时,libbpf 库会检查程序访问的字段,并确保它们与实际运行内核中的数据布局对齐—即使相关的结构体在当前运行的内核中已经发生了变化。CO-RE 机制通过加载时的重定位逻辑实现这一点,使得程序能适配不同内核版本而无需硬编码特定布局。如需技术细节,推荐阅读 Andrii Nakryiko 关于 CO-RE 的博客文章。
4.3 为什么选择 libbpf-rs
我最初尝试了使用纯 Rust 工具链实现的 aya-rs 库,其允许我们使用 Rust 语言编写用户态程序和内核中 eBPF 程序。
Aya 设计非常现代,但在使用某些 eBPF 辅助函数时,我遇到了校验器拒绝价值的错误。经过调查和社区反馈(GitHub Issue),我发现这些问题源于 Rust 编译器(rustc)与 CO-RE 的兼容性问题。由于 Aya 针对 CO-RE 支持尚不成熟,且依赖实验性的 Rust LLVM 特性,因此决定转向 libbpf-rs,这是因为使用 libbpf-rs 不仅可基于稳定 libbpf C 库,还可使用符合 Rust 语言习惯的良好封装。
4.5 为什么 libbpf-rs 适合我的场景
libbpf-rs 提供一流的 CO-RE 机制支持,意味着可移植性是内置的。其次使用 libbpf-rs 库,我可用 C 语言编写 eBPF 程序,这与大多数生产级开源项目(如 Cilium、Tracee 等)保持一致。
同时,围绕 eBPF 的生态系统和文档大多是使用或者假设使用 C 语言编写的。使用 C 语言编写 eBPF 程序更容易基于现有实例扩展,更容易让人理解和沟通交流。
而用户空间逻辑使用 Rust,可让我在依赖稳定的内核交互基础的同时,享受 Rust 生态系统的安全性和工具生态。
5. eBPF + Rust:架构图
以下是 eBPF 程序与 Rust 用户空间应用集成的架构概览:
-
编写 eBPF 程序及配套的 Rust 用户空间代码;
-
编译源代码生成 eBPF 字节码和 Rust 二进制文件;
-
运行用户空间二进制程序;
-
用户空间程序尝试将 eBPF 字节码加载至内核;
-
eBPF 验证器检查程序是否满足内核安全要求;
-
若验证失败则拒绝加载,验证通过则可通过 JIT 编译将字节码转为机器指令,并加载到内核;
-
待 eBPF 程序加载完成后,用户空间程序将 eBPF 程序挂载到相关内核挂载点上;
-
Rust 用户空间与内核空间的通信通过 eBPF Maps 实现;
我们将在后续文章中通过实践案例深入探讨这些核心机制。
6. 使用样例:用 eBPF 和 Rust 追踪系统调用
介绍了基础知识后,让我们通过追踪 Linux 系统上系统调用示例来进行展示。项目结构如下所以,在运行之前,请确保你的系统满足 eBPF 的前提条件,完整代码参见 GitHub 仓库。
|
|
以下是跟踪系统调用时输出输出的样例:
7. 结论
无论你是在构建可观测性工具,还是在实验内核内部机制,eBPF + Rust 都提供了一个强大且安全的基础。而我们才刚刚开始。
原文地址:https://dev.to/maheshrayas/-intro-into-ebpf-and-rust-l2m
作者:Mahesh Rayas
- 原文作者:DavidDi
- 原文链接:https://www.ebpf.top/post/01-intro-into-ebpf-and-rust/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 最后更新时间:2025-08-09 18:19:37.390266794 +0800 CST

