前言
零拷貝技術是指計算機執行操作時,CPU不需要先將資料從某處記憶體複製到另一個特定區域。這種技術通常用於通過網路傳輸檔案時節省CPU週期和記憶體頻寬。
原始的網路請求,需要數次在使用者態和核心態之間切換以及資料的拷貝,這無疑大大影響了處理的效率,零拷貝技術就是為解決這一問題而誕生的。
我們常見的高效能元件(Netty、Kafka等),其內部基本都應用了零拷貝,在學習這些元件之前,有必要先了解什麼是零拷貝。
傳統檔案傳輸 read + write
DMA拷貝:指外部裝置不通過CPU而直接與系統記憶體交換資料的介面技術
如上圖所示,傳統的網路傳輸,需要進行4次使用者態和核心態切換,4次資料拷貝(2次CPU拷貝,2次DMA拷貝)
上下文的切換涉及到作業系統,相對CPU速度是非常耗時的,而且僅僅一次檔案傳輸,竟然需要4次資料拷貝,造成CPU資源極大的浪費
不難看出,傳統網路傳輸涉及很多冗餘且無意義的操作,導致應用在高併發情況下,效能指數級下降,表現異常糟糕
為了解決這一問題,零拷貝技術誕生了,他其實是一個抽象的概念,但其本質就是通過減少上下文切換和資料拷貝次數來實現的
mmap + write
如上圖所示,mmap技術傳輸檔案,需要進行4次使用者態和核心態切換,3次資料拷貝(1次CPU拷貝、兩次DMA拷貝)
相對於傳統資料傳輸,mmap減少了一次CPU拷貝,其具體過程如下:
- 應用程式呼叫 mmap() ,DMA 會把磁碟的資料拷貝到核心的緩衝區裡,應用程式跟作業系統核心「共享」這個緩衝區
- 應用程式再呼叫 write(),作業系統直接將核心緩衝區的資料拷貝到 socket 緩衝區中,這一切都發生在核心態,由 CPU 來搬運資料
- 最後,把核心的 socket 緩衝區裡的資料,拷貝到網路卡的緩衝區裡,這個過程是由 DMA 搬運的
顯然僅僅減少一次資料拷貝,依然難以滿足要求
sendfile
如上圖所屬,sendfile技術傳輸檔案,需要進行2次使用者態和核心態的切換,3次資料拷貝(1次CPU拷貝、兩次DMA拷貝)
相對於mmap,其又減少了兩次上下文的切換,具體過程如下:
- 應用呼叫sendfile介面,傳入檔案描述符,應用程式切換至核心態,並通過 DMA 將磁碟上的資料拷貝到核心緩衝區中
- CPU將緩衝區資料拷貝至Socket緩衝區
- DMA將資料拷貝到網路卡的緩衝區裡,應用程式切換至使用者態
sendfile其實是將原來的兩步讀寫操作進行了合併,從而減少了2次上下文的切換,但其仍然不是真正意義上的“零”拷貝
sendfile + SG-DMA
從 Linux 核心 2.4
版本開始起,對於支援網路卡支援 SG-DMA 技術的情況下, sendfile()
系統呼叫的過程發生了點變化,如上圖所示,sendfile + SG-DMA技術傳輸檔案,需要進行2次使用者態和核心態的切換,2次資料拷貝(1次DMA拷貝,1次SG-DMA拷貝)
具體過程如下:
- 通過 DMA 將磁碟上的資料拷貝到核心緩衝區裡;
- 緩衝區描述符和資料長度傳到 socket 緩衝區,這樣網路卡的 SG-DMA 控制器就可以直接將核心快取中的資料拷貝到網路卡的緩衝區裡,此過程不需要將資料從作業系統核心緩衝區拷貝到 socket 緩衝區中,這樣就減少了一次資料拷貝;
此種方式對比之前的,真正意義上去除了CPU拷貝,CPU 的快取記憶體再不會被汙染了,CPU 可以去執行其他的業務計算任務,同時和 DMA 的 I/O 任務並行,極大地提升系統效能。
但他的劣勢也很明顯,強依賴於硬體的支援
splice
Linux 在 2.6.17 版本引入 splice 系統呼叫,不再需要硬體支援,同時還實現了兩個檔案描述符之間的資料零拷貝。
splice 系統呼叫可以在核心空間的讀緩衝區(read buffer)和網路緩衝區(socket buffer)之間建立管道(pipeline),從而避免了使用者緩衝區和Socket緩衝區的 CPU 拷貝操作。
基於 splice 系統呼叫的零拷貝方式,整個拷貝過程會發生 2次使用者態和核心態的切換,2次資料拷貝(2次DMA拷貝),具體過程如下:
- 使用者程式通過 splice() 函式向核心(kernel)發起系統呼叫,上下文從使用者態(user space)切換為核心態(kernel space)。
- CPU 利用 DMA 控制器將資料從主存或硬碟拷貝到核心空間(kernel space)的讀緩衝區(read buffer)。
- CPU 在核心空間的讀緩衝區(read buffer)和網路緩衝區(socket buffer)之間建立管道(pipeline)。
- CPU 利用 DMA 控制器將資料從網路緩衝區(socket buffer)拷貝到網路卡進行資料傳輸。
- 上下文從核心態(kernel space)切換回使用者態(user space),splice 系統呼叫執行返回。
splice 拷貝方式也同樣存在使用者程式不能對資料進行修改的問題。除此之外,它使用了 Linux 的管道緩衝機制,可以用於任意兩個檔案描述符中傳輸資料,但是它的兩個檔案描述符引數中有一個必須是管道裝置
總結
本文簡單介紹了 Linux 中的幾種 Zero-copy 技術,隨著技術的不斷髮展,又出現了諸如:寫時複製、共享緩衝等技術,本文就不再贅述。
廣義的來講,Linux 的 Zero-copy 技術可以歸納成以下三大類:
- 減少甚至避免使用者空間和核心空間之間的資料拷貝:在一些場景下,使用者程式在資料傳輸過程中並不需要對資料進行訪問和處理,那麼資料在 Linux 的
Page Cache
和使用者程式的緩衝區之間的傳輸就完全可以避免,讓資料拷貝完全在核心裡進行,甚至可以通過更巧妙的方式避免在核心裡的資料拷貝。這一類實現一般是是通過增加新的系統呼叫來完成的,比如 Linux 中的 mmap(),sendfile() 以及 splice() 等。 - 繞過核心的直接 I/O:允許在使用者態程式繞過核心直接和硬體進行資料傳輸,核心在傳輸過程中只負責一些管理和輔助的工作。這種方式其實和第一種有點類似,也是試圖避免使用者空間和核心空間之間的資料傳輸,只是第一種方式是把資料傳輸過程放在核心態完成,而這種方式則是直接繞過核心和硬體通訊,效果類似但原理完全不同。
- 核心緩衝區和使用者緩衝區之間的傳輸優化:這種方式側重於在使用者程式的緩衝區和作業系統的頁快取之間的 CPU 拷貝的優化。這種方法延續了以往那種傳統的通訊方式,但更靈活。