简单聊一聊零拷贝
前言
零拷贝我们经常听说,相比于传统的io在性能上有了很大提升,那么在os里面他是怎么设计的呢,与传统io又有什么区别呢
Direct Memory Access(DMA)
在聊零拷贝之前,先看一看DMA在百度百科中的解释如下
DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
通俗来讲一些硬件子系统在读取内存的时候需要通过cpu调度内存,然后再获取到内存当中的数据,这样的一个过程大大依赖cpu性能,当cpu负载过高时硬件获取内存内容的速度就会大大减小
而DMA技术则不再依赖cpu,而是硬件系统直接去内存读取
传统数据io
传统io模式就是我们在以前学习编程基础时的io和网络这块,在传统模式下,我们从磁盘读取一个文件然后发送至网络中,通常流程就是读取文件,然后将文件转为字节流写入Socket中,进行传输,这样一个过程在OS中中的流程如下
在传统io模型中,读取一个文件的流程如下:
- 从硬盘通过DMA拷贝将文件读取到内核缓冲区(第一次拷贝)
- 从内存缓冲区复制到用户缓冲区中(第二次复制,且从内核态转换到用户态)
- 从用户缓冲区复制到Socket缓冲区(第三次复制,且从用户态切换到内核态)
- 从Socket缓冲区通过DMA拷贝到网络中(第四次复制)
从这个流程中可以看出在一次文件读取到网络的过程中,一共发生了四次复制过程,其中DMA拷贝两次CPU拷贝两次,这样的拷贝方式非常占用资源,而且存在一些不必要的复制过程
mmap映射
mmap通过与用户缓冲区共享内核缓冲区从而减少复制次数
mmap读取一个文件流程如下:
- 首先从硬盘文件复制到内核缓冲区(第一次复制)
- 由于用户缓冲区与共内核缓冲区共享,所以不需要再将内核缓冲区复制到用户缓冲区,用户缓冲区直接操作共享的内核缓冲区即可
- 内核缓冲区将数据拷贝到Socket缓冲区(第二次复制)
- Socket缓冲区再使用DMA拷贝到网络中(第三次复制)
可以看到相比于传统拷贝,mmap拷贝的复制次数减少了一次。由于用户态中的用户缓冲区直接操作内核缓冲区,因此用户态对文件的操作有容易丢失的缺点。
sendFile
在mmap中依然发生了三次复制,能不能再进行优化呢?Linux2.1提供了sendFile函数来进行文件拷贝传输,其基本流程如下
- 首先从硬盘文件复制到内核缓冲区(第一次复制)
- 从内核态再将文件拷贝到Socket缓冲区(第二次复制)
- 从Socket缓冲区使用DMA拷贝到网络(第三次复制)
从这个流程可以看出sendFile的拷贝过程依然是三次复制,但是用户缓冲区没有参与到拷贝当中,因此在这个过程中虽然复制次数没变,但是减少了用户态和内核态的切换次数,效率有了一定提升
Linux2.4之后在这个基础上又进行了修改,修改后的拷贝流程如下
- 首先从硬盘文件复制到内核缓冲区(第一次复制)
- 从内核缓冲区直接拷贝到网络当中(第二次复制)
- 内核缓冲区拷贝一些offset和length信息到Socket缓冲区
从上面的流程可以看到整个过程发生了三次拷贝,但是由于从内核缓冲区复制到Socket缓冲区拷贝内容较少,其消耗可忽略不计,因此修改后的SendFile可以看成之发生了两次拷贝,与传统拷贝相比拷贝次数减少一半,并且用户态和内核态的切换更少,性能更高
mmap和sendFile比较
虽然叫零拷贝,但并不意味着没有发生复制过程,而是相比于传统拷贝复制过程更少,用户态和内核态切换次数更少
mmap和sendFile比较如下
mmap | sendFile |
---|---|
mmap适合小文件的拷贝 | sendFile适合大文件的传输 |
mmap发生4次用户态和内核态切换 | sendFile发生3次用户态和内核态切换 |
mmap发生3次数据拷贝 | sendFile发生2次数据拷贝 |