27

当 splice 被引入时,内核列表中讨论了 sendfile 是基于 splice 重新实现的。splice SLICE_F_MOVE 的文档指出:

尝试移动页面而不是复制。这只是对内核的一个提示:如果内核无法从管道中移动页面,或者管道缓冲区没有引用完整页面,则仍然可以复制页面。这个标志的初始实现是错误的:因此从 Linux 2.6.21 开始,它是一个无操作(但仍然允许在 splice() 调用中);将来,可能会恢复正确的实施。

那么这是否意味着 Linux 没有用于写入套接字的零拷贝方法?或者这是在某个时候修复的,并且多年来没有人更新文档?sendfile 或 splice 在任何最新的 3.x 内核版本中是否具有零拷贝实现?

由于谷歌对此查询没有答案,我正在为下一个可怜的 schmuck 创建一个 stackoverflow 问题,他想知道使用 vmsplice 和 splice 或 sendfile 是否有任何好处而不是普通的旧写。

4

2 回答 2

19

sendfile从那以后一直存在,并且仍然是零拷贝(假设硬件允许,但通常是这样)。零拷贝是首先拥有这个系统调用的全部意义。sendfile现在被实现为splice.

这表明splice也是零拷贝,而且确实如此。至少在理论上,至少在某些情况下是这样。问题在于弄清楚如何正确使用它,以便它可靠地工作,因此它是零拷贝。至少可以说,文档是……稀疏的。

特别是,splice如果页面是作为“礼物”提供的,即您不再拥有它们(正式,但实际上您仍然拥有),则仅适用于零拷贝。如果您只是将文件描述符拼接到套接字上,这不是问题,但如果您想将数据从应用程序的地址空间或从一个管道拼接到另一个管道,这将是一个大问题。目前还不清楚之后(以及何时)如何处理这些页面。该文档指出,您以后不得触摸这些页面或对它们做任何事情,永远不会,永远不会。因此,如果您按照文档的字母进行操作,则必须泄漏内存。
这显然是不正确的(不可能),但没有好的方法知道(至少对您而言!)何时可以安全地重用或释放该内存。内核做一个sendfile会知道,因为一旦它收到 TCP ACK,它就知道不再需要该数据。问题是,您永远看不到 ACK。您只知道何时splice返回的是数据已被接受发送(但您不知道它是否已经发送或接收,也不知道何时会发生)。
这意味着您需要在应用层以某种方式解决这个问题,或者通过手动 ACK(通过可靠的 UDP 免费提供),或者假设如果对方发送了对您的请求的回答,他们显然一定已经收到了请求.

您必须管理的另一件事是有限的管道空间。默认是很小的,但即使你增加大小,也不能天真地拼接任意大小的文件。sendfile另一方面,只会让你这样做,这很酷。

总而言之,sendfile很好,因为它可以工作,而且效果很好,而且您不需要关心上述任何细节。它不是灵丹妙药,但它确实是一个很好的补充。
就个人而言,我会远离splice它的家人,直到整个事情得到彻底检修,直到 100% 清楚你必须做什么(以及何时)以及你不必做什么。

write无论如何,对于大多数应用程序而言,与普通旧设备相比,真正有效的收益是微不足道的。我记得几年前 Torvalds 先生的一些不太礼貌的评论(当时 BSD 有一种形式write可以通过重新映射页面来获得零拷贝,而 Linux 没有)它指出制作副本通常不是任何问题,但是玩页面技巧是[不会在此处重复]

于 2014-08-12T13:33:03.670 回答
4

根据截至 2014-07-08 的 splice 相关手册页,我引用:

尽管我们谈论复制,但通常会避免实际复制。内核通过将管道缓冲区实现为一组指向内核内存页面的引用计数指针来实现这一点。内核通过创建引用页面的新指针(用于输出缓冲区)并增加页面的引用计数来在缓冲区中创建页面的“副本”:只复制指针,而不复制缓冲区的页面。

因此,是的,splice 在大多数情况下被记录为当前是零拷贝。

于 2014-08-12T12:45:22.947 回答