"零拷貝"中的"拷貝"是操做系統在I/O操做中,將數據從一個內存區域複製到另一個內存區域. 而"零"並非指0次複製, 更多的是指在用戶態和內核態以前的複製是0次.java
經過計算機的組成原理咱們知道, 內存的讀寫操做是須要CPU的協調數據總線,地址總線和控制總線來完成的linux
所以在"拷貝"發生的時候,每每須要CPU暫停現有的處理邏輯,來協助內存的讀寫.這種咱們稱爲CPU COPYweb
cpu copy不但佔用了CPU資源,還佔用了總線的帶寬.數據庫
DMA(DIRECT MEMORY ACCESS)是現代計算機的重要功能. 它的一個重要 的特色就是, 當須要與外設進行數據交換時, CPU只須要初始化這個動做即可以繼續執行其餘指令,剩下的數據傳輸的動做徹底由DMA來完成api
能夠看到DMA COPY是能夠避免大量的CPU中斷的性能優化
本文中的上下文切換時指由用戶態切換到內核態, 以及由內核態切換到用戶態bash
操做系統爲了保護系統不被應用程序有意或無心地破壞,爲操做系統設置了用戶態和內核態兩種狀態.用戶態想要獲取系統資源(例如訪問硬盤), 必須經過系統調用進入到內核態, 由內核態獲取到系統資源,再切換回用戶態返回應用程序.服務器
出於"readahead cache"和異步寫入等等性能優化的須要, 操做系統在內核態中也增長了一個"內核緩衝區"(kernel buffer). 讀取數據時並非直接把數據讀取到應用程序的buffer, 而先讀取到kernel buffer, 再由kernel buffer複製到應用程序的buffer. 所以,數據在被應用程序使用以前,可能須要被屢次拷貝網絡
再回答這個問題以前, 咱們先來看一個應用場景app
回想現實世界的全部系統中, 不論是web應用服務器, ftp服務器,數據庫服務器, 靜態文件服務器等等, 全部涉及到數據傳輸的場景, 無非就一種:
從硬盤上讀取文件數據, 發送到網絡上去.
這個場景咱們簡化爲一個模型:
File.read(fileDesc, buf, len); Socket.send(socket, buf, len);
爲了方便描述,上面這兩行代碼, 咱們給它起個名字: read-send模型
操做系統在實現這個read-send模型時,須要有如下步驟:
1. 應用程序開始讀文件的操做 2. 應用程序發起系統調用, 從用戶態切換到內核態(第一次上下文切換) 3. 內核態中把數據從硬盤文件讀取到內核中間緩衝區(kernel buf) 4. 數據從內核中間緩衝區(kernel buf)複製到(用戶態)應用程序緩衝區(app buf),從內核態切換回到用戶態(第二次上下文切換) 5. 應用程序開始發送數據到網絡上 6. 應用程序發起系統調用,從用戶態切換到內核態(第三次上下文切換) 7. 內核中把數據從應用程序(app buf)的緩衝區複製到socket的緩衝區(socket) 8. 內核中再把數據從socket的緩衝區(socket buf)發送的網卡的緩衝區(NIC buf)上 9. 從內核態切換回到用戶態(第四次上下文切換)
以下圖表示:
由上圖能夠很清晰地看到, 一次read-send涉及到了四次拷貝:
1. 硬盤拷貝到內核緩衝區(DMA COPY) 2. 內核緩衝區拷貝到應用程序緩衝區(CPU COPY) 3. 應用程序緩衝區拷貝到socket緩衝區(CPU COPY) 4. socket buf拷貝到網卡的buf(DMA COPY)
其中涉及到2次cpu中斷, 還有4次的上下文切換
很明顯,第2次和第3次的的copy只是把數據複製到app buffer又原封不動的複製回來, 爲此帶來了兩次的cpu copy和兩次上下文切換, 是徹底沒有必要的
linux的零拷貝技術就是爲了優化掉這兩次沒必要要的拷貝
linux內核2.1開始引入一個叫sendFile系統調用,這個系統調用能夠在內核態內把數據從內核緩衝區直接複製到套接字(SOCKET)緩衝區內, 從而能夠減小上下文的切換和沒必要要數據的複製
這個系統調用其實就是一個高級I/O函數, 函數簽名以下:
#include<sys/sendfile.h> ssize_t senfile(int out_fd,int in_fd,off_t* offset,size_t count);
有了sendFile這個系統調用後, 咱們read-send模型就能夠簡化爲:
1. 應用程序開始讀文件的操做 2. 應用程序發起系統調用, 從用戶態切換到內核態(第一次上下文切換) 3. 內核態中把數據從硬盤文件讀取到內核中間緩衝區 4. 經過sendFile,在內核態中把數據從內核緩衝區複製到socket的緩衝區 5. 內核中再把數據從socket的緩衝區發送的網卡的buf上 6. 從內核態切換到用戶態(第二次上下文切換)
以下圖所示:
涉及到數據拷貝變成:
1. 硬盤拷貝到內核緩衝區(DMA COPY) 2. 內核緩衝區拷貝到socket緩衝區(CPU COPY) 3. socket緩衝區拷貝到網卡的buf(DMA COPY)
能夠看到,一次read-send模型中, 利用sendFile系統調用後, 能夠將4次數據拷貝減小到3次, 4次上下文切換減小到2次, 2次CPU中斷減小到1次
相對傳統I/O, 這種零拷貝技術經過減小兩次上下文切換, 1次cpu copy, 能夠將I/O性能提升50%以上(網絡數據, 未親測)
開始的術語中說到, 所謂的零拷貝的"零", 是指用戶態和內核態之間的拷貝次數爲0, 從這個定義上來講, 如今的這個零拷貝技術已是真正的"零"了
然而, 對性能追求極致的偉大的科學家和工程師們並不知足於此. 精益求精的他們對中間第2次的cpu copy依舊耿耿於懷, 想盡想方設法要去掉這一次沒有必要的數據拷貝和CPU中斷
在內核2.4之後的版本中, linux內核對socket緩衝區描述符作了優化. 經過此次優化, sendFile系統調用能夠在只複製kernel buffer的少許元信息的基礎上, 把數據直接從kernel buffer 複製到網卡的buffer中去.從而避免了從"內核緩衝區"拷貝到"socket緩衝區"的這一次拷貝.
這個優化後的sendFile, 咱們稱之爲支持scatter-gather特性的sendFile
在支持scatter-gather特性的sendFile的支撐下, 咱們的read-send模型能夠優化爲:
1. 應用程序開始讀文件的操做 2. 應用程序發起系統調用, 從用戶態進入到內核態(第一次上下文切換) 3. 內核態中把數據從硬盤文件讀取到內核中間緩衝區 4. 內核態中把數據在內核緩衝區的位置(offset)和數據大小(size)兩個信息追加(append)到socket的緩衝區中去 5. 網卡的buf上根據socekt緩衝區的offset和size從內核緩衝區中直接拷貝數據 6. 從內核態返回到用戶態(第二次上下文切換)
這個過程以下圖所示:
最後數據拷貝變成只有兩次DMA COPY:
1. 硬盤拷貝到內核緩衝區(DMA COPY) 2. 內核緩衝區拷貝到網卡的buf(DMA COPY)
完美
MMAP(內存映射文件), 是指將文件映射到進程的地址空間去, 實現硬盤上的物理地址跟進程空間的虛擬地址的一一對應關係.
MMAP是另一個用於實現零拷貝的系統調用.跟sendFile不同的地方是, 它是利用共享內存空間的方式, 避免app buf和kernel buf之間的數據拷貝(兩個buf共享同一段內存)
mmap相對於sendFile的好處:
mmap相對於sendFile的缺點:
參考
http://www.javashuo.com/article/p-npewsnuw-bs.html
https://www.jianshu.com/p/e9f422586749
https://www.ibm.com/developerworks/cn/java/j-zerocopy/