关于零拷贝技术你应该知道的事

前言:

前段时间在学习java NIO的途中,偶然接触到了DirectByteBuffer这个类,这个类和其他byteBuffer实现的差异主要在于它使用了零拷贝技术,非常惭愧地讲,作为一个java小菜鸡,这应该是我第一次比较正式地了解零拷贝这个技术(由于零拷贝需要操作系统层面的支持,所以开发中常用的仍然是基于传统方式实现的),随后在查阅相关文档,博客的过程中,自己也逐渐对零拷贝技术有了一个全新的了解。本篇笔记将延续java IO总结那一篇的风格,仅对零拷贝技术做一个宏观上的阐述,而不像设计模式系列笔记那样做代码层面上的编写和展示。

零拷贝技术定义:

零拷贝(zero-copy)是实现主机或路由器等设备高速网络接口的主要技术。零拷贝技术通过减少或消除关键通信路径影响速率的操作,降低数据传输的操作系统开销和协议处理开销,从而有效提高通信性能,实现高速数据传输。-节选自百度百科

?,emmm 上面的说的是什么,,,

简单来说,零拷贝技术就是减少我们操作系统在通信过程中数据的拷贝次数,以达到提高性能或效率的目的。

既然零拷贝是基于传统拷贝的改进,所以接下来,我们先来大致了解一下传统拷贝下的执行流程。

传统拷贝:

时序图如下:

只看时序图这错综复杂的数据传输线路,就知道为什么传统拷贝下一般效率比较低了,因为每一次的数据拷贝都要消耗一定的时间,虽然这些数据拷贝所消耗的时间对于我们人类来讲是几乎感知不到的,倘若我们需要频繁地传输大量文件的话,积少成多,就会造成我们应用程序性能的下降。

要真正理解时序图中的上下文切换呢,我们首先要明白用户空间和内核空间这两个概念。

Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。

内核空间有访问的所有权限(比如访问硬件),用户空间也可以通过系统接口去访问内核空间。用户空间可以通过内核空间(类似于中介者)进行相互访问。

具体的概念说清楚还是比较复杂的,已经超出了本文所要讲述的范畴,对这方面知识不了解的朋友可以通过搜索引擎搜索或者差查阅相关书籍资料加以了解。

简单的说呢,用户空间就是我们应用程序所运行的空间,只有部分权限,并不能直接操作硬件,所以当我们需要从硬盘上读取一个文件呢,系统首先要切换到内核态,然后再去读取硬盘上的数据,读取之后呢再切换成用户态,继续我们程序的操作,当对数据完成操作,再次切换到内核态,把我们的数据操作同步到我们的磁盘上。

而通过上图时序图我们可以知道,完成一次完整的IO流程,我们数据需要拷贝四次,上下文需要切换四次,而当我们拿到文件并不操作的时候,这种拷贝是完全没有意义的,因为数据并没有发生变化,完全是对系统资源的一种浪费,下面我们详细看一下传统拷贝的执行流程。

  1. 执行read系统调用
  2. 操作系统收到指令从用户态上下文切换到内核态(第一次上下文切换)然后通过DMA方式从硬盘上将数据拷贝到内核空间(第一次数据拷贝)
  3. 然后操作系统将数据从内核空间拷贝到用户空间(第二次数据拷贝),然后从内核态切换到用户态(第二次上下文切换)
  4. 执行一些代码,比如对文件加以改动,或者只是单纯的读文件。
  5. 调用write系统调用,操作系统将数据从用户空间拷贝到内核空间(第三次数据拷贝),同时上下文从用户态切换到内核态。(第三次上下文切换)
  6. 操作系统将内核空间的数据写入磁盘(第四次数据拷贝),write函数返回,操作系统上下文从内核态切换到用户态(第四次上下文切换)。

再次强调:我们拿到数据并不操作的时候,这种来回之间的拷贝是完全没有意义的,因为数据并没有发生变化,完全是对系统资源的一种浪费。

那这个时候有的朋友会想了,为啥要反反复复执行内核空间和用户空间之间的拷贝呢,用户空间直接操作内核空间的数据不就行了吗?

Of course,天才的科学家们自然也考虑到了这个问题,于是零拷贝技术就应运而生了。

准零拷贝:

2.1 内核开始,Linux 引入了 sendfile 来简化操作,成功的把拷贝次数减少到了三次,显而易见,只减少了一次是没有办法满足我们心中理想的零拷贝的要求的(即仅仅需要两次拷贝,分别是读的时候和写的时候)。所以在这里我把它称之为准零拷贝,时序图如下:

从时序图中我们可以看到这准零拷贝和传统拷贝相比,主要差别是在:

  1. 没有了之前的read 和write ,由sendifle()函数替代。
  2. 不再有内核空间和用户空间的数据拷贝环节。
  3. 所有对数据的操作都是直接在内核空间完成的
  4. 上下文切换由原来的4次减少到2次,数据拷贝由原来的4次减少为3次。

准零拷贝的执行流程大致如下:

  1. 发起sendfile()系统调用
  2. 操作系统通过DMA方式将数据从磁盘拷贝到内核空间。
  3. 将数据从内核空间写到socket缓冲区里面。
  4. 将修改过的数据从内核空间同步到磁盘中。

可以看到,准零拷贝的方式的确比以往传统拷贝的流程简化了不少,但是如上文所说的,这样仍然不是我们理想中的零拷贝,仍然是有一次数据拷贝环节对于整体来说是有那么一些显得多余的,那么可不可以把socket 缓冲区这个环节去掉,直接操作内核空间缓冲区的内容呢?答案是可以的,当网卡支持scatter-n-gather模式时,我们便可以实现真正的零拷贝技术,即上下文只切换两次,数据拷贝两次。

零拷贝:

时序图如下:

通过时序图可以看到,整个过程中,上下文切换了两次,数据拷贝发生了两次。整个操作过程都是由内核空间完成的,和CPU没什么关系了已经,真正做到了让我们的CPU从繁琐的拷贝任务中解脱出来,从而专注在其他的任务上,减少不必要的系统资源的浪费。

总结:

本篇文章我们从一个宏观的角度大致了解了一下零拷贝技术,因为个人水平的原因,零拷贝技术的诸多细节都没有提及,这点等我之后自己完全有把握将这部分内容讲清楚之后再做打算。同样的,细心的朋友可以发现,本篇文章丝毫没有提到任何一门编程语言,主要原因是零拷贝是基于操作系统层面支持的技术,本身和我们编程语言并没有太大的关系,日后有打算做一个番外篇来讲解一下零拷贝技术的java实现,至于对零拷贝技术感兴趣的同学也可以私下自行查阅资料学习,我是韩数,祝大家七夕快乐!

暂无评论

文艺中二理工男,技术极客程序员

发表评论