要理解零拷貝機制,首先須要瞭解它所要解決的問題,試想一個場景:咱們須要從磁盤讀取一個文件經過網絡輸出到一個客戶端。html
服務端的步驟通常是這樣的:linux
read(file, tmp_buf, len); write(socket, tmp_buf, len);
雖然只有兩個步驟:從磁盤讀取文件,將文件寫入到socket,可是在操做系統內部經歷了一個較爲複雜的過程,這個過程以下圖所示:api
上面部分顯示,這個過程經歷了四次上下文切換。下面部分顯示,經歷了四次數據拷貝過程:緩存
這裏 要把數據從磁盤複製到內核緩衝區是必須的,由於系統須要讀取數據輸出給網卡嘛。可是爲啥還要從內核複製一份到用戶空間呢?應用程序直接使用內核緩衝區的數據不就好了嗎?這是由於對於操做系統來講,可能有多個應用程序會同時使用這些數據,並有可能進行修改,若是讓你們都使用同一分內核空間的數據就會產生衝突。所以,操做系統設計爲:每一個應用程序想使用這些數據都必須複製一份到本身的用戶空間,這樣就不會互相影響了。因此這個機制在碰到數據不須要作修改的場景時就產生了浪費,數據原本能夠呆在內核緩衝區不動,不必再畫蛇添足拷貝一次到用戶空間。網絡
爲了不這種浪費,人們一開始採用了mmap調用的方式來進行優化。即應用程序再也不調用read,而是採用mmap,mmap會從磁盤複製數據到內核緩衝區,而後與用戶進程共享該內核緩衝區,這樣就再也不須要從內核緩衝區複製到用戶緩衝區了,這樣就比以前少了一次數據複製過程。不過,這種改進方式存在一些問題,好比在mmap一個文件時,文件被另外一個進程截斷將會產生錯誤等。固然,人們也發明了一些方法來解決這類問題,但無論怎樣都使得這個過程變得很複雜不易使用。app
隨着linux內核的發展,在2.1版本中引入了一個叫 sendfile的系統調用。sendfile至關於封裝了(mmap,write)的過程,自動處理了文件被截斷等問題,除此以外,sendfile還減小了上下文切換次數。sendfile的過程是這樣的:異步
這種方式雖好,可是仍然存在一次從內核緩衝區到內核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的工做。而這整個過程都在內核中完成也減小了操做系統的上下文切換開銷。
總結一下:
除了上面所列出的零拷貝機制,linux中零拷貝技術還有如下幾種:
除了操做系統的零拷貝機制,在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