what
零拷贝是相对于操作系统来说的,不需要cpu来进行拷贝数据了,所以称之为零拷贝。
但是实际上还是会进行数据拷贝的。
传统IO
传统IO
- 将磁盘文件,拷贝到操作系统内核缓冲区
- 将内核缓冲区的数据,拷贝到应用程序的buffer
- 将应用程序buffer中的数据,再拷贝到socket网络发送缓冲区(属于内核缓冲区)
- 将socket buffer数据,拷贝到网卡,由网卡进行网络传输
传统IO方式,读取磁盘文件进行网络发送,经过4次数据拷贝,4次上下文切换。
如果你单独看这个过程感觉是2次上下文切换,但是其实一开始操作的时候是在用户态的,最后数据从网卡发送结束之后,也要返回用户态。
MMAP
这种方式使用mmap()代替了read()
- 磁盘的数据通过DMA拷贝到内核缓冲区
- 操作系统把这块内核缓冲区与应用程序共享,避免了用户缓冲区和内核缓冲区的跨界复制
- 应用程序调用write()直接从内核缓冲区的内容拷贝到socket缓冲区
- 最后系统将socket的数据传输到网卡,由网卡进行传输
MMAP减少了一次拷贝,提升了效率,但是并不减少上下文切换的次数,所以总的来说mmap发生了 3次数据拷贝,4次上下文切换
SendFile
2.1版本
这种方式是使用sendfile代替了read+write操作
- 首先sendfile系统调用,通过DMA引擎将磁盘文件拷贝到内存缓冲区
- 在内核缓冲区,内核将数据拷贝到socket缓冲区
- 最后,DMA将数据从内核拷贝到网卡,由网卡传输
数据总共发生3次拷贝,2次上下文切换
在这个版本下 其实还是有cpu 拷贝的,所以这个版本是不能称之为零拷贝的,至少2.4版本之后了
2.4版本
- 磁盘数据通过 DMA 拷贝到内核态 Buffer
- 直接通过 DMA 拷贝到 NIC Buffer
对比
- 都是Linux内核提供,实现零拷贝的API
- sendfile是将读到内核缓冲区的数据,直接转到socket buffer,进行网络发送
- mmap是将磁盘文件读取到内核缓冲区后进行映射,和用户缓冲区共享数据,然后CPU在拷贝数据到socket buffer,进行网络发送
- mmap 适合小数据量读写,sendFile 适合大文件传输。
- mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。
- sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)
使用
- rocketMQ 在消费消息时,使用了 mmap
- kafka 使用了 sendFile
- 如Tomcat、Nginx、Apache等web服务器返回静态资源等,将数据用网络发送出去,都运用了sendfile。
- Java NIO对sendfile的支持就是FileChannel.transferTo()/transferFrom()。