零拷贝
磁盘可以说是计算机最慢的硬件之一,读固态硬盘比读内存慢了1000倍,所以针对优化磁盘的技术非常的多,比如零拷贝、直接IO、异步IO等等
DMA 直接内存访问(Direct Memory Access)
-
没有DMA的情况
-
CPU发出对应的指令给磁盘控制器,然后返回
-
磁盘控制器收到指令后,就开始准备数据,会把数据放入到磁盘控制器的内部缓冲区中,然后产生一个IO中断信号
-
CPU收到中断信号后,停下手头的工作,接着把磁盘控制器的缓冲区的数据一个字节一个字节读进自己的寄存器,然后把寄存器数据写入内存,而在数据传输的期间CPU是无法执行其他任务的。
什么是DMA技术:在进行I/O设备和内存的数据传输的时候,数据搬运的工作全部交给了DMA控制器,而CPU不再参与任何与数据搬运相关的事情
-
有DMA的情况
-
用户调用read方法,向操作系统发出I/O请求,请求读取数据到自己的内存缓冲区,进程进入阻塞状态
-
操作系统收到请求后,进一步将I/O请求发送到DMA,然后让CPU执行其他任务
-
磁盘收到DMA的I/O请求,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向DMA发起中断信号,告知自己缓冲区已满
-
DMA 收到磁盘的信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占用CPU
-
当DMA读取了足够多的数据,就会发送中断信号给CPU
-
CPU收到DMA的信号,知道数据已经准备好了,于是将数据从内核拷贝到用户空间,系统调用返回
可以看到,CPU不再参与【将数据从磁盘控制器缓冲区搬运到内核空间】的工作,这部分完全由DMA完成
如何优化文件传输的性能?
首先,期间发生了4次用户态和内核态的上下文切换。因为发生了两次系统调用:read()和write() 上下文切换成本并不小,一次需要几时纳秒到几微秒
其次,还发生了四次数据拷贝:
- 第一次: 把磁盘数据拷贝到操作系统内核里。
- 第二次: 把内核缓冲区数据拷贝到用户的缓冲区里
- 第三次: 把用户缓冲区里的数据再拷贝到内核的socket缓冲区中
- 第四次: 把内核的socket缓冲区数据,拷贝到网卡的缓冲区里 2次DMA,2次CPU
一、如何减少用户态和上下文切换的次数? - 减少系统调用
二、如何减少数据拷贝次数? 从内核缓冲区拷贝到用户空间,再从用户空间拷贝到socket缓冲区没有必要,因为文件传输过程中,用户空间不会对数据再加工。
如何实现零拷贝?
通常方案有两种:
- mmap + write
- sendfile
mmap+write
read()系统调用的过程中会把内核缓冲区的数据拷贝到用户的缓冲区里,于是为了减少开销,可以使用mmap()替换read()系统调用函数
buf = mmap(file, len);
write(sockfd, buf, len);
mmap()系统调用函数会直接把内核缓冲区里的数据【映射】到用户空间。
具体过程如下:
- 应用进程调用mmap(), DMA会把磁盘的数据拷贝到内核缓冲区里。接着应用进程跟操作系统内核【共享】这个缓冲区
- 应用进程在调用write(),操作系统直接将内核缓冲区的数据拷贝到socket缓冲区中,这一切发生在内核态,由CPU搬运数据
- 最后把内核的socket缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程时DMA搬运的。
mmap()替代read(),可以减少一次数据拷贝的过程。 但这不是最理想的零拷贝,因为仍然需要通过CPU把内核缓冲区的数据拷贝到socket缓冲区里,而且仍然需要4次上下文切换,因为系统调用还是2次。
sendfile
使用sendfile 系统调用 真正的零拷贝技术,如果网卡支持SG-DMA技术,我们可以进一步减少通过CPU把内核缓冲区里的数据拷贝到socket缓冲区里的过程 可以使用linux命令来查看网卡是否支持scatter-gather特性
$ ethtool -k eth0 | grep scatter-gather
scatter-gather: on
于是,从linux内核2.4起,对于网卡支持SG-DMA技术的情况下,sendfile()系统调用发生了变化:
- 通过DMA将磁盘上的数据拷贝到内存缓冲区里
- 缓冲区描述符和数据长度传到socket缓冲区,这样网卡的SG-DMA控制器就可以直接将内核缓冲区数据拷贝到网卡的缓冲区里,此过程不再需要将数据从操作系统内核缓冲区拷贝到socket缓冲区,这样就叫少了一次数据拷贝
此过程,只用了2次数据拷贝
- 零拷贝(Zero-copy),因为我们没有在内存层面去拷贝数据,也就是说全程没有cpu参与搬运数据,所以的数据都是通过DMA的方式来传输的
- 相比传统的文件传输方式,减少了2次上下文切换和数据拷贝次数,只需要2次上下文切换和数据拷贝次数,就可以完成文件的传输,而且2次数据拷贝过程,都不需要通过CPU,2次都通过DMA来搬运。
- 不允许进程对数据进行加工的,比如压缩数据再发送
pagecache
pagecache: 实现程序的局部性原理 空间局部性:实现预读(也是顺序读比随机读快的原因),一个位置被使用,那么它附近的位置也会被引用 时间局部性:来缓存最近被访问的数据
在高并发场景下,针对大文件的传输需要使用【异步I/O+直接I/O】 直接I/O:绕过pageCache
- 大文件 异步I/O+直接I/O
- 小文件 零拷贝