思考问题
什么是零拷贝技术?
为什么需要零拷贝?
零拷贝有哪些应用场景?
什么叫做用户态到内核态切换?为什么需要两种状态的切换?
概述
传统的数据拷贝技术
没有DMA的数据拷贝流程如下 👇
用户发起read的系统调用,应用进程从用户态进入到内核态,CPU发送IO请求到磁盘,磁盘准备好数据之后发送中断信号。
之后CPU响应中断,讲磁盘缓冲区数据拷贝到内核缓冲区,数据拷贝完成之后,在把内核缓冲区的数据拷贝到应用进程的缓冲区中。
这个过程中CPU是一直占用的,不能进行其他的操作。最后,应用进程在从内核态切换到用户态。一共进行和4次数据拷贝和2次用户态/内核态的切换。
为了提高CPU 的执行效率,于是又了DMA技术。
什么是 DMA 技术?简单理解就是,在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。
1、用户进程调用read系统指令,进程从用户态切换到内核态,CPU发起IO请求,把读取磁盘数据的任务交给DMA
2、DMA发送IO请求到磁盘,磁盘准备好数据之后,发送中断信号,由DMA响应中断请求
3、DMA读取磁盘缓冲区的数据到内核缓冲区,数据读取完成之后,通知CPU进行处理
4、CPU把内核缓冲区的数据拷贝到应用缓冲期中,最后,进程从内核态切换到用户态。
为什么系统调用的时候需要 「用户态」和 「内核态」的切换
这是因为用户空间没有权限操作磁盘或网卡,内核的权限最高,这些操作设备的过程都需要交由操作系统内核来完成,所以一般要通过内核去完成某些任务的时候,就需要使用操作系统提供的系统调用函数。
这么设计是为了操作系统的安全考虑。
而一次系统调用必然会发生 2 次上下文切换:首先从用户态切换到内核态,当内核执行完任务后,再切换回用户态交由进程代码执行
零拷贝的实现方式
零拷贝有两种实现方式,mmap 和 sendFile 两种。
它们是如何减少「上下文切换」和「数据拷贝」的次数?
mmap实现零拷贝
mmap()
系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。
- 应用进程调用了
mmap()
后,DMA 会把磁盘的数据拷贝到内核的缓冲区里。接着,应用进程跟操作系统内核「共享」这个缓冲区; - 应用进程再调用
write()
,操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态,由 CPU 来搬运数据 - 最后,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程是由 DMA 搬运的。
我们可以得知,通过使用 mmap()
来代替 read()
, 可以减少一次数据拷贝的过程。
但这还不是最理想的零拷贝,因为仍然需要通过 CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里,而且仍然需要 4 次上下文切换,因为系统调用还是 2 次。
sendfile实现零拷贝
在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile()
,函数形式如下:
1 |
|
该系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 3 次数据拷贝
但是这还不是真正的零拷贝技术。因为CPU还有执行一次内核缓冲区到Socket缓冲区的拷贝。
于是,从 Linux 内核 2.4
版本开始起,对于支持网卡支持 SG-DMA 技术的情况下, sendfile()
系统调用的过程发生了点变化,具体过程如下:
第一步,通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;
第二步,缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝;
零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。