1、在没有DMA技术之前的I/O过程是这样的:
整个传输过程中都要CPU亲自参与搬运数据的过程,而且这个过程,CPU是不能做其他事情的。这会大大降低CPU的效率,并且如果使用千兆网卡或者硬盘传输大量数据时,都用CPU搬运的话,肯定忙不过来
2、DMA技术
直接内存访问 (Direct Memory Access) ,简单理解就是,在进行I/O设备和内存的数据传输的时候,数据搬运的工作全部交给DMA控制器,而CPU不再参与任何与数据搬运相关的事情,这样CPU就可以去处理其他的事务。
与磁盘的交互(IO操作)都交给了DMA控制器去做,CPU得到解放
具体过程:
早期DMA只存在于主板上,如今IO设备越来越多,数据传输的需求也不尽相同,所以每个I/O设备里面都有自己的DMA控制器
3、传统的文件传输
如果服务端要提供文件传输的功能,我们能想到的最简单的方式是:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。
而传统的I/O的工作方式是,数据读取和写入是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统的I/O接口从磁盘读取或写入。
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
这两行代码干了非常多的事,如图:
可以看到,这期间发生了4次用户态与内核态的上下文切换 ,因为发生了两次系统调用,一次是read(),一次是write()。每一次系统调用,都要从用户态切换到内核态,等内核态完成任务后又要切换回用户态。而上下文切换的成本也很大,尤其在高并发的场景下,这类时间容易被累积放大,从而影响系统的性能。
其次,还发生了4次数据拷贝 ,两次是DMA拷贝,两次是CPU拷贝。
只是搬运一份数据,结果却进行了四次数据拷贝,过多的数据拷贝会消耗CPU资源,大大降低系统性能。
这种传统的文件传输存在冗余的上下文切换和拷贝次数!
优化文件传输
1、如何减少用户态与内核态的上下文切换的次数 ?
读取磁盘数据时,之所以要发生上下文切换,这是因为用户空间没有权限操作磁盘或网卡,内核的权限最高。所以一般要通过内核去完成某些任务的时候,就需要使用操作系统提供的系统调用函数。
而一次系统调用必然会发生2次上下文切换:从用户态切换到内核态,内核态完成任务后再切换回用户态
所以要减少上下文切换的次数就要减少系统调用的次数
2、如何减少数据拷贝的次数?
传统的文件传输过程会经过四次数据拷贝,而这其中,从内核的读缓冲区拷贝到用户的缓冲区中,再从用户的缓冲区拷贝到socket的缓冲区 ,这个过程是没有必要的。因为文件传输的应用场景中,在用户空间我们并不会对数据再加工 ,可以省去拷贝到数据缓冲区这一步。
零拷贝
1、实现零拷贝技术的方式通常有2种:
它们是如何减少上下文切换和数据拷贝的次数?
2、mmap+write
read()系统调用的过程,会将内核缓冲区的数据拷贝到用户的缓冲区,为了减少这一步开销,我们可以用mmap()替换read()系统调用函数。
buf = mmap(file, len);
write(sockfd, buf, len);
mmap()系统调用函数会直接把内核缓冲区里的数据映射到用户空间,这样操作系统内核与用户空间就不需要再进行任何的数据拷贝操作
这样做会将内核的读缓冲区拷贝到用户缓冲区,再从用户缓冲区拷贝到socket的缓冲区 这两次拷贝变成内核缓冲区拷贝到socket缓冲区 这一次拷贝
即从原来的四次拷贝变为三次拷贝,减少了一次数据拷贝的过程。
但这并不是理想的零拷贝,因为仍然需要通过CPU把内核缓冲区的数据拷贝到socket缓冲区中,而且仍然需要4次上下文切换
3、sendfile
在Linux内核版本2.1中,提供了一个专门发送文件的系统调用函数sendfile()。
从 Linux 内核 2.4 版本开始起,对于⽀持⽹卡⽀持 SG-DMA 技术的情况下, sendfile() 系统,调⽤的过程发⽣了点变化,具体过程如下:
所以,这个过程之中,只进行了一次系统调用(sendfile(),进⾏了 2 次数据拷⻉(磁盘到内核,内核到网卡)
这就是所谓的零拷贝技术。因为我们没有在内存层面去拷贝数据,全程没有通过CPU来搬运数据
4、总结
实现零拷贝技术的文件传输方式相比传统文件传输的方式,减少了2次上下文切换和数据拷贝次数。只需要进行两次上下文切换和两次数据拷贝就可以完成文件的传输。并且两次数据拷贝都不要通过CPU完成,是由DMA来完成。总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上
总结
1、早期I/O操作,内存与磁盘的数据传输的工作都是由CPU完成,此时CPU不能进行其他任务,会特别浪费CPU资源
2、为了解决这一问题,出现了DMA技术。每个I/O设备都有自己的DMA控制器,通过这个DMA 控制器,CPU 只需要告诉 DMA 控制器,我们要传输什么数据,从哪⾥来,到哪⾥去,就可以放⼼离开了。后续的实际数据传输⼯作,都会由 DMA 控制器来完成,CPU 不需要参与数据传输的⼯作。
3、传统的IO工作方式,从硬盘读取数据,通过网卡向外发送 。需要进行4次用户态与内核态之间的上下文切换,4次数据拷贝。其中2次数据拷贝发生在内核的缓冲区和对应的硬件设备(磁盘、网卡)之间,由DMA完成;2次数据拷贝发送在用户态和内核态之间,由CPU完成。这种传输方式有冗余的上下文切换次数和数据拷贝次数!
4、对于文件传输的优化,实现零拷贝。通过一次系统调用(sendfile)合并了磁盘读取(read)和网络发送(write)两个操作 ,降低了上下文切换次数;只进行了两次数据拷贝,从磁盘文件到内核缓冲区,从内核缓冲区到网卡,都是由DMA搬运,降低了数据拷贝次数 !
5、零拷贝技术是基于 PageCache 的,PageCache 会缓存最近访问的数据,提升了访问缓存数据的性能,同时,为了解决机械硬盘寻址慢的问题,它还协助 I/O 调度算法实现了 IO 合并与预读,这也是顺序读⽐随机读性能好的原因。这些优势,进⼀步提升了零拷⻉的性能。
6、当传输大文件时,不能使用零拷贝,因为可能由于 PageCache 被⼤⽂件占据,⽽导致「热点」小文件无法利用到 PageCache,并且大文件的缓存命中率不⾼,这时就需要使用「异步 IO + 直接 IO 」的方式。