知識補充:
1、DMA是直接內(nèi)存訪問(Direct Memory Access) 技術(shù),早期 DMA 只存在在主板上,如今由于 I/O 設(shè)備越來越多,數(shù)據(jù)傳輸?shù)男枨笠膊槐M相同,所以每個 I/O 設(shè)備里面都有自己的 DMA 控制器。
2、每次系統(tǒng)調(diào)用都得先從用戶態(tài)切換到內(nèi)核態(tài),等內(nèi)核完成任務(wù)后,再從內(nèi)核態(tài)切換回用戶態(tài)。
我們都聽過 kafka 很快,其中一個原因是 kafka 使用零拷貝。看看從文件中讀取數(shù)據(jù)并通過網(wǎng)絡(luò)將數(shù)據(jù)傳輸?shù)搅硪粋€程序的場景,在內(nèi)部的復(fù)制操作,需要在用戶模式和內(nèi)核模式之間進(jìn)行四次上下文切換,并且進(jìn)行數(shù)據(jù)復(fù)制四次。
- 來自磁盤的數(shù)據(jù)被復(fù)制到內(nèi)核讀取緩沖區(qū)中。第一個拷貝由直接內(nèi)存訪問 (DMA) 引擎執(zhí)行,該引擎從磁盤讀取文件內(nèi)容并將它們存儲到內(nèi)核地址空間緩沖區(qū)中。
- 從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用程序讀取緩沖區(qū)(上下文切換,切換到用戶模式)。
- 從應(yīng)用程序緩沖區(qū)復(fù)制到內(nèi)核套接字緩沖區(qū)并再次將數(shù)據(jù)放入內(nèi)核地址空間緩沖區(qū)(切換到內(nèi)核模式)。
- 從套接字緩沖區(qū)它將復(fù)制到網(wǎng)絡(luò)緩沖區(qū)
完成所有四個操作后,它將再次切換到用戶模式。
回顧以上動作,其實發(fā)現(xiàn)實際上第二個和第三個數(shù)據(jù)拷貝是可以避免的。JAVA 類庫通過 java.nio.channels 中的 transferTo() 方法在 UNIX 系統(tǒng)上執(zhí)行零副本,使用零拷貝的應(yīng)用程序請求內(nèi)核直接將數(shù)據(jù)從磁盤文件復(fù)制到套接字,而不通過應(yīng)用程序。零拷貝極大提高了應(yīng)用程序性能,并減少了內(nèi)核和用戶模式之間的上下文切換次數(shù)。
我們看看 transferTo() 是如何復(fù)制數(shù)據(jù)的?
- transferTo()方法致使文件內(nèi)容被 DMA(直接內(nèi)存訪問)引擎復(fù)制到讀取緩沖區(qū)中。
- 然后內(nèi)核將數(shù)據(jù)復(fù)制到套接字關(guān)聯(lián)的內(nèi)核緩沖區(qū)中。
- 從套接字緩沖區(qū)它將復(fù)制到網(wǎng)絡(luò)緩沖區(qū)
這里我們還需要 3 個副本和 2 個上下文切換。
但當(dāng)前還沒有達(dá)到零拷貝,如果底層網(wǎng)卡支持收集操作,可以進(jìn)一步減少內(nèi)核重復(fù)拷貝數(shù)據(jù)的操作。在 linux 內(nèi)核 2.4 及更高版本中,套接字緩沖區(qū)描述符支持該場景。
- transferTo()方法使 DMA 引擎將文件內(nèi)容復(fù)制到內(nèi)核緩沖區(qū)中。
- 沒有數(shù)據(jù)被復(fù)制到套接字緩沖區(qū)中,相反,只有有關(guān)數(shù)據(jù)位置和長度信息的描述符才會附加到套接字緩沖區(qū)。DMA 引擎將數(shù)據(jù)直接從內(nèi)核緩沖區(qū)傳遞到網(wǎng)絡(luò)緩沖區(qū)。
Kafka 和 Nginx 都有實現(xiàn)零拷貝技術(shù),這將大大提高文件傳輸?shù)男阅堋?截惣夹g(shù),本質(zhì)上講就是通過減少非必要的內(nèi)存拷貝以及上下文切換,來提高文件在通道間復(fù)制速度的一種技術(shù)。以本文中的transferTo()方法為例,通過該技術(shù),可以將原來四次內(nèi)存間拷貝減少成兩次,將四次上下文切換減少成兩次,大大提高復(fù)制的速度。但零拷貝技術(shù)并非萬能的,它有自己的使用場景,對于將大量數(shù)據(jù)從一個 I/O 通道復(fù)制到另一個通道的情況(例如 Web 服務(wù)器),都是合適的。