"零拷贝"这三个字,想必你们多多少少都有听过吧,这个技术在各类开源组件中都使用了,好比kafka,rocketmq,netty,nginx等等开源框架都在其中引用了这项技术。因此今天想和你们分享一下有关于零拷贝的一些知识。java
在介绍零拷贝以前我想说下在计算机系统中数据传输的方式。数据传输系统的发展,为了写这一部分又祭出了我尘封多年的计算机组成原理:nginx
分散链接,串行工做,程序查询。 在这个阶段,CPU就像个保姆同样,须要手把手的把数据从I/O接口从读出而后再送给主存。 这个阶段具体流程是:面试
这种效率很低数据传输过程一直占据着CPU,CPU不能作其余更有意义的事。缓存
这一部分介绍的也是咱们后面具体网络
在冯诺依曼结构中,每一个部件之间均有单独连线,不只先多,并且致使扩展I/O设备很不容易,咱们上面的早期阶段就是这个体系,叫做分散链接。扩展一个I/O设备得链接不少线。因此引入了总线链接方式,将多个设备链接在同一组总线上,构成设备之间的公共传输通道。 这个也是如今咱们家用电脑或者一些小型计算器的数据交换结构。app
在这种模式下数据交换采用程序中断的方式,咱们上面知道咱们启动I/O设备以后一直在轮询问I/O设备是否准备好,要是把这个阶段去掉了就行了,程序中断很好的实现了咱们的夙愿:框架
虽然上面的方式虽然提升了CPU的利用率,可是在中断的时候CPU同样是被占用的,为了进一步解决CPU占用,又引入了DMA方式,在DMA方式中,主存和I/O设备之间有一条数据通路,这下主存和I/O设备之间交换数据时,就不须要再次中断CPU。异步
通常来讲咱们只须要关注DMA和中断两种便可,下面介绍的都是用来适合大型计算机的一些,这里只说简单的过一下:socket
在小型计算机中采用DMA方式能够实现高速I/O设备与主机之间组成数据的交换,但在大中型计算机中,I/O配置繁多,数据传送平凡,若采用DMA方式会出现一系列问题。性能
因此引入了通道,通道用来管理I/O设备以及主存与I/O设备之间交换信息的部件,能够视为一种具备特殊功能的处理器。它是从属于CPU的一个专用处理器,CPU不直接参与管理,故提升了CPU的资源利用率
输入输出系统发展到第四阶段,出现了I/O处理机。I/O处理机又称为外围处理机,它独立于主机工做,既能够完成I/O通道要完成的I/O控制,又完成格式处理,纠错等操做。具备I/O处理机的输出系统与CPU工做的并行度更高,这说明I.O系统对主机来讲具备更大的独立性。
咱们能够看到数据传输进化的目标是一直在减小CPU占有,提升CPU的资源利用率。
先介绍一下今天咱们的需求,在磁盘中有个文件,如今须要经过网络传输出去。 若是是你应该怎么作?经过上面的一些介绍,相信你心中应该有些想法了吧。
若是咱们用Java代码实现的话用咱们会有以下的的实现:伪代码参考以下:
public static void main(String[] args) { Socket socket = null; File file = new File("test.file"); byte[] b = new byte[(int) file.length()]; try { InputStream in = new FileInputStream(file); readFully(in, b); socket.getOutputStream().write(b); } catch (Exception e) { } } private static boolean readFully(InputStream in, byte[] b) { int size = b.length; int offset = 0; int len; for (; size > 0;) { try { len = in.read(b, offset, size); if (len == -1) { return false; } offset += len; size -= len; } catch (Exception ex) { return false; } } return true; }
这是咱们传统的拷贝方式具体的数据流转图以下,PS:这里不考虑Java中传输数据时须要先将堆中的数据拷贝到直接内存中。
能够看见咱们总管须要经历四个阶段,2次DMA,2次CPU中断,总共四次拷贝,有四次上下文切换,而且会占用两次CPU。
优势:开发成本低,适合一些对性能要求不高的,好比一些什么管理系统这种我以为就应该够了
缺点:屡次上下文切换,占用屡次CPU,性能比较低。
上面是零拷贝呢?在wiki中的定位:一般是指计算机在网络上发送文件时,不须要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。
在java NIO中FileChannal.transferTo()实现了操做系统的sendFile,咱们能够同下面伪代码完成上面需求:
public static void main(String[] args) { SocketChannel socketChannel = SocketChannel.open(); FileChannel fileChannel = new FileInputStream("test").getChannel(); fileChannel.transferTo(0,fileChannel.size(),socketChannel); }
咱们经过java.nio中的channel替代了咱们上面的socket和fileInputStream,从而完成了咱们的零拷贝。
上面具体过程以下:
能够看见咱们根本没有把数据复制到咱们的应用缓存中,因此这种方式就是零拷贝。可是这种方式依然很蛋疼,虽然减小到了只有三次数据拷贝,可是仍是须要CPU中断复制数据。为啥呢?由于DMA须要知道内存地址我才能发送数据啊。因此在Linux2.4内核中作了改进,将Kernel buffer中对应的数据描述信息(内存地址,偏移量)记录到相应的socket缓冲区当中。 最终造成了下面的过程: 。 咱们经过这种技术将文件直接映射到用户态的内存地址,这样对文件的操做再也不是write/read,而是直接对内存地址的操做。
在Java中依靠MappedByteBuffer进行mmap映射,具体的MappedByteBuffer能够详情参照这篇文章:https://www.jianshu.com/p/f90866dcbffc 。
自此,零拷贝的神秘面纱也被揭盖,零拷贝只是为了减小CPU的占用,让CPU作更多真正业务上的事。经过这篇文章,你们能够本身下来看看Netty是怎么作零拷贝的相信将会有更加深入的印象。
因为做者本人水平不够,若是有什么错误,还请指正。若是上面问题有什么疑问的话能够关注公众号,来和我一块儿讨论吧,关注便可免费领取海量最新java学习资料视频,以及最新面试资料。
若是你们以为这篇文章对你有帮助,或者你有什么疑问想提供1v1免费vip服务,均可以关注个人公众号,关注便可免费领取海量最新java学习资料视频,以及最新面试资料,你的关注和转发是对我最大的支持,O(∩_∩)O: