螞蟻二面,面試官問我零拷貝的實現原理,當場懵了…

螞蟻二面,面試官問我零拷貝的實現原理,當場懵了…

"狼哥,面試又跪了,碰到了知識盲區" "哪個?" "一面還可以,二面面試官問我零拷貝的原理,懵逼了...這塊內容沒去研究過" "哦,這個知識點,我之前應該有講過,你沒注意到?" "這東西工作中用不到,可能被我忽略了" "嘖嘖嘖..." "哎,有空和我講講?"

"先從簡單開始,實現下這個場景:從一個文件中讀出數據並將數據傳到另一臺服務器上?"

"為啥寫這個?"

"你先寫"

"行..."

1分鐘後

"我寫了偽代碼"

<code>File.

read

(file, buf,

len

); Socket.send(socket, buf,

len

);/<code>

"這裡涉及到了幾次數據拷貝?"

"2次?磁盤拷貝到內存,內存拷貝到Socket?"

"emmm,怪不得掛了,不冤"

"這種方式一共涉及了4次數據拷貝,知道用戶態和內核態的區別嗎?"

"瞭解"

"行,文字有點乾癟,你先看這個圖"


螞蟻二面,面試官問我零拷貝的實現原理,當場懵了…

1、應用程序中調用 read() 方法,這裡會涉及到一次上下文切換(用戶態->內核態),底層採用DMA(direct memory access)讀取磁盤的文件,並把內容存儲到內核地址空間的讀取緩存區。

2、由於應用程序無法訪問內核地址空間的數據,如果應用程序要操作這些數據,得把這些內容從讀取緩衝區拷貝到用戶緩衝區。 read() 調用的返回引發一次上下文切換(內核態->用戶態),現在數據已經被拷貝到了用戶地址空間緩衝區,如果有需要,可以操作修改這些內容。

3、我們最終目的是把這個文件內容通過Socket傳到另一個服務中,調用Socket的 send()方法,又涉及到一次上下文切換(用戶態->內核態),同時,文件內容被進行第三次拷貝,這次的緩衝區與目標套接字相關聯,與讀取緩衝區無關。

4、 send()調用返回,引發第四次的上下文切換,同時進行第四次拷貝,DMA把數據從目標套接字相關的緩存區傳到協議引擎進行發送。

"整個過程中,過程1和4是由DMA負責,並不會消耗CPU,只有過程2和3的拷貝需要CPU參與,整明白了?"

"我消化一下..."

半小時後...

"狼哥,感覺這個過程中好幾次的拷貝都是多餘的,很影響性能啊"

"對,所以才有了零拷貝技術"

"具體咋實現?"

"慢慢來,如果在應用程序中,不需要操作內容,過程2和3顯然是多餘的,如果可以直接把內核態讀取緩存衝區數據直接拷貝到套接字相關的緩存區,是不是可以達到目的?"

螞蟻二面,面試官問我零拷貝的實現原理,當場懵了…

這種實現,可以有以下幾點改進:

  • 上下文切換的次數從四次減少到了兩次
  • 拷貝次數從四次減少到了三次(其中DMA copy 2次,CPU copy 1次)

"怎麼實現?"

"在Java中,FileChannel的transferTo() 方法可以實現這個過程,該方法將數據從文件通道傳輸到給定的可寫字節通道, 上面的 file.read()和 socket.send() 調用動作可以替換為 transferTo() 調用"

<code>

publicvoid

transferTo

(

long

position,

long

count, WritableByteChannel target)

;/<code>

在 UNIX 和各種 Linux 系統中,此調用被傳遞到 sendfile() 系統調用中,最終實現將數據從一個文件描述符傳輸到了另一個文件描述符。

"這樣確實改善了很多,但還沒達到零拷貝的要求(還有一次cpu參與的拷貝),還有其它黑技術?"

"對的,如果底層網絡接口卡支持收集操作的話,就可以進一步的優化。"

"怎麼說?"

在 Linux 內核 2.4 及後期版本中,針對套接字緩衝區描述符做了相應調整,DMA自帶了收集功能,對於用戶方面,用法還是一樣,只是內部操作已經發生了改變:

螞蟻二面,面試官問我零拷貝的實現原理,當場懵了…

具體過程:

1、transferTo() 方法使用 DMA 將文件內容拷貝到內核讀取緩衝區。

2、避免了內容的整體拷貝,只把包含數據位置和長度信息的描述符追加到套接字緩衝區,DMA 引擎直接把數據從內核緩衝區傳到協議引擎,從而消除了最後一次 CPU參與的拷貝動作。


分享到:


相關文章: