深入剖析神秘的“零拷贝”
?
https://my.oschina.net/u/4072299/blog/3045755
前言
"零拷貝"這三個字,想必大家多多少少都有聽過吧,這個技術在各種開源組件中都使用了,比如kafka,rocketmq,netty,nginx等等開源框架都在其中引用了這項技術。所以今天想和大家分享一下有關于零拷貝的一些知識。
計算機中數據傳輸
在介紹零拷貝之前我想說下在計算機系統中數據傳輸的方式。數據傳輸系統的發展,為了寫這一部分又祭出了我塵封多年的計算機組成原理:
早期階段:
分散連接,串行工作,程序查詢。 在這個階段,CPU就像個保姆一樣,需要手把手的把數據從I/O接口從讀出然后再送給主存。??這個階段具體流程是:
這種效率很低數據傳輸過程一直占據著CPU,CPU不能做其他更有意義的事。
接口模塊和DMA階段
這一部分介紹的也是我們后面具體
接口模塊
在馮諾依曼結構中,每個部件之間均有單獨連線,不僅先多,而且導致擴展I/O設備很不容易,我們上面的早期階段就是這個體系,叫作分散連接。擴展一個I/O設備得連接很多線。所以引入了總線連接方式,將多個設備連接在同一組總線上,構成設備之間的公共傳輸通道。??這個也是現在我們家用電腦或者一些小型計算器的數據交換結構。
在這種模式下數據交換采用程序中斷的方式,我們上面知道我們啟動I/O設備之后一直在輪詢問I/O設備是否準備好,要是把這個階段去掉了就好了,程序中斷很好的實現了我們的夙愿:
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代碼實現的話用我們會有如下的的實現:偽代碼參考如下:
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,性能比較低。
sendFile實現零拷貝
上面是零拷貝呢?在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是怎么做零拷貝的相信將會有更加深刻的印象。
轉載于:https://www.cnblogs.com/davidwang456/articles/10818641.html
總結
以上是生活随笔為你收集整理的深入剖析神秘的“零拷贝”的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux.Netstat
- 下一篇: 携程在线风控系统架构