Linux 中的零拷貝技術

【轉自】:https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/index.html

Linux 中的直接 I/O

如果應用程序可以直接訪問網絡接口存儲,那么在應用程序訪問數據之前存儲總線就不需要被遍歷,數據傳輸所引起的開銷將會是最小的。應用程序或者運行在用戶模式下的庫函數可以直接訪問硬件設備的存儲,操作系統內核除了進行必要的虛擬存儲配置工作之外,不參與數據傳輸過程中的其它任何事情。直接 I/O 使得數據可以直接在應用程序和外圍設備之間進行傳輸,完全不需要操作系統內核頁緩存的支持。關于直接 I/O 技術的具體實現細節可以參看 developerWorks 上的另一篇文章”Linux 中直接 I/O 機制的介紹” ,本文不做過多描述。

圖 1. 使用直接 I/O 的數據傳輸
圖 1. 使用直接 I/O 的數據傳輸

針對數據傳輸不需要經過應用程序地址空間的零拷貝技術

利用 mmap()

在 Linux 中,減少拷貝次數的一種方法是調用 mmap() 來代替調用 read,比如:

`tmp_buf = mmap(file, len);`

`write(socket, tmp_buf, len);`

首先,應用程序調用了 mmap() 之后,數據會先通過 DMA 拷貝到操作系統內核的緩沖區中去。接著,應用程序跟操作系統共享這個緩沖區,這樣,操作系統內核和應用程序存儲空間就不需要再進行任何的數據拷貝操作。應用程序調用了 write() 之后,操作系統內核將數據從原來的內核緩沖區中拷貝到與 socket 相關的內核緩沖區中。接下來,數據從內核 socket 緩沖區拷貝到協議引擎中去,這是第三次數據拷貝操作。

圖 2. 利用 mmap() 代替 read()
圖 2. 利用 mmap() 代替 read()

通過使用 mmap() 來代替 read(), 已經可以減半操作系統需要進行數據拷貝的次數。當大量數據需要傳輸的時候,這樣做就會有一個比較好的效率。但是,這種改進也是需要代價的,使用 mma()p 其實是存在潛在的問題的。當對文件進行了內存映射,然后調用 write() 系統調用,如果此時其他的進程截斷了這個文件,那么 write() 系統調用將會被總線錯誤信號 SIGBUS 中斷,因為此時正在執行的是一個錯誤的存儲訪問。這個信號將會導致進程被殺死,解決這個問題可以通過以下這兩種方法:

  1. 為 SIGBUS 安裝一個新的信號處理器,這樣,write() 系統調用在它被中斷之前就返回已經寫入的字節數目,errno 會被設置成 success。但是這種方法也有其缺點,它不能反映出產生這個問題的根源所在,因為 BIGBUS 信號只是顯示某進程發生了一些很嚴重的錯誤。
  2. 第二種方法是通過文件租借鎖來解決這個問題的,這種方法相對來說更好一些。我們可以通過內核對文件加讀或者寫的租借鎖,當另外一個進程嘗試對用戶正在進行傳輸的文件進行截斷的時候,內核會發送給用戶一個實時信號:RT_SIGNAL_LEASE 信號,這個信號會告訴用戶內核破壞了用戶加在那個文件上的寫或者讀租借鎖,那么 write() 系統調用則會被中斷,并且進程會被 SIGBUS 信號殺死,返回值則是中斷前寫的字節數,errno 也會被設置為 success。文件租借鎖需要在對文件進行內存映射之前設置。

使用 mmap 是 POSIX 兼容的,但是使用 mmap 并不一定能獲得理想的數據傳輸性能。數據傳輸的過程中仍然需要一次 CPU 拷貝操作,而且映射操作也是一個開銷很大的虛擬存儲操作,這種操作需要通過更改頁表以及沖刷 TLB (使得 TLB 的內容無效)來維持存儲的一致性。但是,因為映射通常適用于較大范圍,所以對于相同長度的數據來說,映射所帶來的開銷遠遠低于 CPU 拷貝所帶來的開銷。

sendfile()

為了簡化用戶接口,同時還要繼續保留 mmap()/write() 技術的優點:減少 CPU 的拷貝次數,Linux 在版本 2.1 中引入了 sendfile() 這個系統調用。

sendfile() 不僅減少了數據拷貝操作,它也減少了上下文切換。首先:sendfile() 系統調用利用 DMA 引擎將文件中的數據拷貝到操作系統內核緩沖區中,然后數據被拷貝到與 socket 相關的內核緩沖區中去。接下來,DMA 引擎將數據從內核 socket 緩沖區中拷貝到協議引擎中去。如果在用戶調用 sendfile () 系統調用進行數據傳輸的過程中有其他進程截斷了該文件,那么 sendfile () 系統調用會簡單地返回給用戶應用程序中斷前所傳輸的字節數,errno 會被設置為 success。如果在調用 sendfile() 之前操作系統對文件加上了租借鎖,那么 sendfile() 的操作和返回狀態將會和 mmap()/write () 一樣。

圖 3. 利用 sendfile () 進行數據傳輸
圖 3. 利用 sendfile () 進行數據傳輸

sendfile() 系統調用不需要將數據拷貝或者映射到應用程序地址空間中去,所以 sendfile() 只是適用于應用程序地址空間不需要對所訪問數據進行處理的情況。相對于 mmap() 方法來說,因為 sendfile 傳輸的數據沒有越過用戶應用程序 / 操作系統內核的邊界線,所以 sendfile () 也極大地減少了存儲管理的開銷。但是,sendfile () 也有很多局限性,如下所列:

  • sendfile() 局限于基于文件服務的網絡應用程序,比如 web 服務器。據說,在 Linux 內核中實現 sendfile() 只是為了在其他平臺上使用 sendfile() 的 Apache 程序。
  • 由于網絡傳輸具有異步性,很難在 sendfile () 系統調用的接收端進行配對的實現方式,所以數據傳輸的接收端一般沒有用到這種技術。
  • 基于性能的考慮來說,sendfile () 仍然需要有一次從文件到 socket 緩沖區的 CPU 拷貝操作,這就導致頁緩存有可能會被傳輸的數據所污染。

帶有 DMA 收集拷貝功能的 sendfile()

上小節介紹的 sendfile() 技術在進行數據傳輸仍然還需要一次多余的數據拷貝操作,通過引入一點硬件上的幫助,這僅有的一次數據拷貝操作也可以避免。為了避免操作系統內核造成的數據副本,需要用到一個支持收集操作的網絡接口,這也就是說,待傳輸的數據可以分散在存儲的不同位置上,而不需要在連續存儲中存放。這樣一來,從文件中讀出的數據就根本不需要被拷貝到 socket 緩沖區中去,而只是需要將緩沖區描述符傳到網絡協議棧中去,之后其在緩沖區中建立起數據包的相關結構,然后通過 DMA 收集拷貝功能將所有的數據結合成一個網絡數據包。網卡的 DMA 引擎會在一次操作中從多個位置讀取包頭和數據。Linux 2.4 版本中的 socket 緩沖區就可以滿足這種條件,這也就是用于 Linux 中的眾所周知的零拷貝技術,這種方法不但減少了因為多次上下文切換所帶來開銷,同時也減少了處理器造成的數據副本的個數。對于用戶應用程序來說,代碼沒有任何改變。首先,sendfile() 系統調用利用 DMA 引擎將文件內容拷貝到內核緩沖區去;然后,將帶有文件位置和長度信息的緩沖區描述符添加到 socket 緩沖區中去,此過程不需要將數據從操作系統內核緩沖區拷貝到 socket 緩沖區中,DMA 引擎會將數據直接從內核緩沖區拷貝到協議引擎中去,這樣就避免了最后一次數據拷貝。

圖 4. 帶有 DMA 收集拷貝功能的 sendfile
圖 4. 帶有 DMA 收集拷貝功能的 sendfile

通過這種方法,CPU 在數據傳輸的過程中不但避免了數據拷貝操作,理論上,CPU 也永遠不會跟傳輸的數據有任何關聯,這對于 CPU 的性能來說起到了積極的作用:首先,高速緩沖存儲器沒有受到污染;其次,高速緩沖存儲器的一致性不需要維護,高速緩沖存儲器在 DMA 進行數據傳輸前或者傳輸后不需要被刷新。然而實際上,后者實現起來非常困難。源緩沖區有可能是頁緩存的一部分,這也就是說一般的讀操作可以訪問它,而且該訪問也可以是通過傳統方式進行的。只要存儲區域可以被 CPU 訪問到,那么高速緩沖存儲器的一致性就需要通過 DMA 傳輸之前沖刷新高速緩沖存儲器來維護。而且,這種數據收集拷貝功能的實現是需要硬件以及設備驅動程序支持的。

splice()

splice() 是  Linux  中與 mmap() 和  sendfile() 類似的一種方法。它也可以用于用戶應用程序地址空間和操作系統地址空間之間的數據傳輸。splice() 適用于可以確定數據傳輸路徑的用戶應用程序,它不需要利用用戶地址空間的緩沖區進行顯式的數據傳輸操作。那么,當數據只是從一個地方傳送到另一個地方,過程中所傳輸的數據不需要經過用戶應用程序的處理的時候,spice() 就成為了一種比較好的選擇。splice() 可以在操作系統地址空間中整塊地移動數據,從而減少大多數數據拷貝操作。而且,splice() 進行數據傳輸可以通過異步的方式來進行,用戶應用程序可以先從系統調用返回,而操作系統內核進程會控制數據傳輸過程繼續進行下去。splice() 可以被看成是類似于基于流的管道的實現,管道可以使得兩個文件描述符相互連接,splice 的調用者則可以控制兩個設備(或者協議棧)在操作系統內核中的相互連接。

splice() 系統調用和 sendfile() 非常類似,用戶應用程序必須擁有兩個已經打開的文件描述符,一個用于表示輸入設備,一個用于表示輸出設備。與 sendfile() 不同的是,splice() 允許任意兩個文件之間互相連接,而并不只是文件到 socket 進行數據傳輸。對于從一個文件描述符發送數據到 socket 這種特例來說,一直都是使用 sendfile() 這個系統調用,而 splice 一直以來就只是一種機制,它并不僅限于 sendfile() 的功能。也就是說,sendfile() 只是 splice() 的一個子集,在 Linux 2.6.23 中,sendfile() 這種機制的實現已經沒有了,但是這個 API 以及相應的功能還存在,只不過 API 以及相應的功能是利用了 splice() 這種機制來實現的。

在數據傳輸的過程中,splice() 機制交替地發送相關的文件描述符的讀寫操作,并且可以將讀緩沖區重新用于寫操作。它也利用了一種簡單的流控制,通過預先定義的水印( watermark )來阻塞寫請求。有實驗表明,利用這種方法將數據從一個磁盤傳輸到另一個磁盤會增加 30% 到 70% 的吞吐量,數據傳輸的過程中, CPU 的負載也會減少一半。

Linux 2.6.17 內核引入了 splice() 系統調用,但是,這個概念在此之前 ] 其實已經存在了很長一段時間了。1988 年,Larry McVoy 提出了這個概念,它被看成是一種改進服務器端系統的 I/O 性能的一種技術,盡管在之后的若干年中經常被提及,但是 splice 系統調用從來沒有在主流的 Linux 操作系統內核中實現過,一直到 Linux 2.6.17 版本的出現。splice 系統調用需要用到四個參數,其中兩個是文件描述符,一個表示文件長度,還有一個用于控制如何進行數據拷貝。splice 系統調用可以同步實現,也可以使用異步方式來實現。在使用異步方式的時候,用戶應用程序會通過信號 SIGIO 來獲知數據傳輸已經終止。splice() 系統調用的接口如下所示:

long splice(int fdin, int fdout, size_t len, unsigned int flags);

調用 splice() 系統調用會導致操作系統內核從數據源 fdin 移動最多 len 個字節的數據到 fdout 中去,這個數據的移動過程只是經過操作系統內核空間,需要最少的拷貝次數。使用 splice() 系統調用需要這兩個文件描述符中的一個必須是用來表示一個管道設備的。不難看出,這種設計具有局限性,Linux 的后續版本針對這一問題將會有所改進。參數 flags 用于表示拷貝操作的執行方法,當前的 flags 有如下這些取值:

  • SPLICE_F_NONBLOCK:splice 操作不會被阻塞。然而,如果文件描述符沒有被設置為不可被阻塞方式的 I/O ,那么調用 splice 有可能仍然被阻塞。
  • SPLICE_F_MORE:告知操作系統內核下一個 splice 系統調用將會有更多的數據傳來。
  • SPLICE_F_MOVE:如果輸出是文件,這個值則會使得操作系統內核嘗試從輸入管道緩沖區直接將數據讀入到輸出地址空間,這個數據傳輸過程沒有任何數據拷貝操作發生。

Splice() 系統調用利用了 Linux 提出的管道緩沖區( pipe buffer )機制,這就是為什么這個系統調用的兩個文件描述符參數中至少有一個必須要指代管道設備的原因。為了支持 splice 這種機制,Linux 在用于設備和文件系統的 file_operations 結構中增加了下邊這兩個定義:

`ssize_t (*splice_write)(struct inode *pipe, strucuct file *out,`

`size_t len, unsigned int flags);`

`ssize_t (*splice_read)(struct inode *in, strucuct file *pipe,`

`size_t len, unsigned int flags);`

這兩個新的操作可以根據 flags 的設定在 pipe 和 in 或者 out 之間移動 len 個字節。Linux 文件系統已經實現了具有上述功能并且可以使用的操作,而且還實現了一個 generic_splice_sendpage() 函數用于和 socket 之間的接合。

對應用程序地址空間和內核之間的數據傳輸進行優化的零拷貝技術

前面提到的幾種零拷貝技術都是通過盡量避免用戶應用程序和操作系統內核緩沖區之間的數據拷貝來實現的,使用上面那些零拷貝技術的應用程序通常都要局限于某些特殊的情況:要么不能在操作系統內核中處理數據,要么不能在用戶地址空間中處理數據。而這一小節提出的零拷貝技術保留了傳統在用戶應用程序地址空間和操作系統內核地址空間之間傳遞數據的技術,但卻在傳輸上進行優化。我們知道,數據在系統軟件和硬件之間的傳遞可以通過 DMA 傳輸來提高效率,但是對于用戶應用程序和操作系統之間進行數據傳輸這種情況來說,并沒有類似的工具可以使用。本節介紹的技術就是針對這種情況提出來的。

利用寫時復制

在某些情況下,Linux 操作系統內核中的頁緩存可能會被多個應用程序所共享,操作系統有可能會將用戶應用程序地址空間緩沖區中的頁面映射到操作系統內核地址空間中去。如果某個應用程序想要對這共享的數據調用  write() 系統調用,那么它就可能破壞內核緩沖區中的共享數據,傳統的 write() 系統調用并沒有提供任何顯示的加鎖操作,Linux 中引入了寫時復制這樣一種技術用來保護數據。

什么是寫時復制

寫時復制是計算機編程中的一種優化策略,它的基本思想是這樣的:如果有多個應用程序需要同時訪問同一塊數據,那么可以為這些應用程序分配指向這塊數據的指針,在每一個應用程序看來,它們都擁有這塊數據的一份數據拷貝,當其中一個應用程序需要對自己的這份數據拷貝進行修改的時候,就需要將數據真正地拷貝到該應用程序的地址空間中去,也就是說,該應用程序擁有了一份真正的私有數據拷貝,這樣做是為了避免該應用程序對這塊數據做的更改被其他應用程序看到。這個過程對于應用程序來說是透明的,如果應用程序永遠不會對所訪問的這塊數據進行任何更改,那么就永遠不需要將數據拷貝到應用程序自己的地址空間中去。這也是寫時復制的最主要的優點。

寫時復制的實現需要 MMU 的支持,MMU 需要知曉進程地址空間中哪些特殊的頁面是只讀的,當需要往這些頁面中寫數據的時候,MMU 就會發出一個異常給操作系統內核,操作系統內核就會分配新的物理存儲空間,即將被寫入數據的頁面需要與新的物理存儲位置相對應。

寫時復制的最大好處就是可以節約內存。不過對于操作系統內核來說,寫時復制增加了其處理過程的復雜性。

數據傳輸的實現及其局限性

數據發送端

對于數據傳輸的發送端來說,實現相對來說是比較簡單的,對與應用程序緩沖區相關的物理頁面進行加鎖,并將這些頁面映射到操作系統內核的地址空間,并標識為“ write only ”。當系統調用返回的時候,用戶應用程序和網絡堆棧就都可以讀取該緩沖區中的數據。在操作系統已經傳送完所有的數據之后,應用程序就可以對這些數據進行寫操作。如果應用程序嘗試在數據傳輸完成之前對數據進行寫操作,那么就會產生異常,這個時候操作系統就會將數據拷貝到應用程序自己的緩沖區中去,并且重置應用程序端的映射。數據傳輸完成之后,對加鎖的頁面進行解鎖操作,并重置 COW 標識。

數據接收端

對于數據接收端來說,該技術的實現則需要處理復雜得多的情況。如果 read() 系統調用是在數據包到達之前發出的,并且應用程序是被阻塞的,那么 read() 系統調用就會告知操作系統接收到的數據包中的數據應該存放到什么地方去。在這種情況下,根本沒有必要進行頁面重映射,網絡接口卡可以提供足夠的支持讓數據直接存入用戶應用程序的緩沖區中去。如果數據接收是異步的,在 read() 系統調用發出之前,操作系統不知道該把數據寫到哪里,因為它不知道用戶應用程序緩沖區的位置,所以操作系統內核必須要先把數據存放到自己的緩沖區中去。

局限性

寫時復制技術有可能會導致操作系統的處理開銷很大.所有相關的緩沖區都必須要進行頁對齊處理,并且使用的 MMU 頁面一定要是整數個的。對于發送端來說,這不會造成什么問題。但是對于接收端來說,它需要有能力處理更加復雜的情況。首先,數據包的尺寸大小要合適,大小需要恰到好處能夠覆蓋一整頁的數據,這就限制了那些 MTU 大小大于系統內存頁的網絡,比如 FDDI 和 ATM。其次,為了在沒有任何中斷的情況下將頁面重映射到數據包的流,數據包中的數據部分必須占用整數個頁面。對于異步接收數據的情況來說,為了將數據高效地移動到用戶地址空間中去,可以使用這樣一種方法:利用網絡接口卡的支持,傳來的數據包可以被分割成包頭和數據兩部分,數據被存放在一個單獨的緩沖區內,虛擬存儲系統然后就會將數據映射到用戶地址空間緩沖區去。使用這種方法需要滿足兩個先決條件,也就是上面提到過的:一是應用程序緩沖區必須是頁對齊的,并且在虛擬存儲上是連續的;二是傳來的數據有一頁大小的時候才可以對數據包進行分割。事實上,這兩個先決條件是很難滿足的。如果應用程序緩沖區不是頁對齊的,或者數據包的大小超過一個頁,那么數據就需要被拷貝。對于數據發送端來說,就算數據在傳輸的過程中對于應用程序來說是寫保護的,應用程序仍然需要避免使用這些忙緩沖區,這是因為寫時拷貝操作所帶來的開銷是很大的。如果沒有端到端這一級別的通知,那么應用程序很難會知道某緩沖區是否已經被釋放還是仍然在被占用。

這種零拷貝技術比較適用于那種寫時復制事件發生比較少的情況,因為寫時復制事件所產生的開銷要遠遠高于一次 CPU 拷貝所產生的開銷。實際情況中,大多數應用程序通常都會多次重復使用相同的緩沖區,所以,一次使用完數據之后,不要從操作系統地址空間解除頁面的映射,這樣會提高效率。考慮到同樣的頁面可能會被再次訪問,所以保留頁面的映射可以節省管理開銷,但是,這種映射保留不會減少由于頁表往返移動和 TLB 沖刷所帶來的開銷,這是因為每次頁面由于寫時復制而進行加鎖或者解鎖的時候,頁面的只讀標志都要被更改。

緩沖區共享

還有另外一種利用預先映射機制的共享緩沖區的方法也可以在應用程序地址空間和操作系統內核之間快速傳輸數據。采用緩沖區共享這種思想的架構最先在 Solaris 上實現,該架構使用了“ fbufs ”這個概念。這種方法需要修改 API。應用程序地址空間和操作系統內核地址空間之間的數據傳遞需要嚴格按照 fbufs 體系結構來實現,操作系統內核之間的通信也是嚴格按照 fbufs 體系結構來完成的。每一個應用程序都有一個緩沖區池,這個緩沖區池被同時映射到用戶地址空間和內核地址空間,也可以在必要的時候才創建它們。通過完成一次虛擬存儲操作來創建緩沖區,fbufs 可以有效地減少由存儲一致性維護所引起的大多數性能問題。該技術在 Linux 中還停留在實驗階段。

為什么要擴展 Linux I/O API

傳統的 Linux 輸入輸出接口,比如讀和寫系統調用,都是基于拷貝的,也就是說,數據需要在操作系統內核和應用程序定義的緩沖區之間進行拷貝。對于讀系統調用來說,用戶應用程序呈現給操作系統內核一個預先分配好的緩沖區,內核必須把讀進來的數據放到這個緩沖區內。對于寫系統調用來說,只要系統調用返回,用戶應用程序就可以自由重新利用數據緩沖區。

為了支持上面這種機制,Linux 需要能夠為每一個操作都進行建立和刪除虛擬存儲映射。這種頁面重映射的機制依賴于機器配置、cache 體系結構、TLB 未命中處理所帶來的開銷以及處理器是單處理器還是多處理器等多種因素。如果能夠避免處理 I/O 請求的時候虛擬存儲 / TLB 操作所產生的開銷,則會極大地提高 I/O 的性能。fbufs 就是這樣一種機制。使用 fbufs 體系結構就可以避免虛擬存儲操作。由數據顯示,fbufs 這種結構在 DECStation? 5000/200 這個單處理器工作站上會取得比上面提到的頁面重映射方法好得多的性能。如果要使用 fbufs 這種體系結構,必須要擴展 Linux API,從而實現一種有效而且全面的零拷貝技術。

快速緩沖區( Fast Buffers )原理介紹

I/O 數據存放在一些被稱作 fbufs 的緩沖區內,每一個這樣的緩沖區都包含一個或者多個連續的虛擬存儲頁。應用程序訪問 fbuf 是通過保護域來實現的,有如下這兩種方式:

  • 如果應用程序分配了 fbuf,那么應用程序就有訪問該 fbuf 的權限
  • 如果應用程序通過 IPC 接收到了 fbuf,那么應用程序對這個 fbuf 也有訪問的權限

對于第一種情況來說,這個保護域被稱作是 fbuf 的“ originator ”;對于后一種情況來說,這個保護域被稱作是 fbuf 的“ receiver ”。

傳統的 Linux I/O 接口支持數據在應用程序地址空間和操作系統內核之間交換,這種交換操作導致所有的數據都需要進行拷貝。如果采用 fbufs 這種方法,需要交換的是包含數據的緩沖區,這樣就消除了多余的拷貝操作。應用程序將 fbuf 傳遞給操作系統內核,這樣就能減少傳統的 write 系統調用所產生的數據拷貝開銷。同樣的,應用程序通過 fbuf 來接收數據,這樣也可以減少傳統 read 系統調用所產生的數據拷貝開銷。如下圖所示:

圖 5. Linux I/O API
圖 5. Linux I/O API

I/O 子系統或者應用程序都可以通過 fbufs 管理器來分配 fbufs。一旦分配了 fbufs,這些 fbufs 就可以從程序傳遞到 I/O 子系統,或者從 I/O 子系統傳遞到程序。使用完后,這些 fbufs 會被釋放回 fbufs 緩沖區池。

fbufs 在實現上有如下這些特性,如圖 9 所示:

  • fbuf 需要從 fbufs 緩沖區池里分配。每一個 fbuf 都存在一個所屬對象,要么是應用程序,要么是操作系統內核。fbuf 可以在應用程序和操作系統之間進行傳遞,fbuf 使用完之后需要被釋放回特定的 fbufs 緩沖區池,在 fbuf 傳遞的過程中它們需要攜帶關于 fbufs 緩沖區池的相關信息。
  • 每一個 fbufs 緩沖區池都會和一個應用程序相關聯,一個應用程序最多只能與一個 fbufs 緩沖區池相關聯。應用程序只有資格訪問它自己的緩沖區池。
  • fbufs 不需要虛擬地址重映射,這是因為對于每個應用程序來說,它們可以重新使用相同的緩沖區集合。這樣,虛擬存儲轉換的信息就可以被緩存起來,虛擬存儲子系統方面的開銷就可以消除。
  • I/O 子系統(設備驅動程序,文件系統等)可以分配 fbufs,并將到達的數據直接放到這些 fbuf 里邊。這樣,緩沖區之間的拷貝操作就可以避免。
圖 6. fbufs 體系結構
圖 6. fbufs 體系結構

前面提到,這種方法需要修改 API,如果要使用 fbufs 體系結構,應用程序和 Linux 操作系統內核驅動程序都需要使用新的 API,如果應用程序要發送數據,那么它就要從緩沖區池里獲取一個 fbuf,將數據填充進去,然后通過文件描述符將數據發送出去。接收到的 fbufs 可以被應用程序保留一段時間,之后,應用程序可以使用它繼續發送其他的數據,或者還給緩沖區池。但是,在某些情況下,需要對數據包內的數據進行重新組裝,那么通過 fbuf 接收到數據的應用程序就需要將數據拷貝到另外一個緩沖區內。再者,應用程序不能對當前正在被內核處理的數據進行修改,基于這一點,fbufs 體系結構引入了強制鎖的概念以保證其實現。對于應用程序來說,如果 fbufs 已經被發送給操作系統內核,那么應用程序就不會再處理這些 fbufs。

fbufs 存在的一些問題

管理共享緩沖區池需要應用程序、網絡軟件、以及設備驅動程序之間的緊密合作。對于數據接收端來說,網絡硬件必須要能夠將到達的數據包利用 DMA 傳輸到由接收端分配的正確的存儲緩沖區池中去。而且,應用程序稍微不注意就會更改之前發到共享存儲中的數據的內容,從而導致數據被破壞,但是這種問題在應用程序端是很難調試的。同時,共享存儲這種模型很難與其他類型的存儲對象關聯使用,但是應用程序、網絡軟件以及設備驅動程序之間的緊密合作是需要其他存儲管理器的支持的。對于共享緩沖區這種技術來說,雖然這種技術看起來前景光明,但是這種技術不但需要對 API 進行更改,而且需要對驅動程序也進行更改,并且這種技術本身也存在一些未解決的問題,這就使得這種技術目前還只是出于試驗階段。在測試系統中,這種技術在性能上有很大的改進,不過這種新的架構的整體安裝目前看起來還是不可行的。這種預先分配共享緩沖區的機制有時也因為粒度問題需要將數據拷貝到另外一個緩沖區中去。

總結

本系列文章介紹了 Linux 中的零拷貝技術,本文是其中的第二部分。本文對第一部分文章中提出的 Linux 操作系統上出現的幾種零拷貝技術進行了更詳細的介紹,主要描述了它們各自的優點,缺點以及適用場景。對于網絡數據傳輸來說,零拷貝技術的應用受到了很多體系結構方面因素的阻礙,包括虛擬存儲體系結構以及網絡協議體系結構等。所以,零拷貝技術仍然只是在某些很特殊的情況中才可以應用,比如文件服務或者使用某種特殊的協議進行高帶寬的通信等。但是,零拷貝技術在磁盤操作中的應用的可行性就高得多了,這很可能是因為磁盤操作具有同步的特點,以及數據傳輸單元是按照頁的粒度來進行的。

針對 Linux 操作系統平臺提出并實現了很多種零拷貝技術,但是并不是所有這些零拷貝技術都被廣泛應用于現實中的操作系統中的。比如,fbufs 體系結構,它在很多方面看起來都很吸引人,但是使用它需要更改 API 以及驅動程序,它還存在其他一些實現上的困難,這就使得 fbufs 還只是停留在實驗的階段。動態地址重映射技術只是需要對操作系統做少量修改,雖然不需要修改用戶軟件,但是當前的虛擬存儲體系結構并不能很好地支持頻繁的虛擬地址重映射操作。而且為了保證存儲的一致性,重映射之后還必須對 TLB 和一級緩存進行刷新。事實上,利用地址重映射實現的零拷貝技術適用的范圍是很小的,這是因為虛擬存儲操作所帶來的開銷往往要比 CPU 拷貝所產生的開銷還要大。此外,為了完全消除 CPU 訪問存儲,通常都需要額外的硬件來支持,而這種硬件的支持并不是很普及,同時也是非常昂貴的。

本系列文章的目的是想幫助讀者理清這些出現在 Linux 操作系統中的零拷貝技術都是從何種角度來幫助改善數據傳輸過程中遇到的性能問題的。關于各種零拷貝技術的具體實現細節,本系列文章沒有做詳細描述。同時,零拷貝技術一直是在不斷地發展和完善當中的,本系列文章并沒有涵蓋 Linux 上出現的所有零拷貝技術。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容