4. 开发环境搭建与第一个AF_XDP程序
AF_XDP 的性能优势建立在“内核 XDP 路径 + 用户态零拷贝环形队列”这一组合之上,因此环境搭建不是简单装几个包,而是要同时打通内核、驱动、库和用户态程序四层。本节以 Ubuntu 22.04(内核 5.15+)为例,从依赖安装、网卡与驱动确认,到编写一个最小可运行的 XSK socket 创建/关闭程序,最后给出环境可用性的判断依据。所有参数均为教学示例,实际值需按你的机器调整。
一、依赖安装:libbpf、libxdp 与编译工具链
AF_XDP 用户态程序通常依赖三个层次:内核头文件(提供 xsk.h 等 UAPI 定义)、libbpf(BPF 对象加载与 XDP 程序管理)、libxdp(XDP 多程序调度与 AF_XDP 辅助封装)。推荐直接使用发行版包管理器安装,避免手工编译带来的版本错配。
sudo apt update
sudo apt install -y clang llvm libelf-dev libpcap-dev build-essential
sudo apt install -y linux-tools-common linux-tools-$(uname -r)
sudo apt install -y libbpf-dev libxdp-dev libxdp1
# 验证关键头文件与库是否存在
ls /usr/include/bpf/xsk.h
ls /usr/include/xdp/xsk.h
pkg-config --modversion libbpf
如果发行版仓库版本过旧(例如 libbpf < 0.6),建议从源码编译安装:先装 libelf、zlib,再依次编译 libbpf、libxdp。编译时注意 make install 后执行 sudo ldconfig,否则运行期会出现 error while loading shared libraries: libxdp.so.1。判断依赖是否就绪,最直接的方式是编译一个只包含 #include <xdp/xsk.h> 的空程序,能通过即说明头文件与库路径正确。
二、网卡与驱动配置:确认 XDP 支持模式
AF_XDP 的收发路径依赖网卡驱动的 XDP 支持。不同驱动支持的模式不同:native(驱动原生 XDP)、generic(SKB 通用路径,性能低)、offload(网卡硬件卸载)。零拷贝 AF_XDP 通常要求 native 模式,且需要驱动实现 ndo_xdp_xmit 与 XDP_REDIRECT。
# 查看网卡与驱动
ip -br link
ethtool -i enp3s0
# 查看当前 XDP 支持能力(需 iproute2 较新版本)
ip link show enp3s0
# 输出中若出现 xdp 字段,说明内核已识别该网卡的 XDP 能力
# 临时关闭无关卸载特性,减少干扰(示例)
sudo ethtool -K enp3s0 gro off gso off tso off
常见可用驱动包括 i40e、ice、mlx5_core、ixgbe、virtio_net(较新内核支持 native)。若使用虚拟机,建议将网卡直通或使用支持 XDP 的 virtio 版本,否则只能退回 generic 模式,性能会明显下降。配置完成后,用 ethtool -S enp3s0 | grep xdp 可观察 XDP 相关计数,作为后续验证的基线。
三、最小化 XSK socket 创建与关闭代码
下面这段程序只做一件事:在指定网卡和队列上创建一个 AF_XDP socket,成功后立即关闭。它不挂载 XDP 程序,也不收发数据,目的是验证“库 + 驱动 + 内核”这条链路是否打通。代码基于 libbpf 的 xsk.h 接口。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <bpf/xsk.h>
#define UMEM_NUM_FRAMES 2048
#define FRAME_SIZE 2048
int main(int argc, char **argv)
{
if (argc != 3) {
fprintf(stderr, "usage: %s <ifname> <queue_id>\n", argv[0]);
return 1;
}
const char *ifname = argv[1];
int queue_id = atoi(argv[2]);
struct xsk_umem *umem;
struct xsk_socket *xsk;
void *umem_area = NULL;
struct xsk_umem_config umem_cfg = {
.fill_size = UMEM_NUM_FRAMES / 2,
.comp_size = UMEM_NUM_FRAMES / 2,
.frame_size = FRAME_SIZE,
.frame_headroom = 0,
.flags = 0,
};
struct xsk_socket_config xsk_cfg = {
.rx_size = 1024,
.tx_size = 1024,
.libbpf_flags = 0,
.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST,
.bind_flags = XDP_ZEROCOPY,
};
if (posix_memalign(&umem_area, 4096,
UMEM_NUM_FRAMES * FRAME_SIZE) != 0) {
perror("posix_memalign");
return 1;
}
int ret = xsk_umem__create(&umem, umem_area,
UMEM_NUM_FRAMES * FRAME_SIZE,
NULL, NULL, &umem_cfg);
if (ret) {
fprintf(stderr, "xsk_umem__create failed: %s\n", strerror(-ret));
free(umem_area);
return 1;
}
ret = xsk_socket__create(&xsk, ifname, queue_id,
umem, NULL, NULL, &xsk_cfg);
if (ret) {
fprintf(stderr, "xsk_socket__create failed: %s\n", strerror(-ret));
xsk_umem__delete(umem);
free(umem_area);
return 1;
}
printf("AF_XDP socket created on %s queue %d\n", ifname, queue_id);
xsk_socket__delete(xsk);
xsk_umem__delete(umem);
free(umem_area);
return 0;
}
编译命令如下,注意链接顺序:先 -lxdp 再 -lbpf,最后 -lelf -lz。
gcc -O2 -Wall -o xsk_min xsk_min.c -lxdp -lbpf -lelf -lz
sudo ./xsk_min enp3s0 0
四、结果判读与常见失败原因
成功时输出 AF_XDP socket created on enp3s0 queue 0,退出码为 0。若失败,strerror(-ret) 会给出具体原因,常见几类如下:
| 错误信息 | 含义 | 处理方向 |
|---|---|---|
| Operation not supported | 驱动不支持 native/zero-copy | 换驱动或改用 XDP_COPY 模式 |
| Device or resource busy | 队列已被占用或已有 XDP 程序 | 换队列号,或先卸载已有 XDP |
| Cannot allocate memory | UMEM 过大或锁页失败 | 减小帧数,检查 ulimit -l |
| No such file or directory | 网卡名错误或队列不存在 | 用 ip link 确认名称与队列数 |
bind_flags 改为 XDP_COPY,它不要求驱动零拷贝支持,兼容性更好,但性能低于 XDP_ZEROCOPY。确认环境无误后再切回零拷贝。