日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

深入剖析神秘的“零拷贝”

發(fā)布時(shí)間:2025/4/5 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入剖析神秘的“零拷贝” 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?

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è)階段具體流程是:

  • CPU主動(dòng)啟動(dòng)I/O設(shè)備
  • 然后CPU一直問I/O設(shè)備老鐵你準(zhǔn)備好了嗎,注意這里是一直詢問。
  • 如果I/O設(shè)備告訴了CPU說:我準(zhǔn)備好了。CPU就從I/O接口中讀數(shù)據(jù)。
  • 然后CPU又繼續(xù)把這個(gè)數(shù)據(jù)傳給主存,就像快遞員一樣。
  • 這種效率很低數(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)了我們的夙愿:

  • CPU主動(dòng)啟動(dòng)I/O設(shè)備。
  • CPU啟動(dòng)之后不需要再問I/O,開始做其他事,類似異步化。
  • I/O準(zhǔn)備好了之后,通過總線中斷告訴CPU我已經(jīng)準(zhǔn)備好了。
  • CPU進(jìn)行讀取數(shù)據(jù),傳輸給主存中。
  • 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。

  • CPU發(fā)指令給I/O設(shè)備的DMA,由DMA將我們磁盤中的數(shù)據(jù)傳輸?shù)絻?nèi)核空間的內(nèi)核buffer。
  • 第二階段觸發(fā)我們的CPU中斷,CPU開始將將數(shù)據(jù)從kernel buffer拷貝至我們的應(yīng)用緩存
  • CPU將數(shù)據(jù)從應(yīng)用緩存拷貝到內(nèi)核中的socket buffer.
  • DMA將數(shù)據(jù)從socket buffer中的數(shù)據(jù)拷貝到網(wǎng)卡緩存。
  • 優(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,從而完成了我們的零拷貝。

    上面具體過程如下:

  • 調(diào)用sendfie(),CPU下發(fā)指令叫DMA將磁盤數(shù)據(jù)拷貝到內(nèi)核buffer中。
  • DMA拷貝完成發(fā)出中斷請(qǐng)求,進(jìn)行CPU拷貝,拷貝到socket buffer中。sendFile調(diào)用完成返回。 3.DMA將socket buffer拷貝至網(wǎng)卡buffer。
  • 可以看見我們根本沒有把數(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)中。 最終形成了下面的過程: ![](https://user-gold-cdn.xitu.io/2018/8/2/164f669eaa58b005?w=1094&h=862&f=png&s=88495

    這種方式讓CPU全程不參與拷貝,因此效率是最好的。

    在第三方開源框架中Netty中都有類似的代碼,大家如果感興趣可以下來自行搜索。

    mmap映射

    上面我們提到了零拷貝的實(shí)現(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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。