Linux有零拷贝技术吗?splice或sendfile?

28
当splice被引入时,内核邮件列表上讨论了sendfile是基于splice重新实现的。splice SLICE_F_MOVE的文档说明如下:
尝试移动页面而不是复制。这只是对内核的提示:如果内核无法从管道中移动页面,或者管道缓冲区不参考完整页面,则页面仍可能被复制。该标志的初始实现存在缺陷:因此,从Linux 2.6.21开始,它是一个无操作(但在splice()调用中仍然允许);将来可能会恢复正确的实现。
这是否意味着Linux没有零拷贝方法来写入套接字?还是在某个时候已经修复了,但多年来没有更新文档?在最新的3.x内核版本中,sendfile、splice或vmsplice是否有任何零拷贝实现?
由于谷歌没有回答这个问题,我正在创建一个stackoverflow问题,为下一个想知道是否使用vmsplice、splice或sendfile比使用普通写入有任何好处的可怜人提供帮助。

1
或许有些老旧,但仍然相关:http://blog.superpat.com/2010/06/01/zero-copy-in-linux-with-sendfile-and-splice/comment-page-1/ - Paul
我对切片不是很了解,但如果你特别感兴趣的是零拷贝套接字,那么你应该看一下内存映射套接字:https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt - gct
@gct packet_mmap 不是零拷贝,因为在用户空间代码中没有办法分配 DMA 友好的内存。在互联网上有关于此进行过讨论,但已经很久了,并且关于零拷贝的信息非常少。它可能已经有所改变。 - Eloff
1
这里是为什么它不是零拷贝的解释:http://yusufonlinux.blogspot.com/2010/11/data-link-access-and-zero-copy.html?showComment=1291517960894#c3884991672834311362 - Eloff
1
@Eloff:全新的AF_XDP利用从Infiniband(RDMA)和DPDK借鉴的思想,实现了原始数据包的真正零拷贝。 - Nemo
显示剩余2条评论
2个回答

21

sendfile自问世以来一直是零拷贝的(假设硬件支持,但通常情况下是这样)。零拷贝是首次引入此系统调用的全部意义。现在,sendfile实现为splice的包装器。

这表明splice也是零拷贝的,确实如此。至少在理论上,在某些情况下也是如此。问题在于如何正确使用它,使其可靠地工作并且具有零拷贝。文档非常简略。

特别是,splice仅在指定页面为“gift”时才能零拷贝,即您不再拥有它们(正式上是这样,但实际上您仍然拥有它们)。如果您只是将文件描述符拼接到套接字上,则不存在问题,但是如果您想从应用程序的地址空间或从一个管道中拼接数据,则存在很大问题。之后需要弄清楚该怎么处理页面(以及何时)。文档规定,您永远不能触摸页面或对其执行任何操作。因此,如果您遵循文档的字面含义,则必须泄漏内存。
显然,这是不正确的(它不可能是这样),但是没有好的方法可以知道(至少对您来说!)何时可以安全地重用或释放该内存。内核执行sendfile将会知道,因为一旦收到TCP ACK,它就知道不再需要数据了。问题是,您永远不会看到ACK。当splice返回时,您只知道数据已被接受以供发送(但您不知道它是否已经发送或接收,也不知道何时会发生这种情况)。
这意味着您需要在应用程序层上找出某种方式来解决此问题,可以通过手动ACK(可靠UDP自带)或者假设如果对方发送了回应,则他们显然必须已经收到请求。

你需要管理的另一件事是有限的管道空间。默认很小,但即使你增加了大小,也不能轻易地拼接任何大小的文件。sendfile则可以让你这样做,非常方便。

总而言之,sendfile 很好用,而且它的性能很好,你不需要关心上面提到的任何细节。它并非万能药,但肯定是一个非常好的补充。
我个人会避免使用 splice 及其相关技术,直到整个过程得到极大改进,并且清楚地知道你必须做什么(以及何时做)以及不必做什么为止。

对于大多数应用程序而言,与普通的write 相比,真正有效的收益微乎其微。几年前,Torvalds先生曾发表过一些不太礼貌的评论(当BSD具有一种形式的 write 时,它能通过重新映射页面来进行零拷贝,而Linux没有),指出制作副本通常不是任何问题,但玩弄页面则另当别论。


4
Torvalds: "我声称 Mach 的开发者(以及显然还包括 FreeBSD)是无能的白痴。在虚拟内存上玩游戏很糟糕。内存拷贝也不好,但说实话,与虚拟内存游戏相比,内存拷贝通常有更少的不利因素,而更大的缓存只会进一步证明这一点。" http://yarchive.net/comp/linux/splice.html - Ben
2
@Ben:是的,那就是引用的内容 :-) - Damon
1
我以前实现过vmsplice和splice,使用赠送页面和ack(应用程序级别的ack,而不是tcp ack)进行垃圾回收。这很棘手,有许多陷阱(把我卡住了),相比于普通的写操作,你可以指望它增加大约500行C++代码。最简单的方法是如果你从环形缓冲区发送数据,因为你的iovec数组只有一个元素,内存永远不需要被释放,并且你需要一种确认已发送数据的方式。它只能是零拷贝,如果你的页面是DMAable的(某些卡不能DMA每个地址),并且你的网络卡支持DMA。否则内核会进行复制。 - Eloff
5
Torvalds所引述的内容是关于BSD开发者如何将页面标记为写时复制。这需要进行TLB刷新,非常消耗资源,需要大约2000个周期,如果在内核完成处理之前向该缓冲区写入,则会执行复制操作,使您陷入负面境地。 - Eloff
2
@Eloff,Linux说使用循环缓冲区将内核缓冲区大小加倍,可以在不进行安全检查的情况下完成:http://yarchive.net/comp/linux/splice.html - akostadinov

4
根据2014年7月8日的相关手册,我引用splice的内容如下:
尽管我们谈论复制,实际上通常避免复制。内核通过将管道缓冲区作为对内核内存页面的引用计数指针集来实现这一点。内核通过创建新指针(用于输出缓冲区)引用页面并增加页面的引用计数来在缓冲区中创建页面的“副本”:仅复制指针,而不是缓冲区的页面。
因此,在大多数情况下,splice文档目前记录为零拷贝。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接