我正在运行Linux 5.1,这是一个带有两个ARMv7内核的FPGA芯片Cyclone V SoC。我的目标是从外部接口收集大量数据,并通过TCP套接字流式传输(部分)此数据。挑战在于数据速率非常高,可能接近饱和千兆以太网界面。我已经有了一个工作实现,只使用write()
调用将数据发送到套接字,但它的峰值仅为55MB/s;大约是理论千兆以太网限制的一半。我现在正在尝试使零拷贝TCP传输工作,以增加吞吐量,但我遇到了障碍。
为了将数据从FPGA传输到Linux用户空间,我编写了一个内核驱动程序。该驱动程序使用FPGA中的DMA块将大量数据从外部接口复制到连接在ARMv7内核上的DDR3存储器中。当通过dma_alloc_coherent()
使用GFP_USER
进行探测时,驱动程序将此内存分配为一组连续的1MB缓冲区,并通过在/dev/
中实现mmap()
,并使用dma_mmap_coherent()
在预分配的缓冲区上向用户空间应用返回地址来将其公开给用户空间应用。
到目前为止,用户空间应用程序看到的是有效数据,而吞吐量大于360MB/s,还有余地(外部接口不足以真正了解上限是多少)。
为了实现零拷贝TCP网络,我的第一种方法是在套接字上使用SO_ZEROCOPY
:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
然而,这会导致send: Bad address
的结果。
经过一番搜索后,我的第二种方法是使用一个管道和splice()
,然后跟着使用vmsplice()
:
ssize_t sent_bytes;
int pipes[2];
struct iovec iov = {
.iov_base = buf,
.iov_len = len
};
pipe(pipes);
sent_bytes = vmsplice(pipes[1], &iov, 1, 0);
if (sent_bytes < 0) {
perror("vmsplice");
return -1;
}
sent_bytes = splice(pipes[0], 0, fd, 0, sent_bytes, SPLICE_F_MOVE);
if (sent_bytes < 0) {
perror("splice");
return -1;
}
然而,结果是相同的:vmsplice: Bad address
。请注意,如果我将对
vmsplice()
或send()
的调用替换为一个仅打印由buf
指向的数据的函数(或者是没有MSG_ZEROCOPY
的send()
),一切都正常工作;因此,用户空间可以访问数据,但vmsplice()
/send(..., MSG_ZEROCOPY)
调用似乎无法处理它。我错过了什么?有没有办法使用从内核驱动程序通过
dma_mmap_coherent()
获得的用户空间地址进行零拷贝TCP发送?我能用另一种方法吗?更新:
因此,我深入研究了内核中的
sendmsg()
MSG_ZEROCOPY
路径,最终失败的调用是get_user_pages_fast()
。此调用返回-EFAULT
,因为check_vma_flags()
发现vma
中设置了VM_PFNMAP
标志。当使用remap_pfn_range()
或dma_mmap_coherent()
将页面映射到用户空间时,会设置此标志。我的下一个方法是找到另一种方法来mmap
这些页面。