我正在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 进行探测时GFP_USER
,驱动程序将该内存分配为一堆连续的1MB缓冲区,并通过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()
或将页面映射到用户空间时,显然会设置此标志dma_mmap_coherent()
。我的下一个方法是找到另一种访问mmap
这些页面的方法。