深入理解NIO零拷貝及用戶空間與內核空間切換

1、什么是零拷貝

維基上是這么描述零拷貝的:零拷貝描述的是CPU不執行拷貝數據從一個存儲區域到另一個存儲區域的任務,這通常用于通過網絡傳輸一個文件時以減少CPU周期和內存帶寬。

2、零拷貝給我們帶來的好處

  • 1、減少甚至完全避免不要的CPU拷貝,讓CPU解脫出來去執行其他的任務
  • 2、減少內存帶寬的占用
  • 3、通常零拷貝技術還能夠減少用戶空間和操作系統內核空間的上下文切換

3、 零拷貝的實現

  • 實際的實現并沒有真正的標準,取決于操作系統如何實現這一點,零拷貝本質完全依賴操作系統,操作系統支持就有,不支持就沒有,不依賴Java本身(HeapByteBuffer,DirectByteBuffer),NIO只是解決部分拷貝過程、
  • 零拷貝實現了內存的數據重用性 減少拷貝過程次數來提高性能

4、傳統I/O

  • 在Java中,我們可以通過InputStream從源數據中讀取數據流到一個緩沖區里,然后再將它們輸入到OutputStream里。我們知道,這種IO方式傳輸效率是比較低的。那么,當使用上面的代碼時操作系統會發生什么情況:


    傳統I/O

1、JVM發出read() 系統調用。
2、OS上下文切換到內核模式(第一次上下文切換)并將數據讀取到內核空間緩沖區。(第一次拷貝:hardware ----> kernel buffer)
3、OS內核然后將數據復制到用戶空間緩沖區(第二次拷貝: kernel buffer --> user buffer),然后read系統調用返回。而系統調用的返回又會導致一次內核空間到用戶空間的上下文切換(第二次上下文切換)。
4、JVM處理代碼邏輯并發送write()系統調用。
5、OS上下文切換到內核模式(第三次上下文切換)并從用戶空間緩沖區復制數據到內核空間緩沖區(第三次拷貝: user buffer ——> kernel buffer)。
6、write系統調用返回,導致內核空間到用戶空間的再次上下文切換(第四次上下文切換)。將內核空間緩沖區中的數據寫到hardware(第四次拷貝: kernel buffer ——> hardware)。

  • 總的來說,傳統的I/O操作進行了4次用戶空間與內核空間的上下文切換,以及4次數據拷貝。顯然在這個用例中,從內核空間到用戶空間內存的復制是完全不必要的,因為除了將數據轉儲到不同的buffer之外,我們沒有做任何其他的事情。所以,我們能不能直接從hardware讀取數據到kernel buffer后,再從kernel buffer寫到目標地點不就好了。為了解決這種不必要的數據復制,操作系統出現了零拷貝的概念。注意,不同的操作系統對零拷貝的實現各不相同。在這里我們介紹linux下的零拷貝實現。

5、通過sendfile實現的零拷貝I/O

  • sendfile

1、發出sendfile系統調用,導致用戶空間到內核空間的上下文切換(第一次上下文切換)。通過DMA將磁盤文件中的內容拷貝到內核空間緩沖區中(第一次拷貝: hard driver ——> kernel buffer)。
2、然后再將數據從內核空間緩沖區拷貝到內核中與socket相關的緩沖區中(第二次拷貝: kernel buffer ——> socket buffer)。
3、sendfile系統調用返回,導致內核空間到用戶空間的上下文切換(第二次上下文切換)。通過DMA引擎將內核空間socket緩沖區中的數據傳遞到協議引擎(第三次拷貝: socket buffer ——> protocol engine)。

  • 通過sendfile實現的零拷貝I/O只使用了2次用戶空間與內核空間的上下文切換,以及3次數據的拷貝。你可能會說操作系統仍然需要在內核內存空間中復制數據(kernel buffer —>socket buffer)。 是的,但從操作系統的角度來看,這已經是零拷貝,因為沒有數據從內核空間復制到用戶空間。 內核需要復制的原因是因為通用硬件DMA訪問需要連續的內存空間(因此需要緩沖區)。 但是,如果硬件支持scatter-and-gather,這是可以避免的。

6、帶有DMA收集拷貝功能的sendfile實現的I/O

DMA
  • 1、發出sendfile系統調用,導致用戶空間到內核空間的上下文切換(第一次上下文切換)。通過DMA引擎將磁盤文件中的內容拷貝到內核空間緩沖區中(第一次拷貝: hard drive —> kernel buffer)。

  • 2、沒有數據拷貝到socket緩沖區。取而代之的是只有相應的描述符信息會被拷貝到相應的socket緩沖區當中。該描述符包含了兩方面的信息:a)kernel buffer的內存地址;b)kernel buffer的偏移量。

  • 3、sendfile系統調用返回,導致內核空間到用戶空間的上下文切換(第二次上下文切換)。DMA gather copy根據socket緩沖區中描述符提供的位置和偏移量信息直接將內核空間緩沖區中的數據拷貝到協議引擎上(第二次拷貝: kernel buffer ——> protocol engine),這樣就避免了最后一次CPU數據拷貝。

  • 4、帶有DMA收集拷貝功能的sendfile實現的I/O只使用了2次用戶空間與內核空間的上下文切換,以及2次數據的拷貝,而且這2次的數據拷貝都是非CPU拷貝。這樣一來我們就實現了最理想的零拷貝I/O傳輸了,不需要任何一次的CPU拷貝,以及最少的上下文切換。

許多Web服務器都支持零拷貝,如Tomcat和Apache。 例如Apache的相關文檔可以在這里找到,但默認情況下關閉。
注意:Java的NIO通過transferTo()提供了這個功能。

  • 傳統I/O用戶空間緩沖區中存有數據,因此應用程序能夠對此數據進行修改等操作;而sendfile零拷貝消除了所有內核空間緩沖區與用戶空間緩沖區之間的數據拷貝過程,因此sendfile零拷貝I/O的實現是完成在內核空間中完成的,這對于應用程序來說就無法對數據進行操作了。為了解決這個問題,Linux提供了mmap零拷貝來實現我們的需求。

7 通過mmap實現的零拷貝I/O

  • mmap(內存映射)是一個比sendfile昂貴但優于傳統I/O的方法。


    mmap
  • 1、發出mmap系統調用,導致用戶空間到內核空間的上下文切換(第一次上下文切換)。通過DMA引擎將磁盤文件中的內容拷貝到內核空間緩沖區中(第一次拷貝: hard drive ——> kernel buffer)。

  • 2、mmap系統調用返回,導致內核空間到用戶空間的上下文切換(第二次上下文切換)。接著用戶空間和內核空間共享這個緩沖區,而不需要將數據從內核空間拷貝到用戶空間。因為用戶空間和內核空間共享了這個緩沖區數據,所以用戶空間就可以像在操作自己緩沖區中數據一般操作這個由內核空間共享的緩沖區數據。

  • 3、發出write系統調用,導致用戶空間到內核空間的上下文切換(第三次上下文切換)。將數據從內核空間緩沖區拷貝到內核空間socket相關聯的緩沖區(第二次拷貝: kernel buffer ——> socket buffer)。

  • 4、write系統調用返回,導致內核空間到用戶空間的上下文切換(第四次上下文切換)。通過DMA引擎將內核空間socket緩沖區中的數據傳遞到協議引擎(第三次拷貝: socket buffer ——> protocol engine)

通過mmap實現的零拷貝I/O進行了4次用戶空間與內核空間的上下文切換,以及3次數據拷貝。其中3次數據拷貝中包括了2次DMA拷貝和1次CPU拷貝。明顯,它與傳統I/O相比僅僅少了1次內核空間緩沖區和用戶空間緩沖區之間的CPU拷貝。這樣的好處是,我們可以將整個文件或者整個文件的一部分映射到內存當中,用戶直接對內存中對文件進行操作,然后是由操作系統來進行相關的頁面請求并將內存的修改寫入到文件當中。我們的應用程序只需要處理內存的數據,這樣可以實現非常迅速的I/O操作。

8、NIO DirectByteBuffer

  • Java NIO引入了用于通道的緩沖區的ByteBuffer。 ByteBuffer有三個主要的實現:HeapByteBuffer、DirectByteBuffer、MappedByteBuffer
類名 類描述說明
HeapByteBuffer 堆緩沖區
DirectByteBuffer 直接緩沖區
MappedByteBuffer 映射緩沖區

HeapByteBuffer

  • 在調用ByteBuffer.allocate()時使用。 它被稱為堆,因為它保存在JVM的堆空間中,因此你可以獲得所有優勢,如GC支持和緩存優化。 但是,它不是頁面對齊的,這意味著如果你需要通過JNI與本地代碼交談,JVM將不得不復制到對齊的緩沖區空間。


    HeapByteBuffer

DirectByteBuffer

  • 在調用ByteBuffer.allocateDirect()時使用。 JVM將使用malloc()在堆空間之外分配內存空間。 因為它不是由JVM管理的,所以你的內存空間是頁面對齊的,不受GC影響,這使得它成為處理本地代碼的完美選擇。 然而,你要C程序員一樣,自己管理這個內存,必須自己分配和釋放內存來防止內存泄漏。


    DirectByteBuffer

MappedByteBuffer

  • 在調用FileChannel.map()時使用。 與DirectByteBuffer類似,這也是JVM堆外部的情況。 它基本上作為OS mmap()系統調用的包裝函數,以便代碼直接操作映射的物理內存數據


    MappedByteBuffer

9 總結

  • 零拷貝是操作系統底層的一種實現,我們在網絡編程中,利用操作系統這一特性,可以大大提高數據傳輸的效率。這也是目前網絡編程框架中都會采用的方式,理解好零拷貝,有助于我們進一步學習Netty等網絡通信框架的底層原理。
  • 以上文章來自 Java NIO學習筆記四(零拷貝詳解)進行個人整理
    推薦看堆外內存、零拷貝、DirectByteBuffer
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容