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

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

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

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

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

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