1. 错误初体验:报错现场与含义拆解
搞 XDP 开发或者调试的人,大概率都撞见过这行报错:
libxdp: No bpffs found at /sys/fs/bpf我第一次遇到这个错误的时候,完全是一头雾水。当时我在一个干净环境里跑一个基于 AF_XDP 的抓包程序,编译顺利通过,结果一执行直接崩了,就留下这么一句。我当时第一反应是"bpffs 是什么?"——那个报错信息确实不算友好,字面上只说在/sys/fs/bpf这个路径下没找到 BPF 文件系统,但对初学者来说,根本不知道这一步到底是干嘛的、挂载没挂载会影响什么。
先说结论:这个错误是 XDP 程序加载时,libxdp 库默认要往 BPF 文件系统(bpffs)里写入或读取 pinned(固定)的 BPF 对象,结果发现这个文件系统没有挂载,或者挂载位置不对,于是直接罢工。
"bpffs" 的全称是 BPF Filesystem,也就是 BPF 文件系统。它和/proc、/sys这种虚拟文件系统是同类,专门用来让 BPF 对象(程序、map)在内核中有一个"可命名的持久化入口"。XDP 程序作为 BPF 家族的一员,自然也需要依赖它来管理生命周期。
很多人会疑惑:我跑个 XDP 程序,明明直接用ip link set dev eth0 xdp obj xxx.o就能加载,为什么还需要 bpffs?这里的关键在于 libxdp 的工作方式。libxdp 是内核 XDP 程序的"高级封装库",它需要把程序固定(pin)到一个文件路径下,通常就是/sys/fs/bpf下的某个子目录。固定的作用是让加载过后的程序不会因为进程退出而被内核自动回收。如果你的内核开了 bpffs 但没挂载,那pin动作就会失败,于是 libxdp 在初始化阶段就报出这个错误。
我还遇到过一种更隐蔽的情况:bpffs 实际是挂载了的,但它挂在了别的路径下,比如/sys/fs/bpf是个空目录,而真正的 bpffs 被挂到了/run/bpffs。这种情况下 libxdp 同样找不到/sys/fs/bpf,报错一模一样。所以排查时不能只检查"有没有挂载",还要检查"挂载到了哪里"以及"默认路径是否有效"。
这个报错还有一个特别坑的地方:它通常不是出现在你预期的时刻。比如你加载 BPF 程序的进程明明是 root 权限,错误还是会冒出来,因为问题根子不在权限,而在文件系统挂载状态。我记得在某次测试里,我用一个只包含 XDP 加载逻辑的小程序复现,程序本身刚写好、代码逻辑没有任何问题,但就是因为宿主机的/sys/fs/bpf没挂载,卡了我整整半天。
如果你是刚接触 XDP,撞上这个错误时不要慌。它是个"环境配置类"问题,解决起来比程序逻辑 bug 要简单得多——你不需要改一行代码,只需要把环境整理好。下面我会从原理到实操,把 bpffs 的来龙去脉彻底讲清楚,再给你一套直接能用的挂载方案。
2. BPF 文件系统的作用与原理:为什么 XDP 离不开它
2.1 虚拟文件系统的使命:让内核对象"看得见、摸得着"
要真正理解 bpffs 的价值,得先想明白一个问题:内核里的 BPF 程序和组织数据,本质上是一串存在于内核内存空间的结构体。进程退出后,这些结构体如果没有被"固定"下来,就会被引用计数归零而销毁。对于安全、网络、可观测性这些需要长期在后台运行的 BPF 程序来说,这是不可接受的——程序得持续运行,而用户态的工具(比如bpftool)也得能够随时定位到它们。
这就好比你在公司实验室里跑一个长期任务,任务本身依附于某个终端会话。一旦关掉终端,任务就被杀了。解决办法是给任务一个"独立的守护方式",让它变成常驻服务,同时挂一个"名牌"在某个固定的位置供人查找。bpffs 就是那块"名牌"所在的位置——它让内核里的 BPF 对象有了一个全局可见的路径,用户态可以按路径打开、读取、更新,哪怕最初的加载程序早退出了。
在 Linux 上,/sys/fs/bpf是 bpffs 的约定俗成挂载点,它是在内核 4.4 版本左右全面铺开的。系统里任何进程都可以通过这个路径访问被 pin 下来的 BPF 对象(前提是权限足够)。XDP 程序通常也会采用"加载后 pin 到/sys/fs/bpf/xdp/下"的做法,就是为了跨进程复用和查找方便。
2.2 libxdp 与 bpffs 的关系
libxdp 库的定位是"为 XDP 程序提供高级操作能力"。它的核心功能之一,是你可以在用户态创建多个 XDP 程序,并把它们 attach 到同一个网络设备上,程序之间还能按照优先级组合成链。这种灵活性很大程度上依赖于每个程序都有稳定的身份标识——不然的话,程序 A 和程序 B 都 attach 在同一张网卡上,后面管理、更新、单独 detach 的时候怎么区分?
答案是:每个程序加载后都会以 BTF ID 或路径标识的方式,被记录在 bpffs 中。libxdp 在初始化和程序 attach 的过程中,默认会尝试在/sys/fs/bpf下创建与 XDP 相关的子目录和对象引用。一旦 bpffs 缺失,这些动作就会抛错,而不是"悄悄跳过"——因为跳过了就意味所有高级管理功能都不可用。所以对 libxdp 来说,bpffs 不是锦上添花,而是硬性依赖。
你可以把这个过程类比成:你往一个储物柜里存放贵重物品,储物柜必须有编号和抽屉;如果整个柜子都不存在,你就没法完成寄存。/sys/fs/bpf就是这个柜子。
2.3 为什么系统默认不挂载 bpffs
很多发行版(尤其是精简版)默认不会挂载 bpffs。原因主要有几个:
- 节省初始化开销,减少虚拟文件系统的挂载数量。
- 部分场景下,系统并不运行任何 BPF 程序,挂载了也就闲置。
- 在容器环境里,挂载点权限和路径隔离需要额外配置,默认不挂反而更安全。
这就是为什么你装好系统、配好内核之后,敲mount | grep bpf可能得到一片空白。注意,bpffs这个文件系统类型在内核配置里通常写作CONFIG_BPF_FS,从名字上容易让人误以为它是和debugfs一样的调试专用文件系统,从而忽略它对 XDP 的硬依赖。我在配置内核的时候也犯过这个糊涂。
3. 分步解决:从临时挂载到开机自启的完整实操
3.1 第一步:确认当前状态
拿到报错后的第一件事,是确认系统里到底有没有挂载 bpffs、挂载到了哪个路径、内核是否支持:
# 查看所有已挂载的 bpf 文件系统 mount | grep bpf # 检查默认路径是否存在 ls -la /sys/fs/bpf/ # 检查内核是否支持 BPF 文件系统 cat /proc/filesystems | grep bpf三个命令配合看,基本能把问题定位清楚:
mount | grep bpf没输出,说明 bpffs 没挂载。ls /sys/fs/bpf如果目录存在但内容是空的,说明目录在但文件系统没挂上来。/proc/filesystems里如果没有bpf这个 nodev 文件系统,说明内核没开CONFIG_BPF_FS,这种属于内核配置问题,通常出现在自编译内核或极小化裁剪系统里——不过现在主流发行版默认内核都会带,概率极低,我至今只在某 ARM 开发板的自制内核上遇到过。
3.2 第二步:立即临时挂载
确认只是没挂载之后,一条命令解决问题:
sudo mount -t bpf bpf /sys/fs/bpf这条命令的含义是:把类型为bpf的文件系统,挂载到/sys/fs/bpf目录下。这里要注意几点:
- 如果
/sys/fs/bpf目录还不存在,需要先创建:sudo mkdir -p /sys/fs/bpf - 挂载这个文件系统需要 root 权限,普通用户不行。
- 挂载之后,再用
ls /sys/fs/bpf,应该能看到目录存在了。此时重新运行你的 XDP 程序,报错大概率就消失了。
不过临时挂载意味着:重启机器之后又失效了。对开发调试来说,临时挂载够用,但要进生产、要长期跑,必须做成持久化配置。这也是我在实践中特别强调的一步——很多人在本机 mount 完,测试通过就以为完工了,结果一次重启后又看到熟悉的No bpffs found。
3.3 第三步:配置开机自动挂载
把挂载做成开机自动生效,最简单的方法是使用 systemd 的挂载单元。我比较推荐这种方式,因为它不依赖 init 脚本的执行顺序,失败还会触发 systemd 的依赖重试机制。
创建一个挂载单元文件:
# /etc/systemd/system/sys-fs-bpf.mount [Unit] Description=BPF File System DefaultDependencies=no Before=local-fs.target [Mount] What=bpf Where=/sys/fs/bpf Type=bpf Options=rw [Install] WantedBy=local-fs.target然后执行:
sudo systemctl daemon-reload sudo systemctl enable --now sys-fs-bpf.mount用 systemd 而不是直接改/etc/fstab,是因为 fstab 里关于 bpffs 的挂载在某些老系统上偶尔会被systemd-remount-fs干扰,而且不提供故障自愈的语义。这里用 systemd 单元,Before=local-fs.target能让它在文件系统服务出现之前完成挂载,后续任何依赖 bpffs 的服务启动时,目录已经就绪。
如果你确实想用 fstab,可以这样写:
# /etc/fstab bpf /sys/fs/bpf bpf rw 0 0然后执行sudo systemctl daemon-reload && sudo mount -a验证。两种方式在绝大多数情况下都能生效。我个人首选 systemd 单元,因为可管理、可查看状态(systemctl status sys-fs-bpf.mount)、出错时日志更清楚。
3.4 第四步:处理挂载点被占用或权限异常的情况
还有一种少见但真实存在的情况:/sys/fs/bpf已经被挂为其他文件系统,或者目录权限不对。判断方法:
stat -f /sys/fs/bpf如果输出里显示的文件系统类型是bpf,说明挂载成功;如果显示的是别的类型(比如sysfs或tmpfs),说明有别的 fs 占用着这个目录。
解决方法也直接:先卸载冲突挂载,再重新挂载 bpffs:
sudo umount /sys/fs/bpf sudo mount -t bpf bpf /sys/fs/bpf如果卸载时报target is busy,说明有进程正握着这个目录的文件句柄。可以先找出来:
sudo lsof /sys/fs/bpf确认没有重要进程依赖后再卸载。实际操作中,systemd的某个单元或者正在运行的bpftool prog tracelog都有可能占用。在测试环境里可以直接重启系统再挂载,生产环境建议谨慎处理,先停掉相关服务,再操作。
3.5 第五步:运行验证与持续观测
挂载完成不等于万事大吉。我建议你跑一遍完整流程,确认 XDP 程序能够正常 attach,pin 操作也生效:
# 加载一个最简单的 XDP 程序(drop 模式) sudo xdp-loader load -p /sys/fs/bpf/xdp eth0 obj drop.o # 确认加载状态 sudo xdp-loader status eth0 # 查看 bpffs 里的内容 ls -la /sys/fs/bpf/xdp/在开发中,我还会额外检查一件事: bpffs 的挂载选项 。默认rw挂载就够用,无需额外选项,但有些加固系统会把 bpffs 挂载成只读(ro)或者加nosuid、nodev等选项。如果后面遇到"明明挂载了还是报错"的情况,记得用mount | grep bpf看一下选项里有没有ro。只读 bpffs 下,XDP 程序的 pin 操作同样会失败,报错也可能还是No bpffs found at /sys/fs/bpf——不过严格来说,这只出现在某些高安全配置的服务器上,比较罕见。
4. 容器环境与特殊场景:报错的变体与应对
4.1 容器里跑 XDP:最容易踩的坑
随着云原生环境普及,不少同学是在容器里做 XDP 开发的。容器里遇到No bpffs found的概率比宿主机大得多,而且原因更复杂。
首先,容器默认会隔离 mount namespace。如果你在容器里看到/sys/fs/bpf是空的或者不存在,不要以为宿主机就是坏的。在宿主机上跑一下mount | grep bpf,可能一切都正常。容器里之所以没有,是因为创建容器时没有把宿主机的 bpffs 挂载点显式共享进来。
解决方法取决于你用的容器运行时:
- Docker:启动时加
-v /sys/fs/bpf:/sys/fs/bpf,把宿主机的挂载点共享进容器。 - Podman:参数类似:
--volume /sys/fs/bpf:/sys/fs/bpf。 - Kubernetes:需要在 Pod 的 volume 和 volumeMount 配置中显式声明 hostPath
/sys/fs/bpf。
有一个细节值得注意:容器内看到/sys/fs/bpf存在,并不代表真的可用。某些容器运行时会在默认配置里做一个"空目录挂载"或"只读挂载",导致容器内的路径是存在但不可写,或者指向全新的虚假空目录。验证方法是在容器里执行:
touch /sys/fs/bpf/test ls /sys/fs/bpf/test如果 touch 报权限错误或者目录不可写,说明挂载方式不对。
4.2 多 BPF 文件系统共存的管理方案
如果你的主机上跑着多个独立 BPF 子系统(比如 Cilium 的 Cluster BPF fs 和 XDP 程序的 BPF fs),或者你自己习惯把 bpffs 挂在/run/bpffs,就会出现"有 bpffs 但 libxdp 找不到"的怪象。
Libxdp 默认只认/sys/fs/bpf,这是编译期就写死的行为。在早期版本中没有环境变量可配置路径,但较新版本支持通过LIBXDP_BPFFS环境变量指定路径。如果你不想改代码、也不想重编译,可以试试:
export LIBXDP_BPFFS=/run/bpffs不过在路径上做文章始终是"绕路走",我更推荐直接统一挂载到/sys/fs/bpf。毕竟 bpffs 的默认挂载路径了已经成为整个 BPF 生态的共识,很多工具、库、守护进程都硬编码依赖它,你改了一处,可能下一个工具又找不到。减少心智负担的最佳方式就是遵循约定。
4.3 自编译内核场景下芯片架构差异导致的坑
还有一些嵌入式场景或者自编译内核的场景下,CONFIG_BPF_FS没开启。验证方法:
cat /proc/filesystems | grep bpf如果这个检查没有任何输出,那问题不是"没挂载",而是"内核不支持",解决思路完全不同。
处理方式是重新配置内核选项:
CONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_BPF_FS=y重新编译内核、安装、重启后,再回到章节 3 操作挂载流程。同时,这一组选项通常也硬依赖着其它配置,比如CONFIG_BPF_JIT、CONFIG_NET_CLS_BPF等。如果是为某个开发板编译内核,建议直接把它在发行版默认内核里的相关 config 完整复制过来,避免漏项。
5. 常见问题与排查技巧实录
5.1 快速诊断清单
每次遇到No bpffs found at /sys/fs/bpf,我会按照下面这个顺序排查,从来没翻过车:
| 排查步骤 | 命令/动作 | 预期结果 |
|---|---|---|
| 内核是否支持 | cat /proc/filesystems | grep bpf | 有nodev bpf输出 |
| 是否已挂载 | mount | grep bpf | 有/sys/fs/bpf行 |
| 挂载类型正确 | stat -f /sys/fs/bpf | 文件系统类型为bpf |
| 挂载选项正确 | mount查看选项 | 没有ro,通常是rw |
| 容器内可用性 | 容器里touch /sys/fs/bpf/test | 创建成功 |
| libxdp 版本 | ldconfig -p | grep libxdp | 版本 ≥ 1.0 时通常兼容性好 |
5.2 实操中最容易忽略的两个细节
细节一:systemd mount 单元的命名必须规范。
sys-fs-bpf.mount这个名称不是随意起的。Systemd 会自动根据"挂载路径转换成 unit 名"的规则来识别:路径/sys/fs/bpf转成 unit 名时,左斜杠全部替换成-,同时去掉开头,所以是sys-fs-bpf.mount。改成了别的名字(比如bpffs.mount),systemctl enable时虽然能注册,但local-fs.target启动阶段不一定会触发它。我在自己机器上刚开始就踩了这个命名坑,后来把文件名纠正过来,一切正常。
细节二:bpffs 下没有自动创建xdp子目录。
有些朋友挂载完 bpffs 后又报目录不存在,打开/sys/fs/bpf发现里面是空的。其实 bpffs 是"用的时候才创建目录"的虚拟文件系统,不是默认内置好一堆子目录的结构。XDP 程序需要的/sys/fs/bpf/xdp/目录,通常由加载工具负责创建。
但如果是自己写的加载逻辑,就得在代码里显式调用。比如用libbpf时,可以这样:
mkdir("/sys/fs/bpf/xdp", 0755);或者用bpftool手动建:
mkdir /sys/fs/bpf/xdp创建成功后,ls -la /sys/fs/bpf/xdp/就能看到刚 pin 下去的对象了。
5.3 值得收藏的深入排查命令组合
在某些"挂载了但还是出错"的疑难场景下,我会把下面这一串命令组合起来使用,排查效率很高:
# 查看 BPF 文件系统的实际挂载点 findmnt -t bpf # 查看某张网卡上的 XDP 程序挂载状态 xdp-loader status eth0 # 列出 bpffs 里所有类型的 pin 对象,包括程序与 map bpftool bpftool prog list bpftool map list如果xdp-loader status显示网卡上已经有程序,但 libxdp 还在报错,那多半是当前进程的用户命名空间权限问题。这种情况常见于运行在 systemd user session 下、或者切换过用户执行程序的场景。解决办法是把程序放到 root 用户下运行,或者在 systemd service 里配置User=root。
6. 一次完整的真实排障记录(供参考)
写到这里,我想分享一次相对完整的排障经过,算是把上面所有点串联起来。
某次,我在一台开发机上尝试运行一个开源的 XDP 负载均衡程序。环境是精简版 Linux,没有图形界面,开发机刚被初始化过。我按 README 执行加载命令,屏幕上立刻弹出了libxdp: No bpffs found at /sys/fs/bpf。
第一步,我例行检查内核和挂载状态:
cat /proc/filesystems | grep bpf # 输出里有 nodev bpf,说明内核支持 mount | grep bpf # 没有任何输出,说明确实没挂载问题定位得很干脆——没挂载而已。
第二步,创建目录并挂载:
sudo mkdir -p /sys/fs/bpf sudo mount -t bpf bpf /sys/fs/bpf再执行加载命令,这次程序跑起来了,但新的问题来了:程序能加载,但几次重启之后又报错。这才意识到临时挂载的局限,于是用 systemd mount 单元做成自启动。我创建好sys-fs-bpf.mount后执行:
sudo systemctl daemon-reload sudo systemctl enable --now sys-fs-bpf.mount查看状态:
systemctl status sys-fs-bpf.mount显示Active: active (mounted)后,重启验证,问题彻底解决。
这个案例本身没有多高深的技术含量,但很能代表这类问题的普遍性——麻烦不在于"不懂原理",而在于"不知道问题出在系统环境层面,还在那里改代码"。我之前看到好几个人在这个报错上纠结了一整天,反复检查 attach 逻辑、BTF 版本、编译器参数,结果最后就是环境没配置好,白白消耗时间。希望这篇内容能帮你绕开那段弯路。
7. 事后完善:让 XDP 开发环境更顺手的四点建议
7.1 统一挂载路径,别搞特殊
不管你是做开发还是做生产部署,把 bpffs 挂到/sys/fs/bpf,并且只挂这一个路径,是最省心的选择。虽然内核允许同一个 bpffs 挂到多个目录,允许你换挂载点,但工具链生态不一定会跟上。让libxdp、bpftool、tc、iproute2全都找到同一个文件系统视图,能省下一大堆环境类疑难杂症的排查时间。
7.2 把挂载检查写进部署脚本
如果你经常要在多台机器上部署 XDP 程序,我建议把挂载检查做成部署脚本的前置步骤:
#!/bin/bash if ! mountpoint -q /sys/fs/bpf; then echo "[INFO] Mounting bpffs at /sys/fs/bpf" sudo mount -t bpf bpf /sys/fs/bpf else echo "[INFO] bpffs already mounted" fi这段脚本可以在 CI/CD 流程、容器启动入口、或者手动部署的第一步执行。有了它,No bpffs found这个错误会在部署阶段就被拦截掉,而不会留到程序运行时才爆炸。
7.3 使用 xdp-loader 而不是裸 ip 命令
如果你的场景允许,尽量优先用xdp-loader来加载和管理 XDP 程序。它依托 libxdp,会自动处理 bpffs 挂载检查、程序 pin 和 attach 的衔接问题,不需要你在每个环节手动补位。即便遇到环境问题,它的报错信息也比裸ip命令清晰得多。
当然,这不代表你可以完全不管/sys/fs/bpf的状态——xdp-loader能检查并提示你,但它不会替你做挂载动作。它默认期望路径存在,所以依然建议把挂载放在更早的启动流程里。
7.4 顺手把 bpftool 也配好
在调试 XDP 程序时,bpftool的价值怎么强调都不过分。它可以查看当前内核里所有 BPF 程序、map、BTF 信息,还能直接实现 pin 操作。一个典型的排查流程是:
# 查看指定接口上的 XDP 程序 bpftool net show dev eth0 # 查看 bpffs 下 pin 的程序 bpftool prog show pinned /sys/fs/bpf/xdp/xxx # 查看某个 map 的内容 bpftool map dump name my_mapbpftool配合挂载好的 bpffs,是 XDP 开发调试的黄金组合。你甚至可以把bpftool的命令写进调试文档,作为团队内部排障的标准操作流程的一部分。
8. 最后再分享两个容易被忽略的小细节
第一,/sys/fs/bpf和内核的 unprivileged BPF 开关其实是两回事。Linux 默认允许非特权用户加载 BPF 程序,但如果内核参数kernel.unprivileged_bpf_disabled=1,普通用户即使 bpffs 挂载正确也会报权限错误。如果你用非 root 运行 XDP 程序遇到权限类报错,不要只盯着挂载问题,顺手检查一下:
sysctl kernel.unprivileged_bpf_disabled第二,bpffs 的文件没有"删除锁"。rm /sys/fs/bpf/xdp/xxx删除 pin 对象后,如果程序还 attach 在网卡上,程序会失去它的路径标识,后续如果想通过路径更新或 detach 会变得麻烦。所以清理 BPF 对象时,我习惯先用xdp-loader unload -a(卸载全部)再删除 pin,保持顺序清晰,避免出现"程序还在跑但找不到路径"的尴尬状态。
说实话,libxdp: No bpffs found at /sys/fs/bpf这个错误本身并不复杂,但它很好地提醒了我们:在 Linux 网络内核生态里,环境配置和代码逻辑同等重要。每次我遇到这个报错,都会想起那个一开始摸不着头脑的自己,也希望读完这篇文章的你,下次能一眼认出问题所在,三分钟内搞定,而不是像我当初一样翻遍代码、怀疑人生。