我相信來看本文章的應該都對操做系統有了一些瞭解,不過在談零拷貝以前,還須要講一些別的東西,避免到時候你們看的暈乎linux
在講零拷貝以前,先得明白拷貝是什麼。這裏的拷貝指「I/O操做時,數據在緩衝區之間的拷貝」。不明白不要緊,咱們先來看linux中的將數據從磁盤讀取,並經過網絡發送出去的一整個過程是怎樣的:編程
DMA將數據從磁盤拷貝到內核緩衝區中網絡
cpu將數據從內核緩衝區拷貝到用戶緩衝區中(讀過程結束,進程能夠從用戶緩衝區直接讀取數據)socket
cpu將數據從用戶緩衝區拷貝到內核socket緩衝區中學習
DMA將數據從socket緩衝區發送到協議引擎中(寫過程結束,數據被協議引擎經過網絡發送)優化
這裏借用一下理解零拷貝原理中的圖: 操作系統
整個讀寫的過程大體是這樣的,涉及2次用戶態和內核態的狀態切換,2次DMA拷貝,和2次cpu拷貝咱們一樣也發現,在一個讀寫過程當中,第2步和第3步彷彿在作「無用功」,數據若是直接從內核緩衝拷貝到Socket緩衝就能夠了,爲何還要經過用戶緩衝中轉呢?沒錯,操做系統的開發者也意識到了這個問題,因此零拷貝就是爲了解決這個問題,因此利用零拷貝機制,能夠避免狀態切換,並減小了cpu的拷貝次數.net
雖然看起來只須要容許數據從內核緩衝拷貝到socket緩衝,便可解決數據拷貝的問題,可是具體卻有不少種實現方式,咱們來一一介紹一下指針
sendFile方式的系統調用爲sendfile system call
,也是最經典的零拷貝解決方案。採用sendFile方式的的讀寫過程爲:netty
這種標準方式就不用過多介紹了,就是咱們在剛纔提到的解決方案,接下來咱們看優化版本的sendFile的讀寫過程是怎樣的:
在優化的sendFile方式中,第2步再也不是拷貝數據,而是拷貝描述符,這樣就真正實現了數據的零拷貝
mmap,也能夠成爲內存映射,效果比傳統的I/O要好,可是代價比sendFile要大。咱們來看mmap方式下的讀寫操做:
這裏畫了個斜線,表示內核緩衝和用戶緩衝是同一塊區域,mmap採用虛擬內存映射,雖然進程認爲本身的用戶緩衝區域是內存中獨立的區域,可是實際上用戶緩衝和內核緩衝指向的同一塊區域,這種方式也能避免數據拷貝問題
FileChannel是Java NIO的解決方案,提供transferTo/transferFrom
接口,用於將一個通道鏈接到另外一個通道,中間就避免了沒必要要的數據拷貝
更嚴格地說,FileChannel並不能算是零拷貝的一種解決方案,實際上仍是須要依賴操做系統的sendFile
Netty中在FileRegion封裝了Java NIO的transferTo/transferFrom
,也能夠實現零拷貝,可是這不是重點,Netty還提供了另外一種形式的零拷貝,也是咱們學習的重點 在數據傳輸時,一般一份完整的消息數據會被切分紅多個數據包進行傳輸,而這些單個的數據包是沒有意義的,只有組合在一塊兒纔有具體的含義,才能被程序進行處理。Netty能夠經過零拷貝的方式,將這些數據組合成完成的消息來供使用,減小數據拷貝次數,此時零拷貝的做用範圍僅侷限於用戶空間
同時,Netty能夠直接在堆外分配內存,避免了數據從堆內拷貝到堆外的過程
零拷貝的這些方式中,sendFile雖然看起來最美好,可是若是咱們的應用在讀取數據後還須要進行更改的話,這種方式就不適用了,就須要使用代價更高的mmap內存映射方式
零拷貝的內容大體就是這些了,若是想更爲深刻地去學習零拷貝知識的話,能夠去學習一下netty的源碼,仍是頗有必要的
這裏我不敢肯定是內存描述符仍是數據描述符,我傾向於內存描述符,是一個相似指針的數據,若是我這裏理解錯誤歡迎指正 ↩︎