【问题标题】:Is Linux kernel splice() zero copy?Linux 内核 splice() 是零拷贝吗?
【发布时间】:2014-01-12 18:13:56
【问题描述】:

我知道 splice() 是为零拷贝而设计的,并使用 Linux 内核管道缓冲区来实现这一点。例如,如果我想将数据从一个文件描述符(fp1)复制到另一个文件描述符(fp2),则不需要从“内核空间->用户空间->内核空间”复制数据。相反,它只是在内核空间中复制数据,流程将类似于“fp1 -> pipe_read -> pipe_write -> fp2”。 我的问题是剂量内核需要在“fp1 -> pipe_read”和“pipe_write -> fp2”之间复制数据?

维基百科说:

Ideally, splice and vmsplice work by remapping pages and do not actually copy any data,    
which may improve I/O performance. As linear addresses do not necessarily correspond to
contiguous physical addresses, this may not be possible in all cases and on all hardware 
combinations.

我已经为我的问题追踪了kernel source(3.12),我发现“fp1->write_pipe”之间的流程,最后它会在fs/splice.c中调用kernel_readv(),然后调用“do_readv_writev()”,最后称为“aio_write()”

558 static ssize_t kernel_readv(struct file *file, const struct iovec *vec,
559                 unsigned long vlen, loff_t offset)
//*vec would point to struct page which belong to pipe

“read_pipe -> fp2”之间的流程最后会调用“__kernel_write()”,然后调用“fp2->f_op->write()”

430 ssize_t __kernel_write(struct file *file, const char *buf, size_t count, loff_t *pos)
//*buf is the pipe buffer

而且我认为“aio_write()”和“file->f_op_write()”都会执行真正的数据复制,那么 splice() 真的执行零复制吗?

【问题讨论】:

  • 参考Ans
  • 感谢您的回复,但如果 fp 和管道之间存在复制,链接仍然无法响应

标签: c linux linux-kernel


【解决方案1】:

据我了解 splice(),它将读取 fd1 的页面,而 MMU 将映射这些页面。映射创建的引用将被放入管道并交给 fd2。 只要每个参与者都有可用的 DMA,就不应在此过程中复制真实数据。 如果没有可用的 DMA,则需要复制数据。

【讨论】:

    【解决方案2】:

    splice 最有可能是零拷贝工作(对此没有硬性保证,但几乎可以肯定,对于任何合理的最新硬件,它都可以以这种方式工作)。严格遵循文档,您需要使用 SPLICE_F_MOVE 调用它,因此不会制作实际的副本,但我不明白只要有 DMA 支持,它需要如何制作任何一种方式(这是一个相当公平的假设)。

    同样不一定涉及vmsplice,因为它(或连续的splice)仅在提供SPLICE_F_GIFT标志时才适用于零拷贝(在这种情况下,我可以看到否则它是如何工作的,因为“源描述符”是主内存)但是这个标志在某些情况下被破坏并且在其他 Linux 版本中不受支持,并且在顶部记录得很糟糕。
    例如,不清楚之后如何处理内存。文档曾经说过,你以后不能再触碰天赋记忆,这在最近被稍微改写了,但它并没有减少歧义。目前尚不清楚内存区域将变成什么。按照文档,您将不得不泄漏内存。似乎没有通知机制告诉您何时可以安全地释放内存或重用它。

    aio_write 是异步 I/O 的用户态 (Glibc) 实现,它使用线程和 write 系统调用。这通常会执行至少一次从用户空间到内核空间的复制。

    【讨论】:

    • 遵循内核开发线程,您可以重用内存,前提是您有某种特定于协议的方式来知道所有数据已发送。例如对于套接字,另一方需要在协议级别确认数据,并且当您看到您知道重用页面是安全的时。尚不清楚它是否仍然以这种方式工作,当前是否为零副本,因为由于实施错误而暂时禁用。
    • @Eloff:问题是,例如,我怎么知道拼接到套接字的数据(比如说 TCP,所以实际上有一个确认)已成功发送? splice 函数没有告诉我(它只是告诉我它接受了多少,而不是它用它做了什么,或者它发生了什么),并等待'(E)POLLOUT'是徒劳的,因为当有多达 1 个字节的发送缓冲区可用时,它会返回“就绪”,无论之前写入的内容是否被确认甚至发送。
    • 有趣的是,使用 UDP 套接字要容易得多,因为您必须自己在应用程序级别进行确认,因此您可以看到这些。一旦你在那里收到了一个 ACK​​,你发送的东西一定已经到达了另一端。
    • 是的,TCP 也是一样,除了 TCP ack 之外,您还需要在协议中构建自己的 ack 机制(糟糕!)
    猜你喜欢
    • 1970-01-01
    • 2013-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多