BPF-eBPF 开发路线二:libbpf、CO-RE 与 libbpf-bootstrap 实战

一个人知道自己为什么而活,就可以忍受任何一种生活。——尼采

写在前面


  • 如果说 BCC 更偏“先跑起来”,那 libbpf 更偏“把 eBPF 真正学扎实”
  • 这一篇会尽量贴近 libbpf-bootstraplibbpf 官方文档的学习脉络
  • 重点放在 CO-REBTF、对象装载、示例项目阅读顺序和最小改造方法

一个人知道自己为什么而活,就可以忍受任何一种生活。——尼采


系列导航

  1. BPF-eBPF 学习总览:从概念、机制到工具链选择
  2. BPF-eBPF 实战入门:环境准备、最小实验与排错思路
  3. BPF-eBPF 开发路线一:BCC 入门、工具使用与自定义脚本
  4. BPF-eBPF 开发路线二:libbpf、CO-RE 与 libbpf-bootstrap 实战
  5. BPF-eBPF 开发路线三:ebpf-go、bpf2go 与 Go 工程集成

为什么说 libbpf 是主干路线

现在很多生产级 eBPF 程序,本质上都绕不开 libbpf 这一层。

原因很简单:

  • 它贴近内核原生生态
  • CO-RE 支持成熟
  • 对对象加载、attach、map 管理、link 生命周期都有比较标准的组织方式
  • 后续无论你走 C 路线还是 Go 路线,理解 libbpf 都会让你更清楚底层到底发生了什么

开始学 libbpf,先看什么

如果你直接啃 libbpf 全量文档,很多初学者会被细节压垮。更稳妥的顺序是:

  1. 先看 libbpf-bootstrap
  2. 先跑 examples/c/minimal
  3. 再跑 examples/c/bootstrap
  4. 再去看 kprobeuprobefentryusdt
  5. 最后结合 libbpf.readthedocs.io 查概念和 API

也就是说,不建议一开始就“API 背诵式学习”,而建议先从一个能运行的标准工程骨架切入。

libbpf-bootstrap 为什么适合入门

https://github.com/libbpf/libbpf-bootstrap 这个项目最有价值的地方在于,它不是只给你一段零散代码,而是给你一整套更接近真实项目的组织方式。

它通常会让你接触到这些关键点:

  • .bpf.c 内核态程序文件
  • 用户态加载程序
  • skeleton 生成
  • ring buffer 事件传递
  • vmlinux.h
  • CO-RE

对初学者来说,这比“只会 attach 一个 probe”更完整。

建议先跑哪些示例

1. minimal

这是最适合理解“对象最小形态”的例子。

重点看:

  • eBPF 程序怎么写在 .bpf.c
  • 用户态怎么 open/load/attach
  • 一个最小 skeleton 是怎么参与构建的

2. bootstrap

这是最值得精读的示例之一。

重点看:

  • 命令行参数
  • ring buffer 事件读取
  • 结构体数据怎么从内核态传给用户态
  • 程序如何优雅退出

3. kprobe / uprobe

这两个例子最适合帮助你建立“内核态函数”和“用户态函数”被观测时的差异感。

4. xdp / tc

如果你明确要走网络方向,再往这两个例子深入会更有价值。

一次典型的 libbpf 开发流程

你可以把它概括成下面几步:

  1. 编写 .bpf.c
  2. 通过 clang 编译出 eBPF 对象文件
  3. 生成 skeleton
  4. 用户态程序加载对象
  5. attach 到 hook
  6. 读取 map 或 ring buffer 数据

这套流程里,最关键的理解点是:

  • .bpf.c 不等于整个程序
  • 真正交付的是“内核态对象 + 用户态加载器”的组合
  • 用户态要负责对象生命周期、attach 生命周期和数据读取

CO-RE 到底解决了什么问题

CO-RE 本质上是为了解决“内核结构体和布局会变”的问题。

如果没有它,你可能会遇到这些情况:

  • 本机能跑,换内核就挂
  • 结构体偏移变了,程序读错字段
  • 需要为不同发行版维护多份对象文件

CO-RE 的基本思想是:

  • 编译时保留足够的类型重定位信息
  • 运行时根据目标内核的 BTF 去调整访问

所以学 libbpf 时,CO-RE 不是“进阶选修”,而几乎是现代 eBPF 开发的基础配置。

vmlinux.h 在这里扮演什么角色

很多初学者看到 vmlinux.h 会以为这是某种普通头文件,其实它更像是:

  • 当前目标内核类型信息的 C 头表示
  • 让你的 eBPF 程序能够引用内核结构体和字段

常见生成方式:

1
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

注意这里也再次说明了一个现实:

  • 现代 eBPF 开发已经越来越依赖 BTF
  • 不理解 vmlinux.hBTF,后面会很难继续走深

一个很实用的学习动作:改 bootstrap 示例

不要停留在“我把示例跑起来了”,建议至少做一次小改动。

比如:

  1. 给事件结构体多加一个字段
  2. .bpf.c 里填充这个字段
  3. 在用户态打印出来

这个小动作能强迫你真正理解:

  • 内核态和用户态如何共享结构体定义
  • ring buffer 里到底传了什么
  • 修改后为什么需要重新编译对象和 skeleton

学 libbpf 时一定要理解的几个术语

object

表示一组已加载或待加载的 BPF 程序与 map 的集合。

skeleton

是围绕 BPF 对象自动生成的辅助封装,目的是降低样板代码量,让 open/load/attach 等流程更标准。

表示 attach 之后形成的连接对象。很多现代 attach 流程都会强调 link 生命周期管理。

ring buffer

常见于事件流场景,用来把内核态事件高效送到用户态。

libbpf 这条路线容易踩的坑

1. 以为“编译成功”就等于“能跑”

真正的问题常常发生在:

  • load
  • attach
  • verifier
  • 运行时字段访问

2. 不理解 section 命名

不同 SEC("...") 对应不同 program type 和 attach 语义,不能随便写。

3. 不重视 BTF/BTF 缺失

很多问题最后都追溯到:

  • 目标机没有可用 BTF
  • vmlinux.h 不匹配
  • 没有按 CO-RE 的方式组织对象

4. 只会抄示例,不会做最小改动

如果你跑通示例后完全不敢动它,那说明你还没真正进入可开发状态。

我建议的 libbpf 学习节奏

  1. minimal
  2. bootstrap
  3. 精读 bootstrap
  4. 改一个事件字段
  5. 再读 libbpf 文档中和 map、program、link、CO-RE 相关的部分
  6. 最后才去补更底层的 API 细节

这条路径通常比“从 API 文档第一页开始通读”更高效。

最后总结

如果你想真正掌握 eBPF,libbpf 几乎是绕不过去的一步。

因为它逼着你直面这些核心问题:

  • eBPF 对象是怎么组织的
  • 为什么现代开发强调 CO-RE
  • 内核态和用户态到底如何配合
  • 程序 attach 之后生命周期怎么管理

把这几个问题吃透之后,你对 eBPF 的理解会从“会跑 demo”进入“能写应用”的阶段。

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)



© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

发布于

2026-04-14

更新于

2026-04-14

许可协议

评论
加载中,最新评论有1分钟缓存...
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×