我在 Cyclone V SoC 上运行 Linux 5.1,这是一个 FPGA,在一个芯片中具有两个 ARMv7 内核。我的目标是从外部接口收集大量数据,并通过 TCP 套接字将这些数据(部分)流出。这里的挑战是数据速率非常高并且可能接近饱和 GbE 接口。我有一个工作实现,它只使用write()
对套接字的调用,但最高速度为 55MB/s;大约是理论 GbE 限制的一半。我现在正试图让零拷贝 TCP 传输工作以增加吞吐量,但我碰壁了。
为了将 FPGA 中的数据导入 Linux 用户空间,我编写了一个内核驱动程序。该驱动程序使用 FPGA 中的 DMA 块将大量数据从外部接口复制到连接到 ARMv7 内核的 DDR3 内存中。dma_alloc_coherent()
当使用with进行探测时,驱动程序将此内存分配为一组连续的 1MB 缓冲区,并通过在文件中实现并将地址返回给使用预分配缓冲区的应用程序GFP_USER
,将这些内存公开给用户空间应用程序。mmap()
/dev/
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
send()
MSG_ZEROCOPY
vmsplice()
send(..., MSG_ZEROCOPY)
我在这里想念什么?有没有办法使用从内核驱动程序获得的用户空间地址进行零拷贝 TCP 发送dma_mmap_coherent()
?我可以使用另一种方法吗?
更新
所以我更深入地sendmsg()
MSG_ZEROCOPY
研究了内核中的路径,最终失败的调用是get_user_pages_fast()
. 这个调用返回-EFAULT
是因为check_vma_flags()
找到了VM_PFNMAP
在vma
. remap_pfn_range()
当使用or将页面映射到用户空间时,显然会设置此标志dma_mmap_coherent()
。我的下一个方法是找到mmap
这些页面的另一种方法。