走进科学之揭开神秘的"零拷贝"

时间:2022-09-09 08:19:29

前言

"零拷贝"这三个字,想必大家多多少少都有听过吧,这个技术在各种开源组件中都使用了,比如kafka,rocketmq,netty,nginx等等开源框架都在其中引用了这项技术。所以今天想和大家分享一下有关于零拷贝的一些知识。

计算机中数据传输

在介绍零拷贝之前我想说下在计算机系统中数据传输的方式。数据传输系统的发展,为了写这一部分又祭出了我尘封多年的计算机组成原理:

走进科学之揭开神秘的"零拷贝"

早期阶段:

分散连接,串行工作,程序查询。 在这个阶段,CPU就像个保姆一样,需要手把手的把数据从I/O接口从读出然后再送给主存。 走进科学之揭开神秘的"零拷贝" 这个阶段具体流程是:

  1. CPU主动启动I/O设备

  2. 然后CPU一直问I/O设备老铁你准备好了吗,注意这里是一直询问。

  3. 如果I/O设备告诉了CPU说:我准备好了。CPU就从I/O接口中读数据。

  4. 然后CPU又继续把这个数据传给主存,就像快递员一样。

这种效率很低数据传输过程一直占据着CPU,CPU不能做其他更有意义的事。

接口模块和DMA阶段

这一部分介绍的也是我们后面具体

接口模块

在冯诺依曼结构中,每个部件之间均有单独连线,不仅先多,而且导致扩展I/O设备很不容易,我们上面的早期阶段就是这个体系,叫作分散连接。扩展一个I/O设备得连接很多线。所以引入了总线连接方式,将多个设备连接在同一组总线上,构成设备之间的公共传输通道。 走进科学之揭开神秘的"零拷贝" 这个也是现在我们家用电脑或者一些小型计算器的数据交换结构。

在这种模式下数据交换采用程序中断的方式,我们上面知道我们启动I/O设备之后一直在轮询问I/O设备是否准备好,要是把这个阶段去掉了就好了,程序中断很好的实现了我们的夙愿:

  1. CPU主动启动I/O设备。

  2. CPU启动之后不需要再问I/O,开始做其他事,类似异步化。

  3. I/O准备好了之后,通过总线中断告诉CPU我已经准备好了。

  4. CPU进行读取数据,传输给主存中。

DMA

虽然上面的方式虽然提高了CPU的利用率,但是在中断的时候CPU一样是被占用的,为了进一步解决CPU占用,又引入了DMA方式,在DMA方式中,主存和I/O设备之间有一条数据通路,这下主存和I/O设备之间交换数据时,就不需要再次中断CPU。

走进科学之揭开神秘的"零拷贝"

一般来说我们只需要关注DMA和中断两种即可,下面介绍的都是用来适合大型计算机的一些,这里只说简单的过一下:

具有通道结构的阶段

在小型计算机中采用DMA方式可以实现高速I/O设备与主机之间组成数据的交换,但在大中型计算机中,I/O配置繁多,数据传送平凡,若采用DMA方式会出现一系列问题。

  • 每台I/O设备都配置专用额DMA接口,不仅增加了硬件成本,而且解决DMA和CPU访问冲突问题,会使控制变得十分复杂。

  • CPU需要对众多的DMA接口进行管理,同样会影响工作效率。

所以引入了通道,通道用来管理I/O设备以及主存与I/O设备之间交换信息的部件,可以视为一种具有特殊功能的处理器。它是从属于CPU的一个专用处理器,CPU不直接参与管理,故提高了CPU的资源利用率

具有I/O处理机的阶段

输入输出系统发展到第四阶段,出现了I/O处理机。I/O处理机又称为外围处理机,它独立于主机工作,既可以完成I/O通道要完成的I/O控制,又完成格式处理,纠错等操作。具有I/O处理机的输出系统与CPU工作的并行度更高,这说明I.O系统对主机来说具有更大的独立性。

小结

我们可以看到数据传输进化的目标是一直在减少CPU占有,提高CPU的资源利用率。

数据拷贝

先介绍一下今天我们的需求,在磁盘中有个文件,现在需要通过网络传输出去。 如果是你应该怎么做?通过上面的一些介绍,相信你心中应该有些想法了吧。

传统拷贝

如果我们用Java代码实现的话用我们会有如下的的实现:伪代码参考如下:

  1. public static void main(String[] args) {

  2.        Socket  socket = null;

  3.        File file = new File("test.file");

  4.        byte[] b = new byte[(int) file.length()];

  5.        try {

  6.            InputStream in = new FileInputStream(file);

  7.            readFully(in, b);

  8.            socket.getOutputStream().write(b);

  9.        } catch (Exception e) {

  10.        }

  11.    }

  12.    private static boolean readFully(InputStream in, byte[] b) {

  13.        int size = b.length;

  14.        int offset = 0;

  15.        int len;

  16.        for (; size > 0;) {

  17.            try {

  18.                len = in.read(b, offset, size);

  19.                if (len == -1) {

  20.                    return false;

  21.                }

  22.                offset += len;

  23.                size -= len;

  24.            } catch (Exception ex) {

  25.                return false;

  26.            }

  27.        }

  28.        return true;

  29.    }

这是我们传统的拷贝方式具体的数据流转图如下,PS:这里不考虑Java中传输数据时需要先将堆中的数据拷贝到直接内存中。 走进科学之揭开神秘的"零拷贝"

可以看见我们总管需要经历四个阶段,2次DMA,2次CPU中断,总共四次拷贝,有四次上下文切换,并且会占用两次CPU。

  1. CPU发指令给I/O设备的DMA,由DMA将我们磁盘中的数据传输到内核空间的内核buffer。

  2. 第二阶段触发我们的CPU中断,CPU开始将将数据从kernel buffer拷贝至我们的应用缓存

  3. CPU将数据从应用缓存拷贝到内核中的socket buffer.

  4. DMA将数据从socket buffer中的数据拷贝到网卡缓存。

优点:开发成本低,适合一些对性能要求不高的,比如一些什么管理系统这种我觉得就应该够了

缺点:多次上下文切换,占用多次CPU,性能比较低。

sendFile实现零拷贝

上面是零拷贝呢?在wiki中的定位:通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。

在java NIO中FileChannal.transferTo()实现了操作系统的sendFile,我们可以同下面伪代码完成上面需求:

  1. public static void main(String[] args) {

  2.        SocketChannel socketChannel = SocketChannel.open();

  3.        FileChannel fileChannel = new FileInputStream("test").getChannel();

  4.        fileChannel.transferTo(0,fileChannel.size(),socketChannel);

  5.    }

我们通过java.nio中的channel替代了我们上面的socket和fileInputStream,从而完成了我们的零拷贝。

走进科学之揭开神秘的"零拷贝"

上面具体过程如下:

  1. 调用sendfie(),CPU下发指令叫DMA将磁盘数据拷贝到内核buffer中。

  2. DMA拷贝完成发出中断请求,进行CPU拷贝,拷贝到socket buffer中。sendFile调用完成返回。 3.DMA将socket buffer拷贝至网卡buffer。

可以看见我们根本没有把数据复制到我们的应用缓存中,所以这种方式就是零拷贝。但是这种方式依然很蛋疼,虽然减少到了只有三次数据拷贝,但是还是需要CPU中断复制数据。为啥呢?因为DMA需要知道内存地址我才能发送数据啊。所以在Linux2.4内核中做了改进,将Kernel buffer中对应的数据描述信息(内存地址,偏移量)记录到相应的socket缓冲区当中。 最终形成了下面的过程: 走进科学之揭开神秘的"零拷贝"

这种方式让CPU全程不参与拷贝,因此效率是最好的。

在第三方开源框架中Netty,RocketMQ,kafka中都有类似的代码,大家如果感兴趣可以下来自行搜索。

mmap映射

上面我们提到了零拷贝的实现,但是我们只能将数据原封不动的发给用户,并不能自己使用。于是Linux提供的一种访问磁盘文件的特殊方式,可以将内存中某块地址空间和我们要指定的磁盘文件相关联,从而把我们对这块内存的访问转换为对磁盘文件的访问,这种技术称为内存映射(Memory Mapping)。 我们通过这种技术将文件直接映射到用户态的内存地址,这样对文件的操作不再是write/read,而是直接对内存地址的操作。

在Java中依靠MappedByteBuffer进行mmap映射,具体的MappedByteBuffer可以详情参照这篇文章:https://www.jianshu.com/p/f90866dcbffc 。

最后

自此,零拷贝的神秘面纱也被揭盖,零拷贝只是为了减少CPU的占用,让CPU做更多真正业务上的事。通过这篇文章,大家可以自己下来看看Netty是怎么做零拷贝的相信将会有更加深刻的印象。

由于作者本人水平不够,如果有什么错误,还请指正。如果上面问题有什么疑问的话可以关注公众号,来和我一起讨论吧,关注即可免费领取海量最新java学习资料视频,以及最新面试资料。

走进科学之揭开神秘的"零拷贝"的更多相关文章

  1. 走进科学之揭开神秘的"零拷贝"!

        "零拷贝"这三个字,想必大家多多少少都有听过吧,这个技术在各种开源组件中都使用了,比如kafka,rocketmq,netty,nginx等等开源框架都在其中引用了这项技术 ...

  2. Linux 中的零拷贝技术,第 2 部分

    技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概 ...

  3. Linux 中的零拷贝技术,第 1 部分

    概述 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关 ...

  4. linux网络编程九:splice函数,高效的零拷贝

    from:http://blog.csdn.net/jasonliuvip/article/details/22600569 linux网络编程九:splice函数,高效的零拷贝 最近在看<Li ...

  5. 零拷贝概念 -- linux内核

    零拷贝(zero-copy) 备快速网络接口的主要技术. 零拷贝技术通过降低或消除关键通信路径影响速率的操作,降低传输数据的操作系统开销和协议处理开销,从而有效提高通信性能,实现快速传输数据. 零拷贝 ...

  6. Linux 零拷贝技术

    简介 零拷贝(zero-copy)技术可以减少数据拷贝和共享总线操作的次数,消除通信数据在存储器之间不必要的中间拷贝过程,有效地提高通信效率,是设计高速接口通道.实现高速服务器和路由器的关键技术之一. ...

  7. Linux &quot&semi;零拷贝&quot&semi; sendfile函数中文说明及实际操作

    Sendfile函数说明 #include ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); sendfile ...

  8. 对于 Netty ByteBuf 的零拷贝&lpar;Zero Copy&rpar; 的理解

    此文章已同步发布在我的 segmentfault 专栏. 根据 Wiki 对 Zero-copy 的定义: "Zero-copy" describes computer opera ...

  9. linux独有的sendfile系统调用--&OpenCurlyDoubleQuote;零拷贝,高效”

    参考:http://blog.csdn.net/caianye/article/details/7576198 如今几乎每个人都听说过Linux中所谓的"零拷贝"特性,然而我经常碰 ...

随机推荐

  1. 安裝 14&period;04&period;1 Ubuntu 到 Lenovo thinkpad t460p

    在 Lenovo Thinkpad T460p 安裝 ubuntu, BIOS 需要做一些設定, 沒設定的現象:不斷地停在 usb disk 設定 可以 使用 usb disk install 了!

  2. C&num;中方法的参数的四种类型

    C#中方法的参数有四种类型:       1. 值参数类型  (不加任何修饰符,是默认的类型)       2. 引用型参数  (以ref 修饰符声明)       3. 输出型参数  (以out 修 ...

  3. java并发:线程同步机制之Volatile关键字&amp&semi;原子操作Atomic

    volatile关键字 volatile是一个特殊的修饰符,只有成员变量才能使用它,与Synchronized及ReentrantLock等提供的互斥相比,Synchronized保证了Synchro ...

  4. 寒哥教你学 iOS - 经验漫谈(转)

    转自http://www.cocoachina.com/ios/20150907/13339.html 本篇文章主要讲解 4个问题 load妙用 aop面向切面编程 NSNumber Or Int @ ...

  5. 部署lvs-rrd监控LVS

    1.安装rrdtool .tar.gz cd rrdtool- ./configure -prefix=/usr/local/rrdtool make make instal 安装完毕后将rrdtoo ...

  6. SublimeLinter

    SublimeLinter 关于代码检查 郑重推荐这个插件 官方插件库中仅有sublime3 有此插件 (对于ST2 有个sublimeLinter for ST2 没试过) SublimeLinte ...

  7. fzu 2035 Axial symmetry&lpar;枚举&plus;几何)

    题目链接:fzu 2035 Axial symmetry 题目大意:给出n个点,表示n边形的n个顶点,判断该n边形是否为轴对称图形.(给出点按照图形的顺时针或逆时针给出. 解题思路:将相邻两个点的中点 ...

  8. kruskal重构树学习笔记

    \(kruskal\) 重构树学习笔记 前言 \(8102IONCC\) 中考到了,本蒟蒻不会,所以学一下. 前置知识 \(kruskal​\) 求最小(大)生成树,树上求 \(lca​\). 算法详 ...

  9. python 读取默认配置文件和用户配置文件 configs

    优先从configs_default中读取配置,但是configs_override中的配置可以override它 import configs_default #import configs_ove ...

  10. &lbrack;转&rsqb;自建Syncthing中继服务器&lpar;私密传输或造福大众&rpar;

    自建Syncthing中继服务器(私密传输或造福大众) 一.介绍 我之前介绍了Syncthing,作为一款开源的文件同步程序,它的功能还是非常强大的,我也很高兴能看到它被越来越多的人知道和使用,前几天 ...