c语言 - AF-XDP - "XDP_USE_NEED_WAKEUP"如何工作,例如 "How to reduce ksoftirqd load" ?



我试图在网卡的单个RX队列上最大限度地提高吞吐量。我目前的设置是利用Shared Umem功能,在同一个RX队列上设置多个套接字,每个套接字都引用同一个Umem。

然后,我的内核XDP程序通过BPF_MAP_TYPE_XSKMAP将数据包流分配给正确的套接字。这一切都很好,但在大约600.000pps时,ksoftirqd/18达到了100%的CPU负载(我通过taskset -c 1将我的用户空间应用程序移动到另一个核心,以减少核心18上的负载(。我的用户空间应用程序的CPU负载不超过14%,所以不幸的是,我无法处理更多数据包的原因是大量的中断。

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

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

CCD_ 6是包含每个套接字的文件描述符的CCD_。我不太确定在哪里添加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: %sn", 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: %sn", 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;

正如您所看到的,只有当Umemrefcount为1时,才使用bind_flags(它不能小于1,因为它在xsk_socket__create中的某个位置递增(。但是,因为对于每个创建的套接字,refcount都会增加——这些bind_flags只用于第一个套接字(其中refcount仍然是<= 1(。

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

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

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

这也回答了您为什么标志会影响套接字的问题:每次我们对TX环进行写入时,它也需要被用户唤醒(如果启用且必要(。从内核的角度来看,它只看到了两个环,不可预测的用户空间是它们的生产者,它提供了一种选择,即友善,在不必要的时候不去轮询你的环。我看不出将来为什么TX环和FILL环不能有不同的标志,或者FILL环的标志不能基于套接字的队列id,但话说回来,我不是内核开发人员。

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

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

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

相关内容

最新更新