零拷贝原理详解

零拷贝原理详解

1. 引言

​ 在nginx、kafka等开源组件的原理和性能调优中,经常会提到零拷贝技术,为了能从原理层面掌握这些常用组件,下面我详细介绍零拷贝的原理。

​ 在介绍零拷贝之前,还有几个概念需要介绍,那就是:用户空间(User space)、内核空间(Kernel space)。

​ 用户空间是指:用户程序代码运行的地方; 内核空间是指:内核代码运行的地方,为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。当进程运行在内核空间时就处于内核态,当进程运行在用户空间时就处于用户态。

​ 内核空间 可以执行任意命令,调用系统的一切资源;用户 只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发出指令。

通过系统接口,进程可以从用户空间切换到内核空间,如下图所示,用户代码调用 cat /etc/hosts,从用户空间切换到内核空间:

image-20220224193041462

​ 操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。空间分配如下图所示:

image-20220224193150565

有了用户空间和内核空间,整个linux内部结构可以分为三部分,从最底层到最上层依次是:硬件-->内核空间-->用户空间。如下图所示:

image-20220224193219304

 需要注意的细节问题:

(1) 内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。

(2) Linux使用两级保护机制:0级供内核使用,3级供用户程序使用。

  内核态与用户态:

(1)当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

(2)当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。

2.零拷贝基础

零拷贝指的是,从一个存储区域到另一个存储区域的 copy 任务没有CPU 参与。零拷贝通常用于网络文件传输,以减少 CPU 消耗和内存带宽占用,减少用户空间与 CPU 内核空间的拷贝过程,减少用户上下文与 CPU 内核上下文间的切换,提高系统效率。

用户上下文指的是用户状态环境,CPU 内核上下文指的是 CPU 内核状态环境。

零拷贝需要 DMA 控制器的协助。DMA,Direct Memory Access,直接内存存取,是 CPU的组成部分,其可以在 CPU 内核(算术逻辑运算器ALU 等)不参与运算的情况下将数据从一个地址空间拷贝到另一个地址空间。

3.传统拷贝方式

下面均以“将一个硬盘中的文件通过网络发送出去”的过程为例,来详细详细分析不同拷贝方式的实现细节。

首先通过应用程序的 read()方法将文件从硬盘读取出来,然后再调用 send()方法将文件发送出去。

image-20220224194648564

该拷贝方式共进行了 4 次用户空间与内核空间的上下文切换,以及 4 次数据拷贝,其中两次拷贝存在 CPU 参与。

我们发现一个很明显的问题:应用程序的作用仅仅就是一个数据传输的中介,最后将kernel buffer 中的数据传递到了 socket buffer。显然这是没有必要的。所以就引入了零拷贝。

4.零拷贝方式

Linux 系统(CentOS6 及其以上版本)对于零拷贝是通过 sendfile 系统调用实现的。

image-20220224194824797

该拷贝方式共进行了 2 次用户空间与内核空间的上下文切换,以及 3 次数据拷贝,但整个拷贝过程均没有 CPU 的参与,这就是零拷贝

我们发现这里还存在一个问题:kernel buffer 到 socket buffer 的拷贝需要吗?kernel buffer 与 socket buffer 有什么区别呢?DMA 控制器所控制的拷贝过程有一个要求,数据在源头的存放地址空间必须是连续的。kernel buffer 中的数据无法保证其连续性,所以需要将数据再拷贝到 socket buffer,socket buffer 可以保证了数据的连续性。

这个拷贝过程能否避免呢?可以,只要主机的 DMA 支持Gather Copy 功能,就可以避免由 kernel buffer 到 socket buffer 的拷贝。

5.Gather Copy DMA 零拷贝方式

由于该拷贝方式是由 DMA 完成,与系统无关,所以只要保证系统支持 sendfile 系统调用功能即可。

image-20220224195339895

该拷贝方式共进行了 2 次用户空间与内核空间的上下文切换,以及 2 次数据拷贝,并且整个拷贝过程均没有 CPU 的参与。

该拷贝方式的系统效率是高了,但与传统相比,也存在有不足。传统拷贝中user buffer 中存有数据,因此应用程序能够对数据进行修改等操作;零拷贝中的 user buffer 中没有了数据,所以应用程序无法对数据进行操作了。Linux 的mmap 零拷贝解决了这个问题。

6.mmap 零拷贝

mmap 零拷贝是对零拷贝的改进。当然,若当前主机的 DMA 支持 Gather Copy,mmap同样可以实现Gather Copy DMA 的零拷贝。

该方式与零拷贝的唯一区别是,应用程序与内核共享了 Kernel buffer。由于是共享,所以应用程序也就可以操作该 buffer 了。当然,应用程序对于 Kernel buffer 的操作,就会引发用户空间与内核空间的相互切换。

image-20220224195602368

该拷贝方式共进行了 4 次用户空间与内核空间的上下文切换,以及 2 次数据拷贝,并且整个拷贝过程均没有 CPU 的参与。虽然较之前面的零拷贝增加了两次上下文切换,但应用程序可以对数据进行修改了。

posted @ 2022-02-24 19:58  bigdata_ai  阅读(385)  评论(0编辑  收藏  举报