GeekIBLi

零拷贝技术

2021-09-09

思考问题

什么是零拷贝技术?

为什么需要零拷贝?

零拷贝有哪些应用场景?

什么叫做用户态到内核态切换?为什么需要两种状态的切换?

概述

传统的数据拷贝技术

没有DMA的数据拷贝流程如下 👇

用户发起read的系统调用,应用进程从用户态进入到内核态,CPU发送IO请求到磁盘,磁盘准备好数据之后发送中断信号。

之后CPU响应中断,讲磁盘缓冲区数据拷贝到内核缓冲区,数据拷贝完成之后,在把内核缓冲区的数据拷贝到应用进程的缓冲区中。

这个过程中CPU是一直占用的,不能进行其他的操作。最后,应用进程在从内核态切换到用户态。一共进行和4次数据拷贝和2次用户态/内核态的切换。

image-20210909150331477

为了提高CPU 的执行效率,于是又了DMA技术。

什么是 DMA 技术?简单理解就是,在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务

image-20210909151257401

1、用户进程调用read系统指令,进程从用户态切换到内核态,CPU发起IO请求,把读取磁盘数据的任务交给DMA

2、DMA发送IO请求到磁盘,磁盘准备好数据之后,发送中断信号,由DMA响应中断请求

3、DMA读取磁盘缓冲区的数据到内核缓冲区,数据读取完成之后,通知CPU进行处理

4、CPU把内核缓冲区的数据拷贝到应用缓冲期中,最后,进程从内核态切换到用户态。

为什么系统调用的时候需要 「用户态」和 「内核态」的切换

这是因为用户空间没有权限操作磁盘或网卡,内核的权限最高,这些操作设备的过程都需要交由操作系统内核来完成,所以一般要通过内核去完成某些任务的时候,就需要使用操作系统提供的系统调用函数。

这么设计是为了操作系统的安全考虑。

而一次系统调用必然会发生 2 次上下文切换:首先从用户态切换到内核态,当内核执行完任务后,再切换回用户态交由进程代码执行

零拷贝的实现方式

零拷贝有两种实现方式,mmap 和 sendFile 两种。

它们是如何减少「上下文切换」和「数据拷贝」的次数?

mmap实现零拷贝

mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。

image-20210909152900745
  • 应用进程调用了 mmap() 后,DMA 会把磁盘的数据拷贝到内核的缓冲区里。接着,应用进程跟操作系统内核「共享」这个缓冲区;
  • 应用进程再调用 write()操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态,由 CPU 来搬运数据
  • 最后,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程是由 DMA 搬运的。

我们可以得知,通过使用 mmap() 来代替 read(), 可以减少一次数据拷贝的过程。

但这还不是最理想的零拷贝,因为仍然需要通过 CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里,而且仍然需要 4 次上下文切换,因为系统调用还是 2 次。

sendfile实现零拷贝

在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile(),函数形式如下:

1
2
3
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
// 它的前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。

该系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 3 次数据拷贝

image-20210909153709043

但是这还不是真正的零拷贝技术。因为CPU还有执行一次内核缓冲区到Socket缓冲区的拷贝。

于是,从 Linux 内核 2.4 版本开始起,对于支持网卡支持 SG-DMA 技术的情况下, sendfile() 系统调用的过程发生了点变化,具体过程如下:

第一步,通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;

第二步,缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝;

image-20210909154030275

零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。

参考资料