1. 程式人生 > >Linux零拷貝技術,看完這篇文章就懂了

Linux零拷貝技術,看完這篇文章就懂了

本文首發於我的公眾號 Linux雲端計算網路(id: cloud_dev),專注於乾貨分享,號內有 10T 書籍和視訊資源,後臺回覆 「1024」 即可領取,歡迎大家關注,二維碼文末可以掃。

本文講解 Linux 的零拷貝技術,雲端計算是一門很龐大的技術學科,融合了很多技術,Linux 算是比較基礎的技術,所以,學好 Linux 對於雲端計算的學習會有比較大的幫助。

本文借鑑並總結了幾種比較常見的 Linux 下的零拷貝技術,相關的引用連結見文後,大家如果覺得本文總結得太抽象,可以轉到連結看詳細解釋。

為什麼需要零拷貝


傳統的 Linux 系統的標準 I/O 介面(read、write)是基於資料拷貝的,也就是資料都是 copy_to_user 或者 copy_from_user,這樣做的好處是,通過中間快取的機制,減少磁碟 I/O 的操作,但是壞處也很明顯,大量資料的拷貝,使用者態和核心態的頻繁切換,會消耗大量的 CPU 資源,嚴重影響資料傳輸的效能,有資料表明,在Linux核心協議棧中,這個拷貝的耗時甚至佔到了資料包整個處理流程的57.1%。

什麼是零拷貝


零拷貝就是這個問題的一個解決方案,通過儘量避免拷貝操作來緩解 CPU 的壓力。Linux 下常見的零拷貝技術可以分為兩大類:一是針對特定場景,去掉不必要的拷貝;二是去優化整個拷貝的過程。由此看來,零拷貝並沒有真正做到“0”拷貝,它更多是一種思想,很多的零拷貝技術都是基於這個思想去做的優化。

零拷貝的幾種方法


原始資料拷貝操作


在介紹之前,先看看 Linux 原始的資料拷貝操作是怎樣的。如下圖,假如一個應用需要從某個磁碟檔案中讀取內容通過網路發出去,像這樣:

while((n = read(diskfd, buf, BUF_SIZE)) > 0)

write(sockfd, buf , n);

那麼整個過程就需要經歷:1)read 將資料從磁碟檔案通過 DMA 等方式拷貝到核心開闢的緩衝區;2)資料從核心緩衝區複製到使用者態緩衝區;3)write 將資料從使用者態緩衝區複製到核心協議棧開闢的 socket 緩衝區;4)資料從 socket 緩衝區通過 DMA 拷貝到網絡卡上發出去。

可見,整個過程發生了至少四次資料拷貝,其中兩次是 DMA 與硬體通訊來完成,CPU 不直接參與,去掉這兩次,仍然有兩次 CPU 資料拷貝操作。

方法一:使用者態直接 I/O


這種方法可以使應用程式或者執行在使用者態下的庫函式直接訪問硬體裝置,資料直接跨過核心進行傳輸,核心在整個資料傳輸過程除了會進行必要的虛擬儲存配置工作之外,不參與其他任何工作,這種方式能夠直接繞過核心,極大提高了效能。

缺陷:

1)這種方法只能適用於那些不需要核心緩衝區處理的應用程式,這些應用程式通常在程序地址空間有自己的資料快取機制,稱為自快取應用程式,如資料庫管理系統就是一個代表。

2)這種方法直接操作磁碟 I/O,由於 CPU 和磁碟 I/O 之間的執行時間差距,會造成資源的浪費,解決這個問題需要和非同步 I/O 結合使用。

方法二:mmap


這種方法,使用 mmap 來代替 read,可以減少一次拷貝操作,如下:

buf = mmap(diskfd, len);

write(sockfd, buf, len);

應用程式呼叫 mmap ,磁碟檔案中的資料通過 DMA 拷貝到核心緩衝區,接著作業系統會將這個緩衝區與應用程式共享,這樣就不用往使用者空間拷貝。應用程式呼叫write ,作業系統直接將資料從核心緩衝區拷貝到 socket 緩衝區,最後再通過 DMA 拷貝到網絡卡發出去。

缺陷:

1)mmap 隱藏著一個陷阱,當 mmap 一個檔案時,如果這個檔案被另一個程序所截獲,那麼 write 系統呼叫會因為訪問非法地址被 SIGBUS 訊號終止,SIGBUS 預設會殺死程序併產生一個 coredump,如果伺服器被這樣終止了,那損失就可能不小了。

解決這個問題通常使用檔案的租借鎖:首先為檔案申請一個租借鎖,當其他程序想要截斷這個檔案時,核心會發送一個實時的 RT_SIGNAL_LEASE 訊號,告訴當前程序有程序在試圖破壞檔案,這樣 write 在被 SIGBUS 殺死之前,會被中斷,返回已經寫入的位元組數,並設定 errno 為 success。

通常的做法是在 mmap 之前加鎖,操作完之後解鎖:

if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {

perror("kernel lease set signal");

return -1;

}

/* l_type can be F_RDLCK F_WRLCK  加鎖*/

/* l_type can be  F_UNLCK 解鎖*/

if(fcntl(diskfd, F_SETLEASE, l_type)){

perror("kernel lease set type");

return -1;

}

方法三:sendfile


從Linux 2.1版核心開始,Linux引入了sendfile,也能減少一次拷貝。

#include<sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

sendfile 是隻發生在核心態的資料傳輸介面,沒有使用者態的參與,自然避免了使用者態資料拷貝。它指定在 in_fd 和 out_fd 之間傳輸資料,其中,它規定 in_fd 指向的檔案必須是可以 mmap 的,out_fd 必須指向一個套接字,也就是規定資料只能從檔案傳輸到套接字,反之則不行。sendfile 不存在像 mmap 時檔案被截獲的情況,它自帶異常處理機制。

缺陷:

1)只能適用於那些不需要使用者態處理的應用程式。

方法四:DMA 輔助的 sendfile


常規 sendfile 還有一次核心態的拷貝操作,能不能也把這次拷貝給去掉呢?

答案就是這種 DMA 輔助的 sendfile。

這種方法藉助硬體的幫助,在資料從核心緩衝區到 socket 緩衝區這一步操作上,並不是拷貝資料,而是拷貝緩衝區描述符,待完成後,DMA 引擎直接將資料從核心緩衝區拷貝到協議引擎中去,避免了最後一次拷貝。

缺陷:

1)除了3.4 中的缺陷,還需要硬體以及驅動程式支援。

2)只適用於將資料從檔案拷貝到套接字上。

方法五:splice


splice 去掉 sendfile 的使用範圍限制,可以用於任意兩個檔案描述符中傳輸資料。

#define _GNU_SOURCE         /* See feature_test_macros(7) */

#include <fcntl.h>

ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

但是 splice 也有侷限,它使用了 Linux 的管道緩衝機制,所以,它的兩個檔案描述符引數中至少有一個必須是管道裝置。

splice 提供了一種流控制的機制,通過預先定義的水印(watermark)來阻塞寫請求,有實驗表明,利用這種方法將資料從一個磁碟傳輸到另外一個磁碟會增加 30%-70% 的吞吐量,CPU負責也會減少一半。

缺陷:

1)同樣只適用於不需要使用者態處理的程式

2)傳輸描述符至少有一個是管道裝置。

方法六:寫時複製


在某些情況下,核心緩衝區可能被多個程序所共享,如果某個程序想要這個共享區進行 write 操作,由於 write 不提供任何的鎖操作,那麼就會對共享區中的資料造成破壞,寫時複製就是 Linux 引入來保護資料的。

寫時複製,就是當多個程序共享同一塊資料時,如果其中一個程序需要對這份資料進行修改,那麼就需要將其拷貝到自己的程序地址空間中,這樣做並不影響其他程序對這塊資料的操作,每個程序要修改的時候才會進行拷貝,所以叫寫時拷貝。這種方法在某種程度上能夠降低系統開銷,如果某個程序永遠不會對所訪問的資料進行更改,那麼也就永遠不需要拷貝。

缺陷:

需要 MMU 的支援,MMU 需要知道程序地址空間中哪些頁面是隻讀的,當需要往這些頁面寫資料時,發出一個異常給作業系統核心,核心會分配新的儲存空間來供寫入的需求。

方法七:緩衝區共享


這種方法完全改寫 I/O 操作,因為傳統 I/O 介面都是基於資料拷貝的,要避免拷貝,就去掉原先的那套介面,重新改寫,所以這種方法是比較全面的零拷貝技術,目前比較成熟的一個方案是最先在 Solaris 上實現的 fbuf (Fast Buffer,快速緩衝區)。

Fbuf 的思想是每個程序都維護著一個緩衝區池,這個緩衝區池能被同時對映到程式地址空間和核心地址空間,核心和使用者共享這個緩衝區池,這樣就避免了拷貝。

缺陷:

1)管理共享緩衝區池需要應用程式、網路軟體、以及裝置驅動程式之間的緊密合作

2)改寫 API ,尚處於試驗階段。

高效能網路 I/O 框架——netmap


Netmap 基於共享記憶體的思想,是一個高效能收發原始資料包的框架,由Luigi Rizzo 等人開發完成,其包含了核心模組以及使用者態庫函式。其目標是,不修改現有作業系統軟體以及不需要特殊硬體支援,實現使用者態和網絡卡之間資料包的高效能傳遞。

在 Netmap 框架下,核心擁有資料包池,傳送環\接收環上的資料包不需要動態申請,有資料到達網絡卡時,當有資料到達後,直接從資料包池中取出一個數據包,然後將資料放入此資料包中,再將資料包的描述符放入接收環中。核心中的資料包池,通過 mmap 技術對映到使用者空間。使用者態程式最終通過 netmap_if 獲取接收發送環 netmap_ring,進行資料包的獲取傳送。

總結:


1、零拷貝本質上體現了一種優化的思想

2、直接 I/O,mmap,sendfile,DMA sendfile,splice,緩衝區共享,寫時複製......


公眾號後臺回覆“加群”,帶你進入高手如雲交流群

我的公眾號 「Linux雲端計算網路」(id: cloud_dev) ,號內有 10T 書籍和視訊資源,後臺回覆 「1024」 即可領取,分享的內容包括但不限於 Linux、網路、雲端計算虛擬化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++程式設計技術等內容,歡迎大家關注。

參考:


(1)Linux 中的零拷貝技術,第 1 部分
https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/index.html
(2)Linux 中的零拷貝技術,第 2 部分
https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/
(3)Netmap 原理
http://www.tuicool.com/articles/MnIR