什麼是零拷貝機制(Zero Copy) ?

要理解零拷貝機制,首先須要瞭解它所要解決的問題,試想一個場景:咱們須要從磁盤讀取一個文件經過網絡輸出到一個客戶端。html

服務端的步驟通常是這樣的:linux

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

雖然只有兩個步驟:從磁盤讀取文件,將文件寫入到socket,可是在操做系統內部經歷了一個較爲複雜的過程,這個過程以下圖所示:api

上面部分顯示,這個過程經歷了四次上下文切換。下面部分顯示,經歷了四次數據拷貝過程:緩存

  1. 數據從磁盤複製到內核緩衝區
  2. 從內核緩衝區複製到用戶空間緩衝區
  3. 從用戶緩衝區複製到內核的socket緩衝區
  4. 從socket緩衝區複製到協議引擎(這裏是網卡驅動)

這裏 要把數據從磁盤複製到內核緩衝區是必須的,由於系統須要讀取數據輸出給網卡嘛。可是爲啥還要從內核複製一份到用戶空間呢?應用程序直接使用內核緩衝區的數據不就好了嗎?這是由於對於操做系統來講,可能有多個應用程序會同時使用這些數據,並有可能進行修改,若是讓你們都使用同一分內核空間的數據就會產生衝突。所以,操做系統設計爲:每一個應用程序想使用這些數據都必須複製一份到本身的用戶空間,這樣就不會互相影響了。因此這個機制在碰到數據不須要作修改的場景時就產生了浪費,數據原本能夠呆在內核緩衝區不動,不必再畫蛇添足拷貝一次到用戶空間。網絡

爲了不這種浪費,人們一開始採用了mmap調用的方式來進行優化。即應用程序再也不調用read,而是採用mmap,mmap會從磁盤複製數據到內核緩衝區,而後與用戶進程共享該內核緩衝區,這樣就再也不須要從內核緩衝區複製到用戶緩衝區了,這樣就比以前少了一次數據複製過程。不過,這種改進方式存在一些問題,好比在mmap一個文件時,文件被另外一個進程截斷將會產生錯誤等。固然,人們也發明了一些方法來解決這類問題,但無論怎樣都使得這個過程變得很複雜不易使用。app

隨着linux內核的發展,在2.1版本中引入了一個叫 sendfile的系統調用。sendfile至關於封裝了(mmap,write)的過程,自動處理了文件被截斷等問題,除此以外,sendfile還減小了上下文切換次數。sendfile的過程是這樣的:異步

  1. 從磁盤讀取文件內容到內核緩衝區
  2. 直接從內核緩衝區複製數據到socket緩衝區
  3. 從socket緩衝區複製到協議引擎(這裏是網卡驅動)

這種方式雖好,可是仍然存在一次從內核緩衝區到內核socket緩衝區的複製行爲,也就是從內存的一個區域複製到內存的另外一個區域的行爲,這個能夠避免嗎?後來人們又對硬件進行了改進,在硬件的幫助下來消除內存之間的數據複製。這種硬件須要支持一種叫「收集」操做的接口,它支持從內存中不一樣位置收集數據,也就是再也不限定於只從內核socket緩衝區來收集數據,而是能夠從內核緩衝區去收集。socket

在linux內核版本2.4中對sendfile調用作了一系列優化來適應這個需求,對於應用程序來講,sendfile的調用方式不須要作任何修改,可是它底層的機制有了必定的改進,以下圖所示:優化

如圖所示:數據複製到內核緩衝區之後,再也不須要整個拷貝到socket緩衝區,而是隻須要將數據的位置和長度信息(append dscr)傳輸到socket緩衝區,這樣DMA引擎會根據這些信息直接從內核緩存區複製數據給協議引擎。spa

最終,數據只須要從磁盤複製到內存,再從內存複製到協議引擎,跟最開始相比減小了從內核到用戶空間,從用戶空間到socket緩衝兩次複製。可是明明還有兩次數據的複製,爲何要叫「零拷貝」呢?這是由於從操做系統的角度來講,數據沒有從內存複製到內存的過程,也就沒有了CPU參與的過程, 因此對於操做系統來講就是零拷貝了。查看wiki對零拷貝的定義以下:

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.

從定義咱們看到,零拷貝是指不須要cpu參與在內存之間複製數據的操做。那這個過程爲啥不須要cpu參與呢?

仔細看上面的圖示,你會發現:從磁盤複製到內核緩衝區是經過DMA引擎來作而不是cpu,一樣的,從socket緩衝區到協議引擎也是由 DMA引擎來作,這樣就節省了cpu的工做。而這整個過程都在內核中完成也減小了操做系統的上下文切換開銷。

總結一下:

  • 存在用戶空間和內核空間的交互行爲就會產生上下文切換,因此即便用戶空間與內核空間共享同一份緩衝區也同樣。
  • 利用硬件支持的DMA引擎能夠減小對cpu的使用,磁盤,網卡都有此引擎。
  • 利用硬件的「收集」接口功能能夠將數據位置和長度直接傳輸給硬件相關的引擎,從而讓硬件引擎直接從相應的內存區域讀取數據。
  • 零拷貝是用於對不變的數據作傳輸,若是應用程序須要修改數據那勢必就不能用到零拷貝了,因此零拷貝可不是萬能的。實際上,零拷貝還有其它的一些限制條件,能夠參考相關的資料。

除了上面所列出的零拷貝機制,linux中零拷貝技術還有如下幾種:

  1. 直接IO:應用程序直接訪問硬件存儲,內核只輔助數據傳輸,不進行頁緩存。
  2. 寫時複製技術:當應用程序不須要修改數據時只保存在內核緩衝區,不復制到用戶空間。(這類方法沒有dma,須要cpu的全程參與)
  3.  splice:是與sendfile相似的一種方法,適用於將數據從一個地方傳送到另外一個地方不須要通過應用程序的處理。splice能夠在內核空間整塊地移動數據,並用能夠經過異步方式進行。splice容許任意兩個文件之間互相鏈接,而sendfile只是文件到socket之間,因此sendfile只是splice的了個子集。在2.6.23版本內核中,sendfile的機制已經沒有了,api相應的功能換成了splice機制來實現。

除了操做系統的零拷貝機制,在netty裏還有一種稱之爲用戶空間的零拷貝機制,但那徹底是另外一種原理以及解決徹底不一樣問題的一種機制。

 

【參考文獻】

https://www.linuxjournal.com/article/6345

https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/index.html

https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/index.html

相關文章
相關標籤/搜索