Linux下的零拷貝

Reference:  https://segmentfault.com/a/1190000011989008segmentfault

 

零拷貝是什麼?

維基百科對「零拷貝」是這樣描述的:緩存

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. This is frequently used to save CPU cycles and memory bandwidth when transmitting a file over a network.網絡

「零拷貝」 描述的是CPU不執行拷貝數據從一塊內存區域到另外一塊區域的任務的計算機操做。它一般用於在網絡上傳輸文件時節省CPU週期和內存帶寬。簡單來講,零拷貝就是一種避免 CPU 將數據從一塊存儲拷貝到另一塊存儲的技術。異步

爲何須要零拷貝技術?

一般咱們會有這樣的需求:將本地磁盤上的一個文件經過網絡發送給遠端的另外一個服務。在傳統的I/O中,咱們經過一張圖來看一下操做系統都會發生什麼:
圖片描述socket

  1. 發出read()系統調用,這時處理器會從用戶空間切換至內核空間;性能

  2. 向磁盤請求數據;spa

  3. 經過DMA將文件從磁盤上讀取到內核空間緩衝區;操作系統

  4. read()系統調用返回,將數據從內核空間緩衝區拷貝至用戶空間緩衝區,這時候處理器會從內核空間切換至用戶空間;orm

  5. 發出write()系統調用,並將數據從用戶空間緩衝區拷貝至目標socket 在內核空間的緩衝區,這時候處理器會從用戶空間切換至內核空間;blog

  6. write()調用返回;

  7. 經過DMA將數據從內核空間緩衝區中拷貝至協議引擎(該操做是獨立且異步的)。

總的來講:傳統的I/O操做在整個過程當中將會產生4次上下文切換和4次數據拷貝。

Q:有人可能會問, 爲何write()調用會先返回,難道他會在數據傳輸前返回?
A:事實上調用的返回並不保證數據被傳輸,甚至他並不保證傳輸的開始,只是意味着以太網驅動程序在其傳輸隊列中有空位,並已經接受咱們的將要傳輸的數據。在咱們以前極可能還有不少數據包在排除。除非驅動程序或硬件實現優先級環或隊列,不然數據都將以先進先出的方式被傳輸。

瞭解了傳統I/O的操做後,咱們再來觀察一下整個過程,咱們會注意到第二次和第三次數據拷貝是徹底沒有意義的,應用程序僅僅緩存了一下數據就又原封不動的把它發送給了目標socket 緩衝區。並且這兩次拷貝是須要CPU全程參與的,從操做系統的角度來講,若是 CPU 一直被佔用着去執行這項簡單的任務,那麼這將會是很浪費資源的;若是有其餘比較簡單的系統部件能夠代勞這件事情,從而使得 CPU 解脫出來能夠作其餘的事情,那麼系統資源的利用則會更加有效。

「零拷貝」正是經過消除這些多餘的拷貝來提高性能的。在數據傳輸的過程當中,避免數據在內核空間緩衝區和用戶空間緩衝區之間進行拷貝,以及數據在內核空間緩衝區內的CPU拷貝。


零拷貝的實現機制

Linux 中提供相似的系統調用主要有 sendfile()、mmap() 和splice()(本文對該系統調用暫不作討論)。

經過sendfile()實現的零拷貝

sendfile系統調用在內核版本2.1中被引入,目的是簡化經過網絡在兩個本地文件之間進行的數據傳輸過程。sendfile系統調用的引入,不只減小了數據複製,還減小了上下文切換的次數。爲了更好的說明,請看下圖:
圖片描述

  1. 發出sendfile()系統調用,這時處理器會從用戶空間切換至內核空間;

  2. 向磁盤請求數據;

  3. 經過DMA將文件從磁盤上讀取到內核空間緩衝區;

  4. 將數據從內核空間緩衝區拷貝到目標socket緩衝區;

  5. Sendfile()返回,這時處理器從內核空間切換至用戶空間;

  6. 經過DMA將數據從目標socket緩衝區拷貝至協議引擎。

總結一下這種實現,整個過程產生了2次上下文切換和3次數據拷貝(其中2次DMA拷貝和1次CPU拷貝)。

該實現雖然減小了2次上下文切換,但仍然還有1次CPU拷貝。那此次拷貝是否是也能夠省掉呢?答案是確定的。可是須要底層操做系統的一些支持。那就是帶有DMA收集功能的sendfile實現的零拷貝。

帶有DMA收集功能的sendfile實現的零拷貝

從Linux2.4開始,操做系統底層提供了帶有scatter/gather的DMA來從內核空間緩衝區中將數據讀取到協議引擎中。這就意味着等待傳輸的數據不須要在連續存儲器中,它能夠分散在不一樣的內存位置。那這樣一來,從文件中讀出的數據就沒必要拷貝至目標socket的緩衝區中,只須要將緩衝區描述符添加到目標socket的緩衝區中,DMA收集操做會根據緩衝區描述符中的信息將內核空間緩衝區中的數據讀取到協議引擎。這種方法不只減小了上下文切換、還減小了由CPU參與的數據拷貝。爲了更好的理解這種方法所涉及的操做,請看下圖:

圖片描述

  1. 發出sendfile()系統調用,處理器從用戶空間切換至內核空間;

  2. 經過DMA將數據copy至內核空間緩衝區;

  3. 將數據在內核空間緩衝區的地址和偏移量拷貝至目標socket的緩衝區;

  4. Sendfile()返回,處理器從內核空間切換至用戶空間。

  5. 帶有scatter/gather 功能的DMA將數據直接從內核緩衝區讀取到協議引擎,從而消除了最後一次CPU拷貝。

總結一下,這種方法產生了2次上下文切換和2次數據拷貝。

這時有人可能會問,若是我把數據從磁盤上讀出來後,再編輯一下,再發送出去,以上所說的零拷貝豈不是不能實現?
對於該問題,Linux提供了mmap來實現。

經過mmap實現的零拷貝

mmap(內存映射):mmap操做提供了一種機制,讓用戶程序直接訪問設備內存,這種機制,相比較在用戶空間和內核空間互相拷貝數據,效率更高。

圖片描述

  1. 發出mmap()系統調用,處理器從用戶空間切換至內核空間。

  2. 向磁盤請求數據;

  3. 經過DMA將數據從磁盤拷貝至內核空間緩衝區;

  4. mmap()調用返回,這時候用戶程序和操做系統共享這個緩衝區,不須要再將數據從kernel buffer 拷貝至 user buffer,處理器從內核空間切換至用戶空間;

  5. 用戶邏輯處理;

  6. 發出write()系統調用,將數據從內核空間緩衝區拷貝至目標socket緩衝區,這時處理器從用戶空間切換至內核空間;

  7. write()調用返回,處理器從內核空間切換至用戶空間;

  8. 經過DMA將數據拷貝至協議引擎。

總結一下:這種方法將產生4次上下文切換和3次數據拷貝。

至此,零拷貝技術就介紹完了。本文所說起的零拷貝技術都是須要底層操做系統支持的,同時,零拷貝技術一直是在不斷地發展和完善當中的,本文並無涵蓋 Linux 上出現的全部零拷貝技術。

相關文章
相關標籤/搜索