设备内存 TCP¶
简介¶
设备内存 TCP (devmem TCP) 允许直接将数据接收到设备内存 (dmabuf) 中。该功能目前针对 TCP 套接字实现。
机会¶
大量数据传输的源或目标都是设备内存。加速器大大增加了此类传输的普遍性。一些例子包括:
分布式训练,其中 ML 加速器(例如不同主机上的 GPU)交换数据。
分布式原始块存储应用程序使用远程 SSD 传输大量数据。其中大部分数据不需要主机处理。
通常,网络中的设备到设备的数据传输实现为以下低级操作:设备到主机复制、主机到主机网络传输和主机到设备复制。
涉及主机复制的流程不是最优的,尤其是对于批量数据传输,并且会对系统资源(如主机内存带宽和 PCIe 带宽)造成巨大压力。
Devmem TCP 通过实现套接字 API 来优化此用例,使用户能够直接将传入的网络数据包接收到设备内存中。
数据包有效负载直接从网卡传输到设备内存。
数据包头传输到主机内存,并由 TCP/IP 协议栈正常处理。网卡必须支持标头拆分才能实现此目的。
优点
与现有的网络传输 + 设备复制语义相比,减轻主机内存带宽压力。
与传统路径(将数据通过根复合体发送)相比,通过将数据传输限制在 PCIe 树的最低级别,从而减轻 PCIe 带宽压力。
更多信息¶
- 幻灯片,视频
https://netdevconf.org/0x17/sessions/talk/device-memory-tcp.html
- 补丁集
[PATCH net-next v24 00/13] 设备内存 TCP https://lore.kernel.org/netdev/[email protected]/
接口¶
示例¶
tools/testing/selftests/net/ncdevmem.c:do_server 显示了设置此 API 的 RX 路径的示例。
网卡设置¶
标头拆分、流向控制和 RSS 是 devmem TCP 的必需功能。
标头拆分用于将传入的数据包拆分为主机内存中的标头缓冲区和设备内存中的有效负载缓冲区。
流向控制和 RSS 用于确保只有目标为 devmem 的流才落在绑定到 devmem 的 RX 队列上。
启用标头拆分和流向控制
# enable header split
ethtool -G eth1 tcp-data-split on
# enable flow steering
ethtool -K eth1 ntuple on
配置 RSS 以将所有流量从目标 RX 队列(本例中为队列 15)转移走
ethtool --set-rxfh-indir eth1 equal 15
用户必须使用 netlink API 将 dmabuf 绑定到给定网卡上的任意数量的 RX 队列
/* Bind dmabuf to NIC RX queue 15 */
struct netdev_queue *queues;
queues = malloc(sizeof(*queues) * 1);
queues[0]._present.type = 1;
queues[0]._present.idx = 1;
queues[0].type = NETDEV_RX_QUEUE_TYPE_RX;
queues[0].idx = 15;
*ys = ynl_sock_create(&ynl_netdev_family, &yerr);
req = netdev_bind_rx_req_alloc();
netdev_bind_rx_req_set_ifindex(req, 1 /* ifindex */);
netdev_bind_rx_req_set_dmabuf_fd(req, dmabuf_fd);
__netdev_bind_rx_req_set_queues(req, queues, n_queue_index);
rsp = netdev_bind_rx(*ys, req);
dmabuf_id = rsp->dmabuf_id;
netlink API 返回 dmabuf_id:一个唯一 ID,用于引用已绑定的 dmabuf。
用户可以通过关闭建立绑定的 netlink 套接字来从网络设备中取消绑定 dmabuf。我们这样做是为了即使用户空间进程崩溃,绑定也会自动取消绑定。
请注意,来自任何导出器的任何行为良好的 dmabuf 都应该与 devmem TCP 一起使用,即使 dmabuf 实际上不是由 devmem 支持的。一个例子是 udmabuf,它将用户内存(非 devmem)包装在 dmabuf 中。
套接字设置¶
套接字必须流向控制到绑定的 dmabuf RX 队列
ethtool -N eth1 flow-type tcp4 ... queue 15
接收数据¶
用户应用程序必须通过将 MSG_SOCK_DEVMEM 标志传递给 recvmsg 来向内核发出信号,表明它可以接收 devmem 数据
ret = recvmsg(fd, &msg, MSG_SOCK_DEVMEM);
未指定 MSG_SOCK_DEVMEM 标志的应用程序将在 devmem 数据上收到 EFAULT。
Devmem 数据直接接收到“网卡设置”中绑定到网卡的 dmabuf 中,并且内核通过 SCM_DEVMEM_* cmsg 向用户发出此类信号
for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
if (cm->cmsg_level != SOL_SOCKET ||
(cm->cmsg_type != SCM_DEVMEM_DMABUF &&
cm->cmsg_type != SCM_DEVMEM_LINEAR))
continue;
dmabuf_cmsg = (struct dmabuf_cmsg *)CMSG_DATA(cm);
if (cm->cmsg_type == SCM_DEVMEM_DMABUF) {
/* Frag landed in dmabuf.
*
* dmabuf_cmsg->dmabuf_id is the dmabuf the
* frag landed on.
*
* dmabuf_cmsg->frag_offset is the offset into
* the dmabuf where the frag starts.
*
* dmabuf_cmsg->frag_size is the size of the
* frag.
*
* dmabuf_cmsg->frag_token is a token used to
* refer to this frag for later freeing.
*/
struct dmabuf_token token;
token.token_start = dmabuf_cmsg->frag_token;
token.token_count = 1;
continue;
}
if (cm->cmsg_type == SCM_DEVMEM_LINEAR)
/* Frag landed in linear buffer.
*
* dmabuf_cmsg->frag_size is the size of the
* frag.
*/
continue;
}
应用程序可能会收到 2 个 cmsg
SCM_DEVMEM_DMABUF:表示片段落在了 dmabuf_id 指示的 dmabuf 中。
SCM_DEVMEM_LINEAR:表示片段落在了线性缓冲区中。当网卡无法在标头边界拆分数据包时,通常会发生这种情况,使得部分(或全部)有效负载落在主机内存中。
应用程序可能收不到 SO_DEVMEM_* cmsg。这表示非 devmem、常规 TCP 数据落在未绑定到 dmabuf 的 RX 队列上。
释放片段¶
通过 SCM_DEVMEM_DMABUF 接收的片段在用户处理片段时由内核固定。用户必须通过 SO_DEVMEM_DONTNEED 将片段返回给内核
ret = setsockopt(client_fd, SOL_SOCKET, SO_DEVMEM_DONTNEED, &token,
sizeof(token));
用户必须确保及时将令牌返回给内核。否则,将耗尽绑定到 RX 队列的有限 dmabuf,并导致数据包丢失。
用户必须传递不超过 128 个令牌,其中所有令牌的 token->token_count 中总共不超过 1024 个片段。如果用户提供的片段超过 1024 个,内核将释放最多 1024 个片段并提前返回。
内核返回实际释放的片段数。在以下情况下,释放的片段数可能小于用户提供的令牌数:
内部内核泄漏错误。
用户传递的片段超过 1024 个。
实现和注意事项¶
不可读的 skb¶
内核无法访问 devmem 有效负载来处理数据包。这导致 devmem skb 有效负载的一些怪癖
环回不起作用。环回依赖于复制有效负载,这对于 devmem skb 是不可能的。
软件校验和计算失败。
TCP Dump 和 bpf 无法访问 devmem 数据包有效负载。
测试¶
更真实的示例代码可以在内核源代码中的 tools/testing/selftests/net/ncdevmem.c
下找到
ncdevmem 是 devmem TCP netcat。它的工作方式与 netcat 非常相似,但直接将数据接收到 udmabuf 中。
要运行 ncdevmem,需要在被测机器上的服务器上运行它,并且需要在对等方上运行 netcat 来提供 TX 数据。
ncdevmem 还具有验证模式,该模式期望传入数据的重复模式并按此进行验证。例如,您可以通过以下方式在服务器上启动 ncdevmem:
ncdevmem -s <server IP> -c <client IP> -f eth1 -d 3 -n 0000:06:00.0 -l \
-p 5201 -v 7
在客户端,使用常规 netcat 将 TX 数据发送到服务器上的 ncdevmem 进程
yes $(echo -e \\x01\\x02\\x03\\x04\\x05\\x06) | \
tr \\n \\0 | head -c 5G | nc <server IP> 5201 -p 5201