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 相关计数,作为后续验证的基线。

AF_XDP 环境四层依赖与验证路径 内核层 Linux 5.15+ xsk.h / XDP UAPI 驱动层 i40e / ice / mlx5 native XDP 模式 库层 libbpf + libxdp xsk_umem / xsk_socket 用户态程序 XSK socket RX/TX 环形队列 环境可用性验证链 编译通过 xsk.h 可引用 驱动识别 ethtool -i 正常 socket 创建 xsk_socket__create=0 收包验证 RX 队列计数增长 任一层失败都会导致 xsk_socket__create 返回负值,需按层回查

三、最小化 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 memoryUMEM 过大或锁页失败减小帧数,检查 ulimit -l
No such file or directory网卡名错误或队列不存在用 ip link 确认名称与队列数
若只想先验证库链路,可把 bind_flags 改为 XDP_COPY,它不要求驱动零拷贝支持,兼容性更好,但性能低于 XDP_ZEROCOPY。确认环境无误后再切回零拷贝。