深入理解零拷貝技術

新人十三發表於2021-09-06

前言

零拷貝技術是指計算機執行操作時,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拷貝,其具體過程如下:

  1. 應用程式呼叫 mmap() ,DMA 會把磁碟的資料拷貝到核心的緩衝區裡,應用程式跟作業系統核心「共享」這個緩衝區
  2. 應用程式再呼叫 write(),作業系統直接將核心緩衝區的資料拷貝到 socket 緩衝區中,這一切都發生在核心態,由 CPU 來搬運資料
  3. 最後,把核心的 socket 緩衝區裡的資料,拷貝到網路卡的緩衝區裡,這個過程是由 DMA 搬運的

顯然僅僅減少一次資料拷貝,依然難以滿足要求

sendfile

深入理解零拷貝技術

如上圖所屬,sendfile技術傳輸檔案,需要進行2次使用者態和核心態的切換,3次資料拷貝(1次CPU拷貝、兩次DMA拷貝)

相對於mmap,其又減少了兩次上下文的切換,具體過程如下:

  1. 應用呼叫sendfile介面,傳入檔案描述符,應用程式切換至核心態,並通過 DMA 將磁碟上的資料拷貝到核心緩衝區中
  2. CPU將緩衝區資料拷貝至Socket緩衝區
  3. DMA將資料拷貝到網路卡的緩衝區裡,應用程式切換至使用者態

sendfile其實是將原來的兩步讀寫操作進行了合併,從而減少了2次上下文的切換,但其仍然不是真正意義上的“零”拷貝

sendfile + SG-DMA

深入理解零拷貝技術

從 Linux 核心 2.4 版本開始起,對於支援網路卡支援 SG-DMA 技術的情況下, sendfile() 系統呼叫的過程發生了點變化,如上圖所示,sendfile + SG-DMA技術傳輸檔案,需要進行2次使用者態和核心態的切換,2次資料拷貝(1次DMA拷貝,1次SG-DMA拷貝)

具體過程如下:

  1. 通過 DMA 將磁碟上的資料拷貝到核心緩衝區裡;
  2. 緩衝區描述符和資料長度傳到 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拷貝),具體過程如下:

  1. 使用者程式通過 splice() 函式向核心(kernel)發起系統呼叫,上下文從使用者態(user space)切換為核心態(kernel space)。
  2. CPU 利用 DMA 控制器將資料從主存或硬碟拷貝到核心空間(kernel space)的讀緩衝區(read buffer)。
  3. CPU 在核心空間的讀緩衝區(read buffer)和網路緩衝區(socket buffer)之間建立管道(pipeline)。
  4. CPU 利用 DMA 控制器將資料從網路緩衝區(socket buffer)拷貝到網路卡進行資料傳輸。
  5. 上下文從核心態(kernel space)切換回使用者態(user space),splice 系統呼叫執行返回。

splice 拷貝方式也同樣存在使用者程式不能對資料進行修改的問題。除此之外,它使用了 Linux 的管道緩衝機制,可以用於任意兩個檔案描述符中傳輸資料,但是它的兩個檔案描述符引數中有一個必須是管道裝置

總結

本文簡單介紹了 Linux 中的幾種 Zero-copy 技術,隨著技術的不斷髮展,又出現了諸如:寫時複製、共享緩衝等技術,本文就不再贅述。

廣義的來講,Linux 的 Zero-copy 技術可以歸納成以下三大類:

  • 減少甚至避免使用者空間和核心空間之間的資料拷貝:在一些場景下,使用者程式在資料傳輸過程中並不需要對資料進行訪問和處理,那麼資料在 Linux 的 Page Cache 和使用者程式的緩衝區之間的傳輸就完全可以避免,讓資料拷貝完全在核心裡進行,甚至可以通過更巧妙的方式避免在核心裡的資料拷貝。這一類實現一般是是通過增加新的系統呼叫來完成的,比如 Linux 中的 mmap(),sendfile() 以及 splice() 等。
  • 繞過核心的直接 I/O:允許在使用者態程式繞過核心直接和硬體進行資料傳輸,核心在傳輸過程中只負責一些管理和輔助的工作。這種方式其實和第一種有點類似,也是試圖避免使用者空間和核心空間之間的資料傳輸,只是第一種方式是把資料傳輸過程放在核心態完成,而這種方式則是直接繞過核心和硬體通訊,效果類似但原理完全不同。
  • 核心緩衝區和使用者緩衝區之間的傳輸優化:這種方式側重於在使用者程式的緩衝區和作業系統的頁快取之間的 CPU 拷貝的優化。這種方法延續了以往那種傳統的通訊方式,但更靈活。

 

相關文章