我是零拷贝概念的新手,但据我所知,这是一种不将任何内容从内核缓冲区复制到用户缓冲区并直接在两个内核缓冲区之间传递数据的方法。通过这种方式,CPU不必执行从内核缓冲区到用户缓冲区和返回内核缓冲区的两个数据拷贝。CPU现在所做的就是在两个内核缓冲区之间复制数据,从而将CPU所做的复制数量减少到1。在Linux 2.4及以上版本的某些情况下,即使数据不必在内核缓冲区中复制,也只需将要传输的数据的位置和长度传递到套接字缓冲区,由DMA进行复制。因此命名为零拷贝。
在Linux中执行零拷贝的两种方法是通过sendfile()
或通过splice()
系统调用。
虽然sendfile()
具有仅将数据从文件的页面缓存复制到套接字缓冲区的固有限制,但另一方面splice()
没有这样的限制。但问题是,在splice()
中,任何一个文件描述符都应该是管道。因此,内核必须首先将数据从源文件描述符复制到管道,然后将数据从管道复制回目标内核缓冲区。此处涉及的CPU的副本数为2。
所以我的问题是:
- 那么
splice()
是如何解决我们原来减少CPU拷贝数量的问题的 - 零拷贝是否只能在套接字和文件之间进行,反之亦然,而不能在文件到文件或套接字到套接字之间进行
如果你看一下splice()
手册的NOTES部分,就会发现"通常避免实际拷贝":
虽然我们谈论复制,但通常避免实际复制。内核通过将管道缓冲区实现为一组指向内核内存页面的引用计数指针来实现这一点。内核创建";副本";通过创建引用页面的新指针(用于输出缓冲区(,并增加页面的引用计数,在缓冲区中复制页面的数量:只复制指针,不复制缓冲区的页面。
关于sendfile()
;out fd必须是套接字";自Linux 2.6.33:以来不再是真的
在2.6.33之前的Linux内核中,out_fd必须引用套接字。从Linux 2.6.33开始,它可以是任何文件。如果是常规文件,然后sendfile((适当地更改文件偏移量。