AF_XDP 技术简介
本文分享自天翼云开发者社区 @《AF_XDP技术简介》,作者:l****n
一.概述
AF_XDP 是一项新增的,针对高性能数据包处理进行优化的地址族协议。
本文档假设读者已经熟悉 BPF 和 XDP。如果没有,可以参考开源 Cilium 项目在 (http://cilium.readthedocs.io/en/latest/bpf/ )通过 XDP 的 XDP_REDIRECT 操作,XDP 程序可以使用 bpf_redirect_map() 函数将入口帧重定向到其他启用 XDP 的网络设备。AF_XDP 套接字使 XDP 程序能够将帧重定向到用户程序中的一块内存缓冲区。
AF_XDP 套接字 (以下简称 XSK) 可以使用通用的 socket() 系统调用创建。与每个 XSK 相关的有两个环:RX Ring 和 TX Ring。套接字可以在 RX 环上接收数据包,也可以在 TX 环上发送数据包。这些环分别使用 setsockopts XDP_RX_RING 和 XDP_TX_RING 注册和调整大小。每个 socket 必须至少有一个这样的环。RX 或 TX 描述符环指向称为 UMEM 中的数据缓冲区。RX 和 TX 可以共享相同的 UMEM,因此不必在 RX 和 TX 之间复制数据包。此外,如果一个数据包由于可能重新传输而需要保留一段时间,则可以将指向该数据包的描述符更改为指向另一个并立即重新使用。这避免了再次复制数据。
UMEM 由许多大小相同的块组成。环中的描述符通过引用其地址来引用帧。addr 只是整个 UMEM 区域内的偏移量。用户空间可以使用合适的任何方式(比如 malloc、mmap、大页面等)为此 UMEM 分配内存。然后使用新的 setsockopt XDP_UMEM_REG 向内核注册此内存区域。UMEM 也有两个环:FILL Ring 和 COMPLETION Ring。应用程序使用 FILL 环向下发送 addr 以供内核填充 RX 数据包。一旦收到每个数据包,对这些帧的引用就会出现在 RX 环中。另一方面,COMPLETION 环包含内核已完全传输的帧地址,现在可以由用户空间再次使用,用于 TX 或 RX。因此,出现在 COMPLETION 环中的帧地址是先前使用 TX 环传输的地址。总之,RX 和 FILL 环用于 RX 路径,TX 和 COMPLETION 环用于 TX 路径。
套接字最终通过 bind() 系统调用和一个设备上的特定队列 ID 绑定,绑定完成后,流量才开始流动。
如果需要,可以在进程之间共享 UMEM。如果一个进程想要这样做,它只需跳过 UMEM 的注册及其相应的两个环,在绑定调用中设置 XDP_SHARED_UMEM 标志并提交它想要共享 UMEM 的进程的 XSK 以及它自己的新创建 XSK 套接字。然后,新进程将在其自己的 RX 环中接收指向此共享 UMEM 的帧地址引用。请注意,由于环结构是单消费者/单生产者(出于性能原因),新进程必须创建自己的带有相关 RX 和 TX 环的套接字,因为它不能与其他进程共享。这也是每个 UMEM 只有一组 FILL 和 COMPLETION 环的原因。处理 UMEM 是单个进程的责任。
数据包是如何从 XDP 程序分发到 XSK 的呢?有一个称为 XSKMAP(BPF_MAP_TYPE_XSKMAP)的 BPF 映射。用户空间应用程序可以将 XSK 放置在此映射中的任意位置。然后 XDP 程序可以将数据包重定向到此映射中的特定索引,XDP 验证该映射中的 XSK 确实绑定到该设备和环号。如果不是,则丢弃该数据包。如果映射在该索引处为空,则数据包也将被丢弃。
AF_XDP 可以在两种不同的模式下运行:XDP_SKB 和 XDP_DRV。如果驱动程序不支持 XDP,或者在加载 XDP 程序时显式选择了 XDP_SKB,则采用 XDP_SKB 模式,该模式将 SKB 复制到用户空间,适用于任何网络设备的后备模式,该模式在协议栈开始处运行。另一方面,如果驱动程序支持 XDP,AF_XDP 代码将使用它来提供更好的性能,但仍有一份数据复制到用户空间。该模式在驱动处运行,性能比 XDP_SKB 模式好。文章来源:英文翻译(https://www.kernel.org/doc/html/latest/networking/af_xdp.html)
二.概念
为了使用 AF_XDP 套接字,需要设置许多关联对象。这些对象及其选项将在以下部分中进行说明。有关 AF_XDP 工作原理的概述,您还可以查看 2018 年有关该主题的 Linux Plumbers 论文:http: //vger.kernel.org/lpc_net2018_talks/lpc18_paper_af_xdp_perf-v2.pdf。不要查阅 2017 年关于“AF_PACKET v4”的论文,这是 AF_XDP 的第一次尝试。从那以后,几乎一切都发生了变化。Jonathan Corbet 还写了一篇关于 LWN 的优秀文章,“使用 AF_XDP 加速网络”。它可以在https://lwn.net/Articles/750845/找到。
UMEM
UMEM 是一个虚拟的连续内存区域,被分成大小相等的帧。UMEM 与 netdev 和该 netdev 的特定队列 id 相关联。它是通过使用 XDP_UMEM_REG setsockopt 系统调用创建和配置的(块大小、headroom、起始地址和大小)。UMEM 通过 bind() 系统调用绑定到 netdev 和该 netdev 的队列 id。
一个 AF_XDP 是连接到单个 UMEM 的套接字,但一个 UMEM 可以有多个 AF_XDP 套接字。要共享套接字 A 创建的 UMEM,下一个套接字 B 可以通过在 struct sockaddr_xdp 成员 sxdp_flags 中设置 XDP_SHARED_UMEM 标志并将 A 的文件描述符传递给 struct sockaddr_xdp 成员
sxdp_shared_umem_fd 来实现。UMEM 有两个单生产者/单消费者环,用于在内核和用户空间应用程序之间转移 UMEM 帧的所有权。
Rings
有四种不同类型的环:FILL、COMPLETION、RX 和 TX。所有环都是单生产者/单消费者,因此用户空间应用程序需要显式同步多个进程/线程正在读取/写入它们。UMEM 使用两个环:FILL 和 COMPLETION。每个与 UMEM 关联的套接字必须有一个 RX 队列、TX 队列或两者兼有。这些环是基于头部(生产者)/尾部(消费者)的环。生产者将数据写入 struct xdp_ring 生产者成员指向的索引处,并增加生产者索引。消费者读取 struct xdp_ring 消费者成员指向的索引处的数据环,并增加消费者索引。环是通过_RING setsockopt 系统调用配置和创建的,然后使用 mmap() 的适当偏移量(XDP_PGOFF_RX_RING、XDP_PGOFF_TX_RING、XDP_UMEM_PGOFF_FILL_RING 和 XDP_UMEM_PGOFF_COMPLETION_RING)映射到用户空间。注:环的大小需要是 2 的大小幂。
UMEM Fill Ring
FILL 环用于将 UMEM 的所有权从用户空间转移到内核空间。UMEM 地址在环中传递。例如,如果 UMEM 是 64k 并且每个块是 4k,那么 UMEM 有 16 个块并且可以传递 0 到 64k 之间的地址。传递给内核的帧用于入口路径(RX 环)。用户应用程序为该环生成 UMEM 地址。请注意,如果以对齐的块模式运行应用程序,内核将屏蔽传入的地址。例如,对于 2k 的块大小,addr 的 log2(2048) LSB 将被屏蔽掉,这意味着 2048、2050 和 3000 指的是同一个块。如果用户应用程序在未对齐的块模式下运行,那么传入的 addr 将保持不变。
UMEM Completion Ring
COMPLETION Ring 用于将 UMEM 的所有权从内核空间转移到用户空间。就像 FILL 环一样,使用 UMEM 索引。从内核传递到用户空间的帧是已经发送的帧(TX 环),可以再次被用户空间使用。用户应用程序消费此环中的 UMEM 地址。
RX Ring
RX 环是套接字的接收端。环中的每个条目都是一个 struct xdp_desc 描述符。描述符包含 UMEM 偏移量 (addr) 和数据长度 (len)。如果没有帧通过 FILL 环传递给内核,则没有描述符出现在 RX 环上。用户应用程序消费此环中的 struct xdp_desc 描述符。
TX Ring
TX 环用于发送帧。struct xdp_desc 描述符被填充(索引、长度和偏移量)并传递到环中。要开始传输,需要 sendmsg() 系统调用。未来可能会放宽。用户应用程序为、在此环生产 struct xdp_desc 描述符。
三.用法
使用 AF_XDP 套接字,有两个部分是需要的。用户空间应用程序和 XDP 程序。有关完整的设置和使用示例,请参阅示例应用程序。用户空间端是 xdpsock_user.c,XDP 端是 libbpf 的一部分。tools/lib/bpf/xsk.c 中包含的 XDP 代码示例如下:
SEC("xdp_sock") int xdp_sock_prog(struct xdp_md *ctx)
{
int index = ctx->rx_queue_index;
// A set entry here means that the corresponding queue_id
// has an active AF_XDP socket bound to it.
if (bpf_map_lookup_elem(&xsks_map, &index))
return bpf_redirect_map(&xsks_map, index, 0);
return XDP_PASS;
}
一个简单但性能不高的 ring dequeue 和 enqueue 可能如下所示:
// struct xdp_rxtx_ring {
// __u32 *producer;
// __u32 *consumer;
// struct xdp_desc *desc;
// };
// struct xdp_umem_ring {
// __u32 *producer;
// __u32 *consumer;
// __u64 *desc;
// };
// typedef struct xdp_rxtx_ring RING;
// typedef struct xdp_umem_ring RING;
// typedef struct xdp_desc RING_TYPE;
// typedef __u64 RING_TYPE;
int dequeue_one(RING *ring, RING_TYPE *item)
{
__u32 entries = *ring->producer - *ring->consumer;
if (entries == 0)
return -1;
// read-barrier!
*item = ring->desc[*ring->consumer & (RING_SIZE - 1)];
(*ring->consumer)++;
return 0;
}
int enqueue_one(RING *ring, const RING_TYPE *item)
{
u32 free_entries = RING_SIZE - (*ring->producer - *ring->consumer);
if (free_entries == 0)
return -1;
ring->desc[*ring->producer & (RING_SIZE - 1)] = *item;
// write-barrier!
(*ring->producer)++;
return 0;
}
四.示例应用程序
下面是一个 xdpsock 测试应用程序,演示如何将 AF_XDP 套接字与私有 UMEM 一起使用。假设你希望来自端口 4242 的 UDP 流量最终进入队列 16,使用 ethtool 启用 AF_XDP:
ethtool -N p3p2 rx-flow-hash udp4 fn
ethtool -N p3p2 flow-type udp4 src-port 4242 dst-port 4242 \
action 16
然后可以使用以下命令在 XDP_DRV 模式下运行 rxdrop 基准测试:
samples/bpf/xdpsock -i p3p2 -q 16 -r -N
对于 XDP_SKB 模式,使用开关“-S”而不是“-N”。
原文链接:https://www.kernel.org/doc/html/latest/networking/af_xdp.html
作者:kernel development community,如需转载,请联系作者。
评论