如何理解sys-fs-bpf

/sys/fs/bpf 是 Linux 内核中 BPF 文件系统 的挂载点。简单来说,它是 BPF 对象(如 Maps 和 Programs)在用户空间文件系统中的“家”

持久化与命名空间化

在 BPF 文件系统出现之前,BPF 程序和 Map 的生命周期完全依赖于持有其文件描述符的进程。如果创建 BPF Map 的进程退出了,Map 也就随之销毁了。

/sys/fs/bpf 解决了这个问题,它提供了两个核心能力:

  • 持久化: 当将一个 BPF Map 或 BPF 程序 pin 到这个文件系统中时,即使创建它的进程结束,只要内核还在,这个对象就会一直存在。其他进程可以随后通过读取该文件来获取对象的句柄,从而实现数据的共享和程序的复用。
  • 命名空间隔离: 它遵循文件系统的权限模型和命名空间规则。不同的容器或用户可以在各自的目录下管理 BPF 对象,避免了冲突,也增加了安全性。

Pin

当用户空间程序加载一个 BPF 程序或创建一个 Map 时,内核会在内存中创建相应的数据结构,并返回一个文件描述符给用户空间。

默认情况下,当 fd 关闭(进程退出)时,内核引用计数归零,对象被销毁。

用户空间程序可以调用 bpf_obj_pin() 系统调用,将这个 fd 与 /sys/fs/bpf/ 下的一个路径绑定:

  • 这会在该路径下创建一个文件(这就是你看到的那些文件)。
  • 内核会增加该对象的引用计数。
  • 只要该文件存在,对象就不会被内核销毁。

目录下的文件是什么?

/sys/fs/bpf 下执行 ls -l 时,看到的文件实际上是 内核 BPF 对象的句柄

  • Map 文件: 代表一个 Key-Value 存储结构。多个 BPF 程序可以共享同一个 Map 文件来交换数据(例如:一个程序统计流量,另一个程序读取统计数据)。
  • Program 文件: 代表编译好的 BPF 字节码。可以被附加到 Hook 点(如 XDP, Tracepoint, TC 等)上执行。

它们不存储文本或二进制数据流,无法用 cat 读取 Map 里的内容(虽然可以用 bpftool 命令操作)。

自动挂载机制

在现代 Linux 发行版中,Systemd 通常会自动挂载 bpffs。你可以通过 mount 命令验证:

1
2
3
mount | grep bpf
# 输出通常类似:
# bpffs on /sys/fs/bpf type bpf (rw,relatime)

如果没有挂载,你需要手动挂载才能使用 BPF 的文件系统特性:

1
mount -t bpf none /sys/fs/bpf

实际工作流程示例

假设你编写了一个 BPF 程序来统计网络包:

  1. 加载: 你的用户空间加载器(Loader)编译并加载 BPF 程序到内核。
  2. 创建 Map: 程序创建了一个 hash map 用来存储 IP 地址和计数。
  3. Pin(关键步骤):
    • 加载器调用系统调用,将 Map 钉在 /sys/fs/bpf/my_stats_map
    • 将程序钉在 /sys/fs/bpf/my_xdp_prog
  4. 进程退出: 加载器完成任务后退出了。
  5. 结果:
    • BPF 程序依然在网卡上运行(如果已附加)。
    • Map 里的数据依然在内存中。
    • 另一个全新的监控程序可以打开 /sys/fs/bpf/my_stats_map,拿到 fd,读取里面的统计数据。

运维和开发人员通常使用 bpftool 来与这个目录交互,而不是直接操作文件。

  • bpftool map list:列出系统中所有的 Map。
  • bpftool map pin id <ID> /sys/fs/bpf/my_map:将一个 Map 钉入文件系统。
  • bpftool prog load my.bpf.o /sys/fs/bpf/my_prog:直接将编译好的对象文件加载并钉住。
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计