/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 命令验证:
| |
如果没有挂载,你需要手动挂载才能使用 BPF 的文件系统特性:
| |
实际工作流程示例
假设你编写了一个 BPF 程序来统计网络包:
- 加载: 你的用户空间加载器(Loader)编译并加载 BPF 程序到内核。
- 创建 Map: 程序创建了一个
hash map用来存储 IP 地址和计数。 - Pin(关键步骤):
- 加载器调用系统调用,将 Map 钉在
/sys/fs/bpf/my_stats_map。 - 将程序钉在
/sys/fs/bpf/my_xdp_prog。
- 加载器调用系统调用,将 Map 钉在
- 进程退出: 加载器完成任务后退出了。
- 结果:
- 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:直接将编译好的对象文件加载并钉住。