淺析操作系統(tǒng)和Netty中的零拷貝機(jī)制

點(diǎn)擊上方「藍(lán)字」關(guān)注我們

零拷貝機(jī)制(Zero-Copy)是在操作數(shù)據(jù)時(shí)不需要將數(shù)據(jù)從一塊內(nèi)存區(qū)域復(fù)制到另一塊內(nèi)存區(qū)域的技術(shù),這樣就避免了內(nèi)存的拷貝,使得可以提高CPU的。零拷貝機(jī)制是一種操作數(shù)據(jù)的優(yōu)化方案,通過(guò)避免數(shù)據(jù)在內(nèi)存中拷貝達(dá)到的提高CPU性能的方案。
一、操作系統(tǒng)的零拷貝機(jī)制
操作系統(tǒng)的存儲(chǔ)空間包含硬盤(pán)和內(nèi)存,而內(nèi)存又分成用戶空間和內(nèi)核空間。以從文件服務(wù)器下載文件為例,服務(wù)器需要將硬盤(pán)中的數(shù)據(jù)通過(guò)網(wǎng)絡(luò)通信發(fā)送給客戶端,大致流程如下:
操作系統(tǒng)通過(guò)DMA傳輸將硬盤(pán)中的數(shù)據(jù)復(fù)制到內(nèi)核緩沖區(qū)
操作系統(tǒng)執(zhí)行read方法將內(nèi)核緩沖區(qū)的數(shù)據(jù)復(fù)制到用戶空間
操作系統(tǒng)執(zhí)行write方法將用戶空間的數(shù)據(jù)復(fù)制到內(nèi)核socket緩沖區(qū)
操作系統(tǒng)通過(guò)DMA傳輸將內(nèi)核socket緩沖區(qū)數(shù)據(jù)復(fù)制給網(wǎng)卡發(fā)送數(shù)據(jù)
流程如下圖示:

整個(gè)流程中:DMA拷貝2次、CPU拷貝2次、用戶空間和內(nèi)核空間切換4次
整個(gè)流程從內(nèi)核空間和硬件之間數(shù)據(jù)拷貝是DMA復(fù)制傳輸,內(nèi)核空間和用戶空間之間數(shù)據(jù)拷貝是通過(guò)CPU復(fù)制.另外CPU除了需要參與拷貝任務(wù),還需要多次從內(nèi)核空間和用戶空間之間來(lái)回切換,無(wú)疑都額外增加了很多的CPU工作負(fù)擔(dān)。
所以操作系統(tǒng)為了減少CPU拷貝數(shù)據(jù)帶來(lái)的性能消耗,提供了幾種解決方案來(lái)減少CPU拷貝次數(shù)
1.1、使用mmap函數(shù)
mmap函數(shù)的作用相當(dāng)于是內(nèi)存共享,將內(nèi)核空間的內(nèi)存區(qū)域和用戶空間共享,這樣就避免了將內(nèi)核空間的數(shù)據(jù)拷貝到用戶空間的步驟,通過(guò)mmap函數(shù)發(fā)送數(shù)據(jù)時(shí)上述的步驟如下:
操作系統(tǒng)通過(guò)DMA傳輸將硬盤(pán)中的數(shù)據(jù)復(fù)制到內(nèi)核緩沖區(qū),執(zhí)行了mmap函數(shù)之后,拷貝到內(nèi)核緩沖區(qū)的數(shù)據(jù)會(huì)和用戶空間進(jìn)行共享,所以不需要進(jìn)行拷貝
CPU將內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到內(nèi)核空間socket緩沖區(qū)
操作系統(tǒng)通過(guò)DMA傳輸將內(nèi)核socket緩沖區(qū)數(shù)據(jù)拷貝給網(wǎng)卡發(fā)送數(shù)據(jù)
流程如下圖示:

整個(gè)流程中:DMA拷貝2次、CPU拷貝1次、用戶空間和內(nèi)核空間切換4次
可以發(fā)現(xiàn)此種方案避免了內(nèi)核空間和用戶空間之間數(shù)據(jù)的拷貝工作,但是在內(nèi)核空間內(nèi)部還是會(huì)有一次數(shù)據(jù)拷貝過(guò)程,而且CPU還是會(huì)有從內(nèi)核空間和用戶空間的切換過(guò)程
1.2、使用sendfile函數(shù)
senfile函數(shù)的作用是將一個(gè)文件描述符的內(nèi)容發(fā)送給另一個(gè)文件描述符。而用戶空間是不需要關(guān)心文件描述符的,所以整個(gè)的拷貝過(guò)程只會(huì)在內(nèi)核空間操作,相當(dāng)于減少了內(nèi)核空間和用戶空間之間數(shù)據(jù)的拷貝過(guò)程,而且還避免了CPU在內(nèi)核空間和用戶空間之間的來(lái)回切換過(guò)程。整體流程如下:
通過(guò)DMA傳輸將硬盤(pán)中的數(shù)據(jù)復(fù)制到內(nèi)核頁(yè)緩沖區(qū)
通過(guò)sendfile函數(shù)將頁(yè)緩沖區(qū)的數(shù)據(jù)通過(guò)CPU拷貝給socket緩沖區(qū)
網(wǎng)卡通過(guò)DMA傳輸將socket緩沖區(qū)的數(shù)據(jù)拷貝走并發(fā)送數(shù)據(jù)
流程如下圖示:

整個(gè)過(guò)程中:DMA拷貝2次、CPU拷貝1次、內(nèi)核空間和用戶空間切換0次
可以看出通過(guò)sendfile函數(shù)時(shí)只會(huì)有一次CPU拷貝過(guò)程,而且全程都是在內(nèi)核空間實(shí)現(xiàn)的,所以整個(gè)過(guò)程都不會(huì)使得CPU在內(nèi)核空間和用戶空間進(jìn)行來(lái)回切換的操作,性能相比于mmap而言要更好
另外如果硬件支持的話,sendfile函數(shù)還可以直接將文件描述符和數(shù)據(jù)長(zhǎng)度發(fā)送給socket緩沖區(qū),然后直接通過(guò)DMA傳輸將頁(yè)緩沖區(qū)的數(shù)據(jù)拷貝給網(wǎng)卡進(jìn)行發(fā)送即可,這樣就避免了CPU在內(nèi)核空間內(nèi)的拷貝過(guò)程,流程如下:
通過(guò)DMA傳輸將硬盤(pán)中的數(shù)據(jù)復(fù)制到內(nèi)核頁(yè)緩沖區(qū)
通過(guò)sendfile函數(shù)將頁(yè)緩沖區(qū)數(shù)據(jù)的文件描述符和數(shù)據(jù)長(zhǎng)度發(fā)送給socket緩沖區(qū)
網(wǎng)卡通過(guò)DMA傳輸根據(jù)文件描述符和文件長(zhǎng)度直接從頁(yè)緩沖區(qū)拷貝數(shù)據(jù)
如下圖示:

整個(gè)過(guò)程中:DMA拷貝2次、CPU拷貝0次、內(nèi)核空間和用戶空間切換0次
所以整個(gè)過(guò)程都是沒(méi)有CPU拷貝的過(guò)程的,實(shí)現(xiàn)了真正的CPU零拷貝機(jī)制
1.3、使用slice函數(shù)
splice函數(shù)的作用是將兩個(gè)文件描述符之間建立一個(gè)管道,然后將文件描述符的引用傳遞過(guò)去,這樣在使用到數(shù)據(jù)的時(shí)候就可以直接通過(guò)引用指針訪問(wèn)到具體數(shù)據(jù)。過(guò)程如下:
通過(guò)DMA傳輸將文件復(fù)制到內(nèi)核頁(yè)緩沖區(qū)
通過(guò)splice函數(shù)在頁(yè)緩沖區(qū)和socket緩沖區(qū)之間建立管道,并將文件描述符的引用指針發(fā)送給socket緩沖區(qū)
網(wǎng)卡通過(guò)DMA傳輸根據(jù)文件描述符的指針直接訪問(wèn)數(shù)據(jù)
如下圖示:

整個(gè)過(guò)程中:DMA拷貝2次、CPU拷貝0次、內(nèi)核空間和用戶空間切換0次
可以看出通過(guò)slice函數(shù)傳輸數(shù)據(jù)時(shí)同樣可以實(shí)現(xiàn)CPU的零拷貝,且不需要CPU在內(nèi)核空間和用戶空間之間來(lái)回切換
總結(jié):實(shí)際上操作系統(tǒng)的零拷貝機(jī)制只是針對(duì)于CPU的零拷貝,而內(nèi)核空間和硬件之間還是會(huì)存在數(shù)據(jù)拷貝的過(guò)程,只不過(guò)通過(guò)DMA傳輸,而不需要CPU來(lái)參與數(shù)據(jù)的拷貝過(guò)程可以看出通過(guò)mmap函數(shù)可以減少一次CPU拷貝,但是還會(huì)有一個(gè)CPU拷貝。而使用sendfile和splice函數(shù)都已經(jīng)實(shí)現(xiàn)了CPU零拷貝而實(shí)現(xiàn)了數(shù)據(jù)傳輸過(guò)程。
二、Java中的零拷貝機(jī)制
Java的應(yīng)用程序經(jīng)常會(huì)遇到數(shù)據(jù)傳輸?shù)膱?chǎng)景,在Java NIO包中就提供了零拷貝機(jī)制的實(shí)現(xiàn),主要是通過(guò)NIO包中的FileChannel實(shí)現(xiàn)FileChannel提供了transferTo和transferFrom方法,都是采用了調(diào)用底層操作系統(tǒng)的sendfile函數(shù)來(lái)實(shí)現(xiàn)的CPU零拷貝機(jī)制。
kafka服務(wù)器就是采用了FileChannel的transfer方法實(shí)現(xiàn)了高性能的IO傳輸操作
Netty中的零拷貝機(jī)制Netty作為NIO的高性能網(wǎng)絡(luò)通信框架,同樣也實(shí)現(xiàn)了零拷貝機(jī)制,不過(guò)和操作系統(tǒng)的零拷貝機(jī)制則不是一個(gè)概念
Netty中的零拷貝機(jī)制體現(xiàn)在多個(gè)場(chǎng)景:
使用直接內(nèi)存,在進(jìn)行IO數(shù)據(jù)傳輸時(shí)避免了ByteBuf從堆外內(nèi)存拷貝到堆內(nèi)內(nèi)存的步驟,而如果使用堆內(nèi)內(nèi)存分配ByteBuf的話,那么發(fā)送數(shù)據(jù)時(shí)需要將IO數(shù)據(jù)從堆內(nèi)內(nèi)存拷貝到堆外內(nèi)存才能通過(guò)Socket發(fā)送
Netty的文件傳輸使用了FileChannel的transferTo方法,底層使用到sendfile函數(shù)來(lái)實(shí)現(xiàn)了CPU零拷貝
Netty中提供CompositeByteBuf類,用于將多個(gè)ByteBuf合并成邏輯上的ByteBuf,避免了將多個(gè)ByteBuf拷貝成一個(gè)ByteBuf的過(guò)程
ByteBuf支持slice方法可以將ByteBuf分解成多個(gè)共享內(nèi)存區(qū)域的ByteBuf,避免了內(nèi)存拷貝
source:https://www.cnblogs.com/jackion5/p/13604331.html
掃碼二維碼
獲取更多精彩
Java樂(lè)園

