深入剖析神秘的“零拷贝”
?
https://my.oschina.net/u/4072299/blog/3045755
前言
"零拷貝"這三個(gè)字,想必大家多多少少都有聽過吧,這個(gè)技術(shù)在各種開源組件中都使用了,比如kafka,rocketmq,netty,nginx等等開源框架都在其中引用了這項(xiàng)技術(shù)。所以今天想和大家分享一下有關(guān)于零拷貝的一些知識(shí)。
計(jì)算機(jī)中數(shù)據(jù)傳輸
在介紹零拷貝之前我想說下在計(jì)算機(jī)系統(tǒng)中數(shù)據(jù)傳輸?shù)姆绞健?shù)據(jù)傳輸系統(tǒng)的發(fā)展,為了寫這一部分又祭出了我塵封多年的計(jì)算機(jī)組成原理:
早期階段:
分散連接,串行工作,程序查詢。 在這個(gè)階段,CPU就像個(gè)保姆一樣,需要手把手的把數(shù)據(jù)從I/O接口從讀出然后再送給主存。??這個(gè)階段具體流程是:
這種效率很低數(shù)據(jù)傳輸過程一直占據(jù)著CPU,CPU不能做其他更有意義的事。
接口模塊和DMA階段
這一部分介紹的也是我們后面具體
接口模塊
在馮諾依曼結(jié)構(gòu)中,每個(gè)部件之間均有單獨(dú)連線,不僅先多,而且導(dǎo)致擴(kuò)展I/O設(shè)備很不容易,我們上面的早期階段就是這個(gè)體系,叫作分散連接。擴(kuò)展一個(gè)I/O設(shè)備得連接很多線。所以引入了總線連接方式,將多個(gè)設(shè)備連接在同一組總線上,構(gòu)成設(shè)備之間的公共傳輸通道。??這個(gè)也是現(xiàn)在我們家用電腦或者一些小型計(jì)算器的數(shù)據(jù)交換結(jié)構(gòu)。
在這種模式下數(shù)據(jù)交換采用程序中斷的方式,我們上面知道我們啟動(dòng)I/O設(shè)備之后一直在輪詢問I/O設(shè)備是否準(zhǔn)備好,要是把這個(gè)階段去掉了就好了,程序中斷很好的實(shí)現(xiàn)了我們的夙愿:
DMA
雖然上面的方式雖然提高了CPU的利用率,但是在中斷的時(shí)候CPU一樣是被占用的,為了進(jìn)一步解決CPU占用,又引入了DMA方式,在DMA方式中,主存和I/O設(shè)備之間有一條數(shù)據(jù)通路,這下主存和I/O設(shè)備之間交換數(shù)據(jù)時(shí),就不需要再次中斷CPU。
一般來說我們只需要關(guān)注DMA和中斷兩種即可,下面介紹的都是用來適合大型計(jì)算機(jī)的一些,這里只說簡單的過一下:
具有通道結(jié)構(gòu)的階段
在小型計(jì)算機(jī)中采用DMA方式可以實(shí)現(xiàn)高速I/O設(shè)備與主機(jī)之間組成數(shù)據(jù)的交換,但在大中型計(jì)算機(jī)中,I/O配置繁多,數(shù)據(jù)傳送平凡,若采用DMA方式會(huì)出現(xiàn)一系列問題。
- 每臺(tái)I/O設(shè)備都配置專用額DMA接口,不僅增加了硬件成本,而且解決DMA和CPU訪問沖突問題,會(huì)使控制變得十分復(fù)雜。
- CPU需要對(duì)眾多的DMA接口進(jìn)行管理,同樣會(huì)影響工作效率。
所以引入了通道,通道用來管理I/O設(shè)備以及主存與I/O設(shè)備之間交換信息的部件,可以視為一種具有特殊功能的處理器。它是從屬于CPU的一個(gè)專用處理器,CPU不直接參與管理,故提高了CPU的資源利用率
具有I/O處理機(jī)的階段
輸入輸出系統(tǒng)發(fā)展到第四階段,出現(xiàn)了I/O處理機(jī)。I/O處理機(jī)又稱為外圍處理機(jī),它獨(dú)立于主機(jī)工作,既可以完成I/O通道要完成的I/O控制,又完成格式處理,糾錯(cuò)等操作。具有I/O處理機(jī)的輸出系統(tǒng)與CPU工作的并行度更高,這說明I.O系統(tǒng)對(duì)主機(jī)來說具有更大的獨(dú)立性。
小結(jié)
我們可以看到數(shù)據(jù)傳輸進(jìn)化的目標(biāo)是一直在減少CPU占有,提高CPU的資源利用率。
數(shù)據(jù)拷貝
先介紹一下今天我們的需求,在磁盤中有個(gè)文件,現(xiàn)在需要通過網(wǎng)絡(luò)傳輸出去。 如果是你應(yīng)該怎么做?通過上面的一些介紹,相信你心中應(yīng)該有些想法了吧。
傳統(tǒng)拷貝
如果我們用Java代碼實(shí)現(xiàn)的話用我們會(huì)有如下的的實(shí)現(xiàn):偽代碼參考如下:
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; }這是我們傳統(tǒng)的拷貝方式具體的數(shù)據(jù)流轉(zhuǎn)圖如下,PS:這里不考慮Java中傳輸數(shù)據(jù)時(shí)需要先將堆中的數(shù)據(jù)拷貝到直接內(nèi)存中。?
可以看見我們總管需要經(jīng)歷四個(gè)階段,2次DMA,2次CPU中斷,總共四次拷貝,有四次上下文切換,并且會(huì)占用兩次CPU。
優(yōu)點(diǎn):開發(fā)成本低,適合一些對(duì)性能要求不高的,比如一些什么管理系統(tǒng)這種我覺得就應(yīng)該夠了
缺點(diǎn):多次上下文切換,占用多次CPU,性能比較低。
sendFile實(shí)現(xiàn)零拷貝
上面是零拷貝呢?在wiki中的定位:通常是指計(jì)算機(jī)在網(wǎng)絡(luò)上發(fā)送文件時(shí),不需要將文件內(nèi)容拷貝到用戶空間(User Space)而直接在內(nèi)核空間(Kernel Space)中傳輸?shù)骄W(wǎng)絡(luò)的方式。
在java NIO中FileChannal.transferTo()實(shí)現(xiàn)了操作系統(tǒng)的sendFile,我們可以同下面?zhèn)未a完成上面需求:
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,從而完成了我們的零拷貝。
上面具體過程如下:
可以看見我們根本沒有把數(shù)據(jù)復(fù)制到我們的應(yīng)用緩存中,所以這種方式就是零拷貝。但是這種方式依然很蛋疼,雖然減少到了只有三次數(shù)據(jù)拷貝,但是還是需要CPU中斷復(fù)制數(shù)據(jù)。為啥呢?因?yàn)镈MA需要知道內(nèi)存地址我才能發(fā)送數(shù)據(jù)啊。所以在Linux2.4內(nèi)核中做了改進(jìn),將Kernel buffer中對(duì)應(yīng)的數(shù)據(jù)描述信息(內(nèi)存地址,偏移量)記錄到相應(yīng)的socket緩沖區(qū)當(dāng)中。 最終形成了下面的過程: 現(xiàn),但是我們只能將數(shù)據(jù)原封不動(dòng)的發(fā)給用戶,并不能自己使用。于是Linux提供的一種訪問磁盤文件的特殊方式,可以將內(nèi)存中某塊地址空間和我們要指定的磁盤文件相關(guān)聯(lián),從而把我們對(duì)這塊內(nèi)存的訪問轉(zhuǎn)換為對(duì)磁盤文件的訪問,這種技術(shù)稱為內(nèi)存映射(Memory Mapping)。 我們通過這種技術(shù)將文件直接映射到用戶態(tài)的內(nèi)存地址,這樣對(duì)文件的操作不再是write/read,而是直接對(duì)內(nèi)存地址的操作。
在Java中依靠MappedByteBuffer進(jìn)行mmap映射,具體的MappedByteBuffer可以詳情參照這篇文章:https://www.jianshu.com/p/f90866dcbffc?。
最后
自此,零拷貝的神秘面紗也被揭蓋,零拷貝只是為了減少CPU的占用,讓CPU做更多真正業(yè)務(wù)上的事。通過這篇文章,大家可以自己下來看看Netty是怎么做零拷貝的相信將會(huì)有更加深刻的印象。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/10818641.html
總結(jié)
以上是生活随笔為你收集整理的深入剖析神秘的“零拷贝”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux.Netstat
- 下一篇: java如何消除太多的if else判断