Wiki对零拷贝的定义
"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.
零拷贝(Zero-copy)是指在计算机执行操作时,CPU 不需要将数据从一块内存拷贝到另一块内存,减少拷贝次数可以提高性能。
在操作系统层面来说零拷贝是指不需要将数据从内核空间复制到用户空间,而Netty、Kafka等框架都因零拷贝而闻名著称,技术来不得半点马虎和一知半解,本着知其然知其所以然的态度,本系列为你揭秘其中原委和解决方案。
计算机组成
计算机是由硬件、内核和上层应用组成,通过它们在功能上由下而上的层层传递给用户提供各种服务,如下图所示。为了便于独立开发、调试和维护,采用了高内聚低耦合分层设计思想:内核负责对硬件直接操作,在封装硬件操作的同时给上层应用提供功能接口,承上启下;上层应用调用内核接口为用户提供业务服务。每一层都使用较低层提供的功能而不必知道其实现细节,只需了解其接口能做什么即可。
实际上随着时间的演进和功能的不断丰富,内核体积变得庞大不易管理,所以现代的操作系统基本都采用微内核的设计思想:把内核模块化,将非基础模块从内核中移除,把这些非基础模块实现为系统应用,甚至用户应用,从而减少内核的体积和复杂度,使其功能更聚焦。
微内核设计的好处颇多:
- 方便扩展内核,所有新的应用都可以在用户空间增加而不必修改内核;
- 由于大多数应用都是作为用户进程而非内核进程来运行的,为微内核提供了更好的安全性和可靠性;
- 微内核较小,开发、调试和维护工作量都相对较少,而且容易进行硬件平台移植;
然而微内核究竟包括哪些基础模块,实际上每个操作系统的处理并不相同,并没有定论和标准,但一般至少会包括进程、内存管理和通讯功能。
上层应用包含了系统应用和用户应用:系统应用一般跟随系统发行版而来,但也是通过调用内核接口开发的,一般是用于系统管理;用户应用是用户自行开发的应用,一般是为了实现自己的业务逻辑。但上层应用一般都采用高级语言来编写,为了降低复杂性和高效,高级语言对内核接口进行了封装,提供了编程语言类库,比如C语言的标准库、Java的JDK等。
用户空间和内核空间
用户进程不能直接操作硬件,只有通过内核提供的接口来操作,而为了确保内核的正常、安全地运行,不会因上层应用的异常导致内核陷入灾难,就必须区分内核代码执行和用户定义代码执行,也就是所谓的双重模式即:内核态和用户态。当用户程序正在执行,系统处于用户态,当用户程序需要调用内核功能,它必须通过系统调用的形式转换为内核态执行。一般是通过硬件支持来区分两种模式的,在硬件中增加了一个模式bit位:0代表内核态,1代表用户态。
系统通过引导程序,装入内核处于内核态,紧接着开始执行用户进程进入用户态(模式位为1),一旦遇到中断或系统调用,则又从用户态进入内核态(模式位为0),内核处理完后返回用户进程进入用户态(模式位为1),如上图。
进程是操作系统资源分配的最小单元,进程在执行上有用户态和内核态,那么虚拟内存也分为了用户空间和内核空间,供用户态和内核态的进程使用。比如32位的操作系统内存可以高达4G,把最高的1G字节(虚拟内存地址0xC0000000~0xFFFFFFFF)供内核使用,称为内核空间,而将较低的3G字节(虚拟内存地址 从0x00000000到0xBFFFFFFF)供上层应用使用,称为用户空间。
虚拟内存是相对于物理内存而言,操作系统会对物理内存进行映射和抽象,软件的所有操作都是对虚拟内存而言,也可以理解为物理内存映射为用户空间和内核空间。
当进程由用户态进行进入内核态,系统需要保存当前运行在CPU中进程的上下文,从而能在其处理完毕后转换为用户态时能恢复其上下文,这一任务称为上下文切换(context switch)。上下文切换在计算机执行期间会比较频繁,只要牵涉到内核态和用户态的转换就会涉及到上下文切换。所谓的上下文究竟是指什么呢?上下文从其英文context可知,是进行运行的环境,它包括进程的状态、计数器、全局变量、临时数据如函数参数、返回地址、局部变量等,还可能包括运行期间动态分配的内存堆(heap)。频繁的上下文切换,对数据的保存和恢复操作过于频繁,对性能的影响特别显著。
示例
为了全面理解用户空间、内核空间、用户态、内核态,我们举个例子来说明:读取文件内容并通过Socket发送。
由于用户进程无法直接操作硬件,因此用户进程首先需要通过系统调用(System Call)来调用内核接口,此时的事件流如下
- 用户进程通过系统调用读取文件内容,由用户态进入内核态;
- 内核通过直接内存(DMA)的方式获取文件内容,并返回用户进程,由内核态转换用户态;
- 用户进程对数据进行处理后,通过系统调用Socket发送数据,由用户态进入内核态;
- 内核通过直接内存(DMA)的方式发送数据,并返回用户态;
由上图流程可知会经历4次上下文切换,而它的数据会也经历了4次拷贝,数据流向如下
- 内核通过DMA方式,把文件内容拷贝到内核空间;
- 从内核空间通过CPU拷贝到用户空间,供用户进程使用;
- 用户处理完毕后,把数据从用户空间通过CPU拷贝到内核空间;
- 内核通过DMA方式拷贝到网卡发送;
什么是DMA 所有的硬件是通过控制器连接到计算机总总线,传统的中断式I/O方式是把数据拷贝到控制器寄存器,控制器再以中断的方式通知CPU拷贝到内存。由于这种方式频繁的中断,导致大量占用CPU时间片。DMA是Direct Memory Access的缩写,中文称之为直接存储器访问。它的使用方式是CPU通知DMA控制器进行I/O后CPU就被解脱出来做别的事情了,数据的拷贝过程都是由DMA来操作完成,数据拷贝完成后DMA控制器以中断的方式通知CPU。
写在最后
本篇我们了解了用户态、内核态、用户空间、内核空间的概念,而且从事件流和数据流我们也找到了提高性能的优化方向:降低拷贝和上下文切换次数,下一篇我们将揭晓优化方案。
End
版权归@码农神说所有,转载须经授权,翻版必究