淺談零拷貝機制

預備知識

我相信來看本文章的應該都對操做系統有了一些瞭解,不過在談零拷貝以前,還須要講一些別的東西,避免到時候你們看的暈乎linux

  • 內核態和用戶態:這兩種不一樣的狀態分別賦予進程不一樣的權限,內核態下能夠訪問內存的全部數據,可以訪問外圍設備;而用戶態下則只能訪問受限的內存。因此若是一個進程想要執行深度操做,就須要涉及用戶態到內核態的切換
  • 系統調用:系統調用不是一種調用行爲,能夠理解成操做系統開放的編程接口,經過使用「系統調用」接口,能夠達到一些在用戶態下沒法完成的行爲,固然,會涉及到用戶態到內核態的狀態切換
  • 緩衝區:緩衝區是內存中的一塊區域,是IO操做的基礎。任何IO操做均可以理解爲數據在緩衝區之間的拷貝,用戶空間和內核空間都有對應的緩衝區。操做系統不能直接將數據從磁盤拷貝到用戶空間的緩衝區中
  • DMA:DMA即Direct Memory Access,負責將數據從一個地址空間轉移到另外一個地址空間,傳輸動做由CPU初始化,但由DMA來執行。在DMA執行操做期間,CPU能夠處理其餘的事情
  • 虛擬內存:內存地址分爲虛擬內存地址和物理內存地址兩種,進程可見的是虛擬內存地址,操做系統會將虛擬內存地址映射到物理內存上,虛擬內存是連續的,可是物理內存能夠是不連續的

零拷貝解決什麼問題

在講零拷貝以前,先得明白拷貝是什麼。這裏的拷貝指「I/O操做時,數據在緩衝區之間的拷貝」。不明白不要緊,咱們先來看linux中的將數據從磁盤讀取,並經過網絡發送出去的一整個過程是怎樣的:編程

  1. DMA將數據從磁盤拷貝到內核緩衝區中網絡

  2. cpu將數據從內核緩衝區拷貝到用戶緩衝區中(讀過程結束,進程能夠從用戶緩衝區直接讀取數據)socket

  3. cpu將數據從用戶緩衝區拷貝到內核socket緩衝區中學習

  4. DMA將數據從socket緩衝區發送到協議引擎中(寫過程結束,數據被協議引擎經過網絡發送)優化

這裏借用一下理解零拷貝原理中的圖: 操作系統

整個讀寫的過程大體是這樣的,涉及2次用戶態和內核態的狀態切換,2次DMA拷貝,和2次cpu拷貝

咱們一樣也發現,在一個讀寫過程當中,第2步和第3步彷彿在作「無用功」,數據若是直接從內核緩衝拷貝到Socket緩衝就能夠了,爲何還要經過用戶緩衝中轉呢?沒錯,操做系統的開發者也意識到了這個問題,因此零拷貝就是爲了解決這個問題,因此利用零拷貝機制,能夠避免狀態切換,並減小了cpu的拷貝次數.net

零拷貝的實現方式

雖然看起來只須要容許數據從內核緩衝拷貝到socket緩衝,便可解決數據拷貝的問題,可是具體卻有不少種實現方式,咱們來一一介紹一下指針

sendFile

sendFile方式的系統調用爲sendfile system call,也是最經典的零拷貝解決方案。採用sendFile方式的的讀寫過程爲:netty

  1. DMA將數據從磁盤拷貝到內核緩衝
  2. cpu將數據從內核緩衝拷貝到內核socket緩衝
  3. DMA將數據從socket緩衝拷貝到協議引擎中

這種標準方式就不用過多介紹了,就是咱們在剛纔提到的解決方案,接下來咱們看優化版本的sendFile的讀寫過程是怎樣的:

  1. DMA將數據從磁盤拷貝到內核緩衝
  2. cpu將描述符[1]從內核緩衝拷貝到socket緩衝
  3. DMA將數據從socket緩衝拷貝到協議引擎中

在優化的sendFile方式中,第2步再也不是拷貝數據,而是拷貝描述符,這樣就真正實現了數據的零拷貝

mmap

mmap,也能夠成爲內存映射,效果比傳統的I/O要好,可是代價比sendFile要大。咱們來看mmap方式下的讀寫操做:

  1. DMA將數據從磁盤拷貝到內核緩衝/用戶緩衝
  2. cpu將數據從內核緩衝/用戶緩衝拷貝到socket緩衝
  3. DMA將數據從socket緩衝拷貝到協議引擎中

這裏畫了個斜線,表示內核緩衝和用戶緩衝是同一塊區域,mmap採用虛擬內存映射,雖然進程認爲本身的用戶緩衝區域是內存中獨立的區域,可是實際上用戶緩衝和內核緩衝指向的同一塊區域,這種方式也能避免數據拷貝問題

FileChannel

FileChannel是Java NIO的解決方案,提供transferTo/transferFrom接口,用於將一個通道鏈接到另外一個通道,中間就避免了沒必要要的數據拷貝

更嚴格地說,FileChannel並不能算是零拷貝的一種解決方案,實際上仍是須要依賴操做系統的sendFile

Netty Zero-Copy

Netty中在FileRegion封裝了Java NIO的transferTo/transferFrom,也能夠實現零拷貝,可是這不是重點,Netty還提供了另外一種形式的零拷貝,也是咱們學習的重點 在數據傳輸時,一般一份完整的消息數據會被切分紅多個數據包進行傳輸,而這些單個的數據包是沒有意義的,只有組合在一塊兒纔有具體的含義,才能被程序進行處理。Netty能夠經過零拷貝的方式,將這些數據組合成完成的消息來供使用,減小數據拷貝次數,此時零拷貝的做用範圍僅侷限於用戶空間

同時,Netty能夠直接在堆外分配內存,避免了數據從堆內拷貝到堆外的過程

總結

零拷貝的這些方式中,sendFile雖然看起來最美好,可是若是咱們的應用在讀取數據後還須要進行更改的話,這種方式就不適用了,就須要使用代價更高的mmap內存映射方式

零拷貝的內容大體就是這些了,若是想更爲深刻地去學習零拷貝知識的話,能夠去學習一下netty的源碼,仍是頗有必要的


  1. 這裏我不敢肯定是內存描述符仍是數據描述符,我傾向於內存描述符,是一個相似指針的數據,若是我這裏理解錯誤歡迎指正 ↩︎

相關文章
相關標籤/搜索