開發

面試題:如何理解 Linux 的零拷貝技術?

本文講解 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 之前加鎖,操作完之后解鎖:

方法三: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 提供了一種流控制的機制,通過預先定義的水?。╳atermark)來阻塞寫請求,有實驗表明,利用這種方法將數據從一個磁盤傳輸到另外一個磁盤會增加 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,進行數據包的獲取發送。

我還沒有學會寫個人說明!

12306遭用戶吐槽,我們該支持還是反對?

上一篇

會員服務在高可用架構的實戰探索

下一篇

你也可能喜歡

面試題:如何理解 Linux 的零拷貝技術?

長按儲存圖像,分享給朋友

ITPUB 每周精要將以郵件的形式發放至您的郵箱


微信掃一掃

微信掃一掃
30岁的男人干啥赚钱快赚钱多