零拷贝技术
参考文章:零拷贝
问题引入
问题
- 在RocketMQ消息中间件中使用文件系统来对消息进行持久化,当RocketMQ将消息发送到消费者时需要涉及到文件读取和传输
- 服务端如果提供了文件传输功能
传统的文件传输过程
- DMA将磁盘数据拷贝到内核中
- CPU将内核中的数据拷贝到用户态
- CPU将用户态的数据拷贝到内核态
- DMA将数据拷贝到网卡中发送
整个流程中会涉及到两次系统调用(两次系统调用对应四次上下文切换),四次数据拷贝。
我们可以发现从CPU将内核数据拷贝到用户数据,再将用户数据拷贝内核,这两次拷贝是没有必要的
零拷贝实现方式
使用零拷贝技术可以减少数据拷贝的次数来提高数据的发送效率。
零拷贝技术实现的方式通常有2种
- mmap+write
- sendfile
mmap+write实现方式
从前面可以知道,之所以有四次上下文切换是因为有两次系统调用,两次系统调用分别是read()和write()
而使用mmap()来替换read()后,系统可以直接将内核缓冲区中的数据映射到用户空间,这样操作系统的内核态和用户态之间就不需要再进行任何的数据拷贝
具体过程如下
- 应用进程调用mmap(),DMA会把磁盘的数据拷贝到内核态的缓冲区中,应用程序和操作系统内核态共享这个缓冲区
- 应用进程再调用write(),操作系统直接将内核态缓冲区的数据拷贝到socket缓冲区,这里由CPU执行
- 把内核中socket的数据拷贝到网卡的缓冲区中,这里由DMA执行
从上面可以看出,基于mmap+write的零拷贝技术,可以减少一次数据拷贝,但仍然会有两次系统调用和四次上下文切换
sendfile实现方式
Linux内核版本2.1开始提供了一种专门用于发送文件的系统调用函数sendfile()
用户进程发送一次文件只需要一次系统调用。
具体过程如下
- DMA将文件从磁盘中拷贝到内核态缓冲区
- CPU将内核态缓冲区中的数据拷贝到内核态Socket缓冲区中
- DMA将内核态Socket缓冲区中的数据拷贝到网卡中
这种方式只需要发起一次系统调用(SendFile())、两次上下文切换、三次数据拷贝。
如果网卡支持DMA技术的话,甚至只需要两次数据拷贝即可完成
具体过程如下
- DMA将磁盘文件拷贝到内核态中
- SG-DMA将数据从内核态中直接拷贝到网卡中
可以通过查看网卡是否支持
$ ethtool -k eth0 | grep scatter-gather
scatter-gather: on
零拷贝技术缺点
零拷贝技术不适合用来传输大文件(GB级别)
大文件传输
零拷贝技术会使用PageCache(磁盘数据的缓存),而PageCache使用了预读功能
预读功能
由于空间局部性原理,PageCache在读取数据时,会多读一部分数据到PageCache中,这样如果后续使用到了这部分数据,就可以减少一次IO,并且这个优化在大部分情况下是有效的。
但是在传输大文件时,因为文件本身很大,PageCache被大文件占满,导致其他热点文件无法使用PageCache,这样会降低磁盘读写性能。所以在传输大文件时避免使用零拷贝
大文件传输如何实现
使用异步IO+直接IO
异步IO就是指用户发起一次系统调用时指定数据准备好后需要完成的操作,内核直接将磁盘数据拷贝到用户空间中。