0

我尝试在我的网卡的单个 RX-Queue 上最大化吞吐量。我当前的设置是Shared Umem通过在同一个 RX-Queue 上设置多个套接字来利用该功能,每个套接字都引用同一个 Umem。

然后我的内核 XDP 程序通过BPF_MAP_TYPE_XSKMAP. 这一切工作正常,但在大约 600.000 pps 时,ksoftirqd/18达到 100% 的 CPU 负载(我将我的用户空间应用程序移动到另一个核心,taskset -c 1以减少核心 18 上的负载)。我的用户空间应用程序没有超过 14% 的 CPU 负载,所以不幸的是,我无法处理更多数据包的原因是因为大量的中断。

然后,我阅读了 xdp 绑定标志,该标志XDP_USE_NEED_WAKEUP将 Umem 填充环发送到睡眠状态,从而减少了中断开销(据我正确理解,关于这个主题的信息并不多)。因为 Umem Fill-Ring 可能正在睡觉,所以必须定期检查:

    if (xsk_ring_prod__needs_wakeup(&umem->fq)) {
        const int ret = poll(fds, len, 10);
    }

fds包含struct pollfd每个套接字的文件描述符。我不太确定在哪里添加XDP_USE_NEED_WAKEUP标志,但这是我使用它的方式:

static struct xsk_socket_info *xsk_configure_socket(struct xsk_umem_info *umem, struct config *cfg,
                                                    const bool rx, const bool tx) {
    struct xsk_socket_config xsk_socket_cfg;
    struct xsk_socket_info *xsk;
    struct xsk_ring_cons *rxr;
    struct xsk_ring_prod *txr;
    int ret;

    xsk = calloc(1, sizeof(*xsk));
    if (!xsk) {
        fprintf(stderr, "xsk `calloc` failed: %s\n", strerror(errno));
        exit(1);
    }

    xsk->umem = umem;
    xsk_socket_cfg.rx_size = XSK_CONS_AMOUNT;
    xsk_socket_cfg.tx_size = XSK_PROD_AMOUNT;
    if (cfg->ip_addrs_len > 1) {
        xsk_socket_cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD;
    } else {
        xsk_socket_cfg.libbpf_flags = 0;
    }
    xsk_socket_cfg.xdp_flags = cfg->xdp_flags;
    xsk_socket_cfg.bind_flags = cfg->xsk_bind_flags | XDP_USE_NEED_WAKEUP;

    rxr = rx ? &xsk->rx : NULL;
    txr = tx ? &xsk->tx : NULL;
    ret = xsk_socket__create(&xsk->xsk, cfg->ifname_buf, cfg->xsk_if_queue, umem->umem, rxr, txr, &xsk_socket_cfg);
    if (ret) {
        fprintf(stderr, "`xsk_socket__create` returned error: %s\n", strerror(errno));
        exit(-ret);
    }

    return xsk;
}

我观察到它对负载的影响很小,ksoftirqd/18并且我能够比以前多处理 50.000 pps(但这也可能是因为系统的一般负载发生了变化 - 我不确定:/)。但我也注意到,这XDP_USE_NEED_WAKEUP不起作用,Shared Umem因为 libbpf 有这个代码xsk.c

sxdp.sxdp_family = PF_XDP;
sxdp.sxdp_ifindex = xsk->ifindex;
sxdp.sxdp_queue_id = xsk->queue_id;
if (umem->refcount > 1) {
    sxdp.sxdp_flags = XDP_SHARED_UMEM;
    sxdp.sxdp_shared_umem_fd = umem->fd;
} else {
    sxdp.sxdp_flags = xsk->config.bind_flags;

如您所见,bind_flags仅当Umemarefcount为 1 时才使用(它不能小于该值,因为它在 的上方某处递增xsk_socket__create)。但是因为对于每个创建的套接字,refcount都会增加 - 这些bind_flags仅用于第一个套接字(refcount仍然是<= 1)。

我不太明白为什么XDP_USE_NEED_WAKEUP只能用于一个插座?事实上,我完全不明白如果这个标志实际上影响了 Umem,为什么它与套接字有关?

尽管如此,我正在寻找一种减少中断开销的方法——有什么想法可以实现吗?我需要至少 1.000.000 pps。

4

1 回答 1

0

xsk.c 中的代码只是确保具有相同 UMEM 的所有套接字都使用 XSK_NEEDS_WAKEUP,或者它们都不使用,即如果您将第一个套接字(未创建为共享套接字)配置为启用 XSK_NEEDS_WAKEUP,则所有共享套接字之后附加到同一个 umem 也会启用此标志,反之亦然。我不能 100% 确定为什么决定这样做,但是 XSK_NEEDS_WAKEUP 会影响用户空间生产者的所有环,因此 TX 环和 FILL 环都。由于 FILL 环与 UMEM 而非套接字相关联,因此此标志会影响共享的 UMEM,因此在共享同一 UMEM 的套接字上不能是不同的。

这也回答了为什么标志会影响套接字的问题:它也是用户每次写入时都需要唤醒的 TX 环(如果启用且必要的话)。从内核的角度来看,它只看到 2 个不可预测的用户空间是生产者的环,并且它提供了一个选项,即在不需要时不要从你的环中轮询。我看不出为什么将来TX环和FILL环不能有不同的标志,或者基于套接字的队列ID的FILL环不能有不同的标志,但话又说回来,我不是内核开发商。

至于在哪里启用它,假设它是绑定标志的一部分是正确的(https://www.kernel.org/doc/html/latest/networking/af_xdp.html#xdp-use-need-唤醒绑定标志)。

我还注意到您在 poll() 调用中使用了 10 毫秒的超时值。我对此不太确定,但在我的测试中,重要的似乎不是 poll() 调用的结果,但调用它的事实足以让内核接受你的通知我把它吵醒了。此外,我不确定在 AF_XDP 套接字上接收到的数据包是否构成可以由 poll() 捕获的事件,因此内核可能真的每次都会让你承受 10 毫秒的延迟。出于这个原因,我养成了将超时值设置为 0 的习惯:需要唤醒的是内核,而不是需要通知您任何事情。

至于您关于提高数据包速率的问题,在我从大约 800.000 pps 开始的测试中(64 字节供参考),如果不启用 XDP_DRV 模式,您实际上无法进行任何 XDP 基准测试,因为这是我测试的点SKB 缓冲区的分配和解除分配成为丢包的根源。在那之后,瓶颈可能会变成您的用户空间应用程序,或者您的 NIC 上的 RX 队列数量,如果不查看更多代码就很难判断。

于 2020-09-18T09:46:04.957 回答