什么是DMA?

我们先说为什么要有DMA,在没有DMA之前,磁盘I/O的过程是这样的:
image.png

  1. read函数系统调用,CPU向磁盘发起IO请求
  2. 磁盘控制器收到CPU指令,将数据放入内部的缓存区中,并向CPU发送一个中断信号
  3. CPU收到中断后,磁盘缓冲区的数据拷贝到自己的PageCache寄存器中,再将寄存器的数据写入到内存中。

整个数据的传输过程,都要需要 CPU 亲自参与搬运数据的过程,而且这个过程,CPU 是不能做其他事情的。
DMA(Direct Memery Access)直接内存访问:在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务
image.png
我们对比上两张图,发现从数据从磁盘缓存区到内核缓存区的工作交由了DMA控制器去做(真的是计算机什么问题都可以中间加一层去实现)。但是 CPU 在这个过程中也是必不可少的,因为传输什么数据,从哪里传输到哪里,都需要 CPU 来告诉 DMA 控制器。最后拷贝到从内核缓冲区拷贝到用户缓冲区也是交给CPU去做的。

什么是零拷贝

DMA(直接内存访问)帮我们提高了磁盘I/O时的CPU效率。但是当我们传输一个文件的时候,依然很麻烦:
服务端要提供文件传输的功能,最简单的实现方式是:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。传统 I/O 的工作方式是,数据读取和写入是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。
image.png
这样期间发生了4次用户态和内核态的上下文切换,因为发生了两次系统调用,一次是 read() ,一次是 write(),每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。还发生了以及4次数据拷贝。效率很低。
如何减少「用户态与内核态的上下文切换」和「内存拷贝」的次数呢?
答案就是零拷贝技术

mmap + write

image.png
用mmap函数代替read函数,mmap函数会将内核缓存区和用户缓存区做一个映射,从而减少了从内核缓冲区到用户缓冲区的拷贝。减少了一次数据拷贝

sendfile

image.png
linux 2.1 提供了专门发送文件的系统调用函数sendfile()
直接替代了read() 和 write() 两个系统调用,只剩下了两次上下文切换。还可以直接把内核缓冲区的数据拷贝到socket缓冲区里,3次数据拷贝。

SG-DMA

image.png
这就是所谓的零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。
零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。
kafka、nginx都是使用零拷贝技术,提高了处理大数据的效率。

PageCache是什么

上文提到的内核缓冲区实际上就是磁盘告诉缓存(PageCache)
好处:

  • 利用PageCache来缓存最近被访问过的数据,提供缓存命中率
  • 提供预读功能,因为读取磁盘的磁盘操作非常耗时,可以在read的过程中,将数据预读到PageCache中。

但是在处理大文件的时候我们不能使用PageCache,因为这会影响热点数据。
针对大文件传输
image.png
在高并发的场景下,针对大文件的传输的方式,应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术