前言
零拷貝技術是指計算機執行操作時,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 拷貝的優化。這種方法延續了以往那種傳統的通信方式,但更靈活。