三分鐘掌握零拷貝的那些事
零拷貝可以直觀的理解為不需要將數據從一個存儲區域拷貝到另外一個存儲區域,從而提高數據的效率。這里的零是指CPU參與整個拷貝過程的次數。下面我們來聊聊傳統的數據傳輸(write+read)和零拷貝的幾種實現方式的數據傳輸原理。
1、write+read數據傳輸的原理
在我們的Java代碼中傳輸的數據時候會用到write方法,write方法寫數據發送到網卡的過程如下圖所示:
圖片
然后接收方使用read方法從網卡接收數據的流程如下圖所示:
圖片
通過write+read的方式我們就可以實現數據的傳輸,但是在這兩個方法背后是需要做很多的工作,如下所示的原理圖:
圖片
當我們調用read方法的時候,首先需要從用戶態切換到內核態,然后通過DMA拷貝(Direct Memory Access,即直接內存訪問。DMA本質上是一塊主板上獨立的芯片,允許外設設備和內存存儲器之間直接進行IO數據傳輸,其過程不需要CPU的參與)將磁盤里面的文件拷貝到內核緩沖區上,數據拷貝到內存緩沖區之后又需要進行內核態轉化為用戶態,將內核緩沖區中的數據拷貝到用戶緩沖區。這里涉及到2次的狀態切換、1次DMA拷貝、1次CPU拷貝。
文件數據拷貝到用戶緩存區之后,首先需要從用戶態切換到內核態,然后通過調用write方法,此時CPU就會將用戶緩存區的文件數據拷貝到內核的socket緩存區上,最后通過DMA拷貝將數據拷貝到網卡上,當數據拷貝到網卡成功后再從內核態切換到用戶態。
傳統的文件傳輸的整個過程中,涉及到了4次用戶態與內核態的上下文切換,執行了4次數據的拷貝(2次DMA拷貝、2次CPU拷貝)。
在文件數據傳輸的過程中,我們的目的就是將磁盤的文件發送到網卡上
圖片
但是文件數據需要經過多個過程才能到達網卡,于是就研究人員提出了通過減少用戶態和內核態的轉換或者減少內存拷貝的次數的方式來提高文件拷貝的效率,這就是零拷貝技術產生的背景。
2、mmap + write
使用mmap+write方式替換原來的傳統IO方式,實質就是利用了虛擬內存。虛擬內存在現代操作系統使用很廣泛,其特性如下所示:
(a)多個虛擬內存可以指向同一個物理地址。
(b)虛擬內存空間可以遠遠大于物理內存空間。
mmap正是利用第一條特性,將內核空間和用戶空間的虛擬地址映射到同一個物理地址,這樣在IO操作時就不需要來回復制了,如下所示的虛擬內存示意圖:
圖片
mmap + write實現的零拷貝的流程圖如下所示:
圖片
當調用mmap方法的時候,首先從用戶態切換到內核態,然后將磁盤文件使用DMA拷貝到內核緩存區,由于內核緩沖區與用戶緩沖區已經完成了映射(虛擬內存),所以這個時候就不需要將數據從內核緩沖區拷貝到用戶緩存區,當數據拷貝到內核緩沖區之后,又要從內核態切換到用戶態。
當調用write方法的時候,進程要從用戶態切換到內核態,然后進程直接操作內核緩沖區里面的數據拷貝到socket緩沖區中,拷貝到socket緩沖區完成后,再通過DMA拷貝到網卡,網上數據拷貝完成之后又要從內核態切換到用戶態。
整個過程還是存在了4次用戶態和內核態的切換,發生了3次數據的拷貝(2次DMA拷貝、1次CPU拷貝),雖然相比于傳統的文件傳輸過程少了一次CPU拷貝,數據的傳輸的效率有一定的提升。
3、sendfile
sendfile是Linux2.1版本提供的一個系統調用函數,主要是負責發送文件的。只需要調用sendfile函數就可以完成整個文件拷貝的過程,如下圖所示的流程圖:
圖片
當調用sendfile的時候,首先需要從用戶態切換到內核態,使用DMA拷貝將文件拷貝到內核緩存區,然后將內核緩存區的數據通過CPU拷貝到socket的緩存區,最后再通過DMA拷貝數據到網卡上,完成網卡數據拷貝后再從內核態切換到用戶態。
sendfile實現數據傳輸的過程存在了2次用戶態和內核態的切換,發生了3次數據的拷貝(2次DMA拷貝、1次CPU拷貝),相比于mmap + write的方式又提升了一些效率。
4、sendfile + SG-DMA
在Linux2.4版本中網卡支持SG-DMA技術,那么使用SG-DMA可以進一步的優化零拷貝的過程,如下所示的流程圖:
圖片
當調用sendfile的時候,首先用戶態切換到內核態,然后使用DMA拷貝將文件從磁盤拷貝到內核緩存區,接下來它就會將描述符和數據長度發送到socket緩存區,這樣就可以直接將數據從內核緩存區通過SG-DMA拷貝到網卡,數據拷貝到網卡上結束后再從內核態切換到用戶態。
通過SG-DMA拷貝就不需要將數據拷貝到socket緩存區再通過DMA的方式拷貝到網卡了,而是直接從內核緩存區拷貝到網卡。整個過程存在了2次用戶態和內核態的切換,發生了2次數據的拷貝(2次DMA拷貝、0次CPU拷貝)。
send file + SG-DMA算是真正意義上實現了零拷貝技術,它在整個過程都是通過DMA在系統內核完成的,數據拷貝不需要CPU參與。
5、splice
在Linux2.6.17內核版本中引入了splice系統調用方法,splice和sendfle方法不同點在于它是不需要硬件支持。如下所示的splice原理圖:
圖片
splice是在內核空間的緩存區和socket緩存區之間建立管道,從而避免了兩者之間的CPU拷貝操作。splice的整個拷貝過程發生了2次用戶態和內核態的切換,2次數據的拷貝(2次DMA拷貝、0次CPU拷貝)。
總結:
(1)無論是傳統的IO方式還是零拷貝技術,2次DMA拷貝是必備的(DMA都是依賴硬件完成的),零拷貝只是減少CPU拷貝與上下文的切換(用戶態和內核態的切換)。
(2)零拷貝的實現有mmap+write、sendfile、sendfile + SG-DMA、splice等方式。
(3)不是所有的操作系統都支持零拷貝技術,目前只有在使用NIO和 Epoll數據傳輸時才可使用。
(4)RocketMQ和Kafka都使用到了零拷貝的技術。其中,RocketMQ中生產者發送數據、消費者讀取數據都是使用mmap+write方式;而Kafka的生產者持久化數據使用mmap+write方法,消費者讀取數據使用sendfile方式。
(5)Java的NIO中MappedByteBuffer底層使用的是mmap;FileChannel的transferTo()/transferFrom(),底層使用sendfile。