零拷贝技术
零拷贝
零拷贝(zero-copy)是一种I/O操作优化技术,可以快速高效地将数据从文件按系统移动到网络接口,而不需要将其从内核空间复制到用户空间,其在FTP或者HTTP等协议中可以显著地提升性能。但是需要注意的是,并不是所有的操作系统都支持这一特性,目前只有在使用NIO和Epoll传输时才可使用该特性。需要注意,他不能用于实现了数据加密或者压缩的文件系统上,只能传输文件的原始内容。这类原始内同也包括加密了的文件内容。
传统I/O操作
读操作
- 应用程序发起读数据操作,触发read()系统调用。这时操作系统会进行一次上下文切换(把用户空间切换到内核空间)。
- 通过磁盘控制器把数据复制到内核缓冲区(页缓存)中,这里发生了一次DMA Copy。
- 然后内核将数据复制到用户空间的应用缓冲区中,发生了一次CPU Copy。
- read调用返回后,会再进行一次上下文切换(把内核空间切换到用户空间)
上述读过程,发生2次上下文切换和2次数据复制(一次是DMA Copy,一次是CPU Copy)。
DMA Copy是内核从磁盘上面读取数据,这是不消耗CPU时间的,是通过磁盘控制器完成的。
写操作
- 应用程序发起写操作,触发write()系统调用,操作进行一次上下文切换(从用户空间到内核空间)
- 把数据复制到内核缓冲区Socket缓冲区,做了一次CPU Copy。
- 内核空间再把数据复制到磁盘或其他存储器(网卡,进行网络传输),进行了DMA Copy。
- 写入结束返回,又从内核空间切换到用户空间。
上述写操作,也发生了2次上下文切换和2次数据复制(一次DMA Copy,一次CPU Copy)
总结:
传统的I/O读写操作,总共进行了4次上下文的切换,4次的数据复制动作。数据在内核空间和应用空间之间来回复制,其实并没有做任何有意义的逻辑,就是单纯的复制而已。所以这个机制太浪费时间,而且浪费CPU时间。
零拷贝技术原理
零拷贝主要是用来解决操作系统在处理I/O操作时,频繁复制数据的问题。零拷贝技术主要有mmap+write、sendfile、splice等几种方式。
虚拟内存
所有的现代操作系统都使用虚拟内存,使用虚拟地址取代物理地址,主要有以下几点好处:
- 多个虚拟内存可以指向同一个物理地址
- 虚拟内存空间可以远远大于物理内存空间
利用上面的第一条特性可以优化,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样在I/O操作时 就不需要来回复制了。
虚拟内存原理
mmap/write方式
使用mmap/write方式替代原来的传统I/O方式,就是利用了虚拟内存的特性。
整体流程的核心区别是,把数据读取到内核缓冲区后,应用程序进行数据写入操作时,直接把内核的Read Buffer的数据复制到Socket Buffer以便写入,这次内核之间的复制也是需要CPU参与的。
上述流程就少了一个CPU Copy,提升了I/O的速度。不过发现上下文的切换还是4次并没有减少,这是因为还是要应用程序发起write操作。
sendfile方式
从linux2.1版本开始,Linux引入了sendfile来简化操作。sendfile方式可以替换上面的mmap/write方式来进一步优化。
sendfile将以下操作:
1
2mmap();
write();替换为:
1
sendfile()
这样就减少了上下文切换,因为少了一个应用程序发起write操作,直接发起sendfile操作。
sendfile方式只有3次数据复制(其中只有一次CPU copy)以及2次上下文切换。
带有scatter/gather的sendfile方式
Linux2.4内核进行了优化,提供了带有scatter/gather的sendfile操作,这个操作可以把最后一次CPU copy去除。其原理就是在内核空间Read Buffer和Socket Buffer不做数据复制,而是将Read Buffer的内存地址、偏移量记录到Socket Buffer中,这样就不需要复制,其本质和虚拟内存的解决方法思路一样,就是内存地址的记录。
scatter/gatter的sendfile只有2次数据复制(都是DMA Copy)以及2次上下文切换。CPU copy已经完全没有。不过这一种收集复制功能是需要硬件及驱动程序支持的。
splice方式
splice调用和sendfile非常类似,用户应用程序必须拥有两个已经打开的文件描述符,一个表示输入设备,另外一个表示输出设备。与sendfile不同的是,splice允许任意两个文件之间互相连接,而并不是文件到socket进行数据传输。对于一个文件描述符发送数据到socket这种特例来说,一直都是使用sendfile系统调用,而splice一直依赖只是一种机制,它并不仅是sendfile的功能,也就是说,sendfile只是splice的一个子集。
总结:
无论是传统I/O方式,还是引入了零拷贝之后,2次DMA Copy是都少不了的。因为两次DMA都是依赖硬件完成的。所以,所谓零拷贝,都是为了减少CPU copy及减少了上下文的切换。
CPU拷贝 | DMA拷贝 | 系统调用 | 上下文切换 | |
---|---|---|---|---|
传统方式 | 2 | 2 | read/write | 4 |
内存映射 | 1 | 2 | mmap/write | 4 |
sendfile | 1 | 2 | sendfile | 2 |
sendfile with dma scatter/gather copy |
0 | 2 | sendfile | 2 |
splice | 0 | 2 | splice | 2 |