使用BPF_MAP_TYPE_CPUMAP时调用bpf_map_update_elem报ENOMEM(Cannot allocate memory)错误的排查与解决
我仔细啃了你的代码和报错信息,这个ENOMEM错误其实是几个小问题叠加出来的,咱们一步步拆解解决:
1. 最核心的坑:update元素的标志用错了
你现在调用bpf_map_update_elem时用了BPF_EXIST标志——这个标志的意思是只有当key已经存在时才允许更新,但刚load完的cpumap是空的,根本没有cpu=0这个条目。
理论上这种情况应该返回ENOENT(条目不存在),但某些内核版本在处理逻辑上有个小问题:它会先尝试为队列分配内存,发现条目不存在后再释放内存,但错误码却返回成了ENOMEM,直接把我们带偏了。
解决办法:把标志改成BPF_ANY(存在则更新,不存在则创建)或者BPF_NOEXIST(仅当不存在时创建),推荐用BPF_ANY更稳妥:
ret = bpf_map_update_elem(cpu_map_fd, &cpu, &val, BPF_ANY);
2. 队列qsize可能设置过大
你给qsize设了1024,cpumap的每个条目对应内核要为该CPU创建一个长度为1024的skb队列,这个队列需要占用不少内存。如果系统剩余内存吃紧,或者内核的内存限制比较严格,就会触发ENOMEM。
解决办法:先把qsize改小测试,比如64或者128,验证成功后再根据业务需求调整:
struct bpf_cpumap_val val = { .qsize = 64, // 从小值开始试,稳了再调大 .bpf_prog.fd = just_pass_fd, };
3. 代码逻辑的小疏漏:update成功后直接return
你在update elem成功后直接return 0了,后面挂载xdp主程序到lo接口的代码根本没机会执行!哪怕update成功了,你的主xdp程序也没挂上,完全起不到转发UDP包的作用。
解决办法:把这个多余的return删掉,让程序继续执行到挂载步骤:
} else { fprintf(stderr, "Successfully updated cpu_map\n"); // 这里别return 0!让程序往下走 }
4. 硬编码网卡index的隐患
你直接把LO_IFINDEX设为1,虽然大部分系统的lo接口index是1,但也有例外情况。最好用动态获取的方式更保险:
#include <net/if.h> // 记得加这个头文件 int LO_IFINDEX = if_nametoindex("lo"); if (LO_IFINDEX == 0) { fprintf(stderr, "Failed to get lo interface index\n"); goto end_destroy; }
最后验证步骤
改完代码后,按以下步骤验证:
- 确保内核版本≥4.19(CPUMAP是4.19内核引入的)
- 用root权限编译运行(eBPF程序需要root权限)
- 用
bpftool map list查看cpumap是否成功创建并添加了cpu=0的条目 - 发送UDP包测试(比如
nc -u localhost 1234),然后用cat /sys/kernel/debug/tracing/trace_pipe查看bpf_printk的输出,验证just_pass程序是否正常运行
备注:内容来源于stack exchange,提问作者Chasing1020

