一口Linux

电子技术应用专栏作家——一口Linux。一起学习嵌入式、Linux、网络、驱动、arm知识

一文读懂计算机内核态、用户态和零拷贝技术

0
阅读(386)

存储介质的性能

话不多说,先看一张图,下图左边是磁盘到内存的不同介质,右边形象地描述了每种介质的读写速率。一句话总结就是越靠近cpu,读写性能越快。了解了不同硬件介质的读写速率后,你会发现零拷贝技术是多么的香,对于追求极致性能的读写系统而言,掌握这个技术是多么的优秀~


image.png

上图是当前主流存储介质的读写性能,从磁盘到内存、内存到缓存、缓存到寄存器,每上一个台阶,性能就提升10倍。如果我们打开一个文件去读里面的内容,你会发现时间读取的时间是远大于磁盘提供的这个时延的,这是为什么呢?问题就在内核态和用户态这2个概念后面深藏的I/O逻辑作怪。

内核态和用户态

内核态:也称为内核空间。cpu可以访问内存的所有数据,还控制着外围设备的访问,例如硬盘、网卡、鼠标、键盘等。cpu也可以将自己从一个程序切换到另一个程序。

用户态:也称为用户空间。只能受限的访问内存地址,cpu资源可以被其他程序获取。

image.png

坦白地说内核态就是一个高级管理员,它可以控制整个资源的权限,用户态就是一个业务,每个人都可以使用它。那计算机为啥要这么分呢?且看下文......

由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络。CPU划分出两个权限等级:用户态和内核态。

32 位操作系统和 64 位操作系统的虚拟地址空间大小是不同的,在 Linux 操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,如下所示:

image.png


通过这里可以看出:

32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间;

64 位系统的内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。

内核态控制的是内核空间的资源管理,用户态访问的是用户空间内的资源。

从用户态到内核态切换可以通过三种方式:

系统调用,其实系统调用本身就是中断,但是软件中断,跟硬中断不同。

异常:如果当前进程运行在用户态,如果这个时候发生了异常事件,就会触发切换。例如:缺页异常。

外设中断:当外设完成用户的请求时,会向CPU发送中断信号。

内核态和用户态是怎么控制数据传输的?

举个例子:当计算机A上a进程要把一个文件传送到计算机B上的b进程空间里面去,它是怎么做的呢?在当前的计算机系统架构下,它的I/O路径如下图所示:

image.png


计算机A的进程a先要通过系统调用Read(内核态)打开一个磁盘上的文件,这个时候就要把数据copy一次到内核态的PageCache中,进入了内核态;

进程a负责将数据从内核空间的 Page Cache 搬运到用户空间的缓冲区,进入用户态;

进程a负责将数据从用户空间的缓冲区搬运到内核空间的 Socket(资源由内核管控) 缓冲区中,进入内核态。

进程a负责将数据从内核空间的 Socket 缓冲区搬运到的网络中,进入用户态;

从以上4个步骤我们可以发现,正是因为用户态没法控制磁盘和网络资源,所以需要来回的在内核态切换。这样一个发送文件的过程就产生了4 次上下文切换:

  1. read 系统调用读磁盘上的文件时:用户态切换到内核态;

  2. read 系统调用完毕:内核态切换回用户态;

  3. write 系统调用写到socket时:用户态切换到内核态;

  4. write 系统调用完毕:内核态切换回用户态。

如此笨拙的设计,我们觉得计算机是不是太幼稚了,为啥要来回切换不能直接在用户态做数据传输吗?

  1. CPU 全程负责内存内的数据拷贝,参考磁盘介质的读写性能,这个操作是可以接受的,但是如果要让内存的数据和磁盘来回拷贝,这个时间消耗就非常的难看,因为磁盘、网卡的速度远小于内存,内存又远远小于 CPU;

  2. 4 次 copy + 4 次上下文切换,代价太高。

所以计算机体系结构的大佬们就想到了能不能单独地做一个模块来专职负责这个数据的传输,不因为占用cpu而降低系统的吞吐呢?方案就是引入了DMA(Direct memory access)

image.png

什么是 DMA ?

没有 DMA ,计算机程序访问磁盘上的数据I/O 的过程是这样的:

CPU 先发出读指令给磁盘控制器(发出一个系统调用),然后返回;

磁盘控制器接受到指令,开始准备数据,把数据拷贝到磁盘控制器的内部缓冲区中,然后产生一个中断;

  1. CPU 收到中断信号后,让出CPU资源,把磁盘控制器的缓冲区的数据一次一个字节地拷贝进自己的寄存器,然后再把寄存器里的数据拷贝到内存,而在数据传输的期间 CPU 是无法执行其他任务的。

image.png


可以看到,整个数据的传输有几个问题:一是数据在不同的介质之间被拷贝了多次;二是每个过程都要需要 CPU 亲自参与(搬运数据的过程),在这个过程,在数据拷贝没有完成前,CPU 是不能做额外事情的,被IO独占。

如果I/O操作能比较快的完成,比如简单的字符数据,那没问题。如果我们用万兆网卡或者硬盘传输大量数据,CPU就会一直被占用,其他服务无法使用,对单核系统是致命的。

为了解决上面的CPU被持续占用的问题,大佬们就提出了 DMA 技术,即直接内存访问(Direct Memory Access) 技术。

那到底什么是 DMA 技术?

所谓的 DMA(Direct Memory Access,即直接存储器访问)其实是一个硬件技术,其主要目的是减少大数据量传输时的 CPU 消耗,从而提高 CPU 利用效率。其本质上是一个主板和 IO 设备上的 DMAC 芯片。CPU 通过调度 DMAC 可以不参与磁盘缓冲区到内核缓冲区的数据传输消耗,从而提高效率。

那有了DMA,数据读取过程是怎么样的呢?下面我们来具体看看。

image.png


详细过程:

  1. 用户进程a调用系统调用read 方法,向OS内核(资源总管)发出 I/O 请求,请求读取数据到自己的内存缓冲区中,进程进入阻塞状态;

  2. OS内核收到请求后,进一步将 I/O 请求发送 DMA,然后让 CPU 执行其他任务;

  3. DMA 再将 I/O 请求发送给磁盘控制器;

  4. 磁盘控制器收到 DMA 的 I/O 请求,把数据从磁盘拷贝到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被写满后,它向 DMA 发起中断信号,告知自己缓冲区已满;

  5. DMA 收到磁盘的中断信号后,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占用 CPU,CPU 可以执行其他任务;

  6. 当 DMA 读取了一个固定buffer的数据,就会发送中断信号给 CPU;

  7. CPU 收到 DMA 的信号,知道数据已经Ready,于是将数据从内核拷贝到用户空间,结束系统调用;

DMA技术就是释放了CPU的占用时间,它只做事件通知,数据拷贝完全由DMA完成。虽然DMA优化了CPU的利用率,但是并没有提高数据读取的性能。为了减少数据在2种状态之间的切换次数,因为状态切换是一个非常、非常、非常繁重的工作。为此,大佬们就提了零拷贝技术。

零拷贝技术实现的方式

常见的有2种,而今引入持久化内存后,还有APP直接访问内存数据的方式,这里先不展开。下面介绍常用的2种方案,它们的目的减少“上下文切换”和“数据拷贝”的次数。

  • mmap + write(系统调用)

  • sendfile

mmap + write

主要目的,减少数据的拷贝

read() 系统调用:把内核缓冲区的数据拷贝到用户的缓冲区里,用 mmap() 替换 read() ,mmap() 直接把内核缓冲区里的数据映射到用户空间,减少这一次拷贝。

image.png

image.png



具体过程如下:

  1. 应用进程调用了 mmap() 后,DMA 会把磁盘的数据拷贝到内核的缓冲区里。因为建立了这个内存的mapping,所以用户态的数据可以直接访问了;

  2. 应用进程再调用 write(),CPU将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态

  3. DMA把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里

由上可知,系统调用mmap() 来代替 read(), 可以减少一次数据拷贝。那我们是否还有优化的空间呢?毕竟用户态和内核态仍然需要 4 次上下文切换,系统调用还是 2 次。那继续研究下是否还能继续减少切换和数据拷贝呢?答案是确定的:可以

sendfile

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

image.png

参数说明:

  • 前2个参数分别是目的端和源端的文件描述符,

  • 后2个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。

首先,使用sendfile()可以替代前面的 read() 和 write() 这两个系统调用,减少一次系统调用和 2 次上下文切换。

其次,sendfile可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,优化后只有 2 次上下文切换,和 3 次数据拷贝。如下图:

image.png


尽管如此,我们还是又数据拷贝,这不符合我们的标题目标。如果网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术,我们就可以进一步减少通过 CPU 把内核缓冲区里的数据拷贝到 socket 缓冲区的过程。

我们可以在 Linux 系统下通过下面的命令,查看网卡是否支持 scatter-gather 特性:

image.png

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

  1. 通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;

  2. 缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里;

在这个过程之中,实际上只进行了 2 次数据拷贝,如下图:

image.png


这就是零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。

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

所以,零拷贝技术可以把文件传输的性能提高至少一倍。

image.png


image.png

image.png

image.png


大文件传输怎么做?

我们先来回顾下前文的读流程,当调用 read 方法读取文件时,如果数据没有准备好,进程会阻塞在 read 方法调用,要等待磁盘数据的返回,如下图:

image.png


image.png

image.png


image.png

Baidu
map