天天看點

Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹

文章目錄

  • 使用标準I/O的痛點
  • 零拷貝技術介紹
    • 直接I/O操作
    • mmap記憶體映射
    • sendfile 在檔案描述符之間傳遞資料

使用标準I/O的痛點

在Linux中 标準I/O操作都是基于資料拷貝的緩沖機制,從核心中拷貝資料到使用者空間的緩沖區中,然後将使用者緩沖區中的資料拷貝至核心中。是以I/O操作頻繁的使用會導緻資料在核心和使用者空間之間進行頻繁的切換,這樣做的好處雖然是可以通過緩沖機制減少實際的I/O系統調用,但是在資料拷貝的過程中會額外增加CPU的開銷。

這裡我們主要讨論如何在I/O操作的時候,有效減少資料拷貝,資料拷貝就是通過 copy_to_user 或者 copy_from_user将資料在核心和使用者空間之間互相拷貝的操作,如下:

Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹
DMA(Direct Memory Access):直接存儲器通路。DMA是一種無需CPU的參與,讓外設和系統記憶體之間進行雙向資料傳輸的硬體機制。使用DMA可以使系統CPU從實際的I/O資料傳輸過程中擺脫出來,進而大大提高系統的吞吐率。

零拷貝技術介紹

零拷貝技術,就是避免将資料從一塊存儲區拷貝至另一塊存儲區的技術,減少了資料拷貝所帶來的CPU開銷。但是零拷貝并不是将拷貝操作完全消除,百度如下:

零複制(英語:Zero-copy;也譯零拷貝)技術是指計算機執行操作時,CPU不需要先将資料從某處記憶體複制到另一個特定區域。這種技術通常用于通過網絡傳輸檔案時節省CPU周期和記憶體帶寬。

目前零拷貝技術主要有三種類型:

  • 使用檔案I/O:檔案I/O不提供緩沖機制,每次使用都會引起系統調用,檔案I/O的操作是在使用者位址空間與IO裝置之間傳遞。
  • 避免核心與使用者空間之間的資料拷貝:通過避免核心與使用者空間之間的資料拷貝,來實作零拷貝。
  • 寫時複制技術:資料不會立即拷貝,而是等需要修改的時候再進行部分拷貝,感覺這一部分的技術邏輯與Make相似。

下面主要讨論在網絡程式設計時,讀取本地檔案,通過socket發送出去 場景下的零拷貝技術。

直接I/O操作

直接跨過核心,使運作在使用者态下直接通路硬體裝置,資料跨過核心進行傳輸,核心僅僅做一些必要的虛拟存儲配置工作,工作邏輯如下:

Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹

缺陷:

  • 這種方法隻能适用于那些不需要核心緩沖區處理的應用程式,這些應用程式通常在程序位址空間有自己的資料緩存機制,稱為自緩存應用程式,如資料庫管理系統就是一個代表。
  • 這種方法直接操作磁盤 I/O,由于 CPU 和磁盤 I/O 之間的執行時間差距,會造成資源的浪費,解決這個問題需要和異步 I/O 結合使用。

mmap記憶體映射

首先了解虛拟記憶體與實體位址的映射關系,虛拟記憶體是OS為了友善使用者操作而實作對實體位址的抽象,虛拟記憶體與實體記憶體之間通過頁表進行關聯。每個程序都有自己的頁表,頁表負責從虛拟記憶體到實體記憶體的映射,如下:

Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹
每個程序都有自己的PageTable,程序的虛拟記憶體位址通過PageTable對應于實體記憶體,記憶體配置設定具有惰性,它的過程一般是這樣的:程序建立後建立與程序對應的PageTable,當程序需要記憶體時會通過PageTable尋找實體記憶體,如果沒有找到對應的頁幀就會發生缺頁中斷,進而建立PageTable與實體記憶體的對應關系。虛拟記憶體不僅可以對實體記憶體進行擴充,還可以更友善地靈活配置設定,并對程式設計提供更友好的操作

記憶體映射(mmap)就是說,将使用者空間和核心空間的虛拟位址映射到同一塊實體記憶體中,是以使用者就可以在使用者空間直接通路核心中的資料了,如下:

Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹

是以mmap并沒有為使用者提供直接操作核心位址空間的能力,而是通過記憶體映射機制,将核心中的部分記憶體空間映射到使用者空間中(相同的實體記憶體),進而使使用者可以直接通路核心空間的位址。mmap函數原型如下:

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
           
注意,調用mmap之後,并不會立即讀取檔案内容并加載到實體記憶體中,而是會在虛拟記憶體中配置設定位址空間,而實際要通路資料的時候,會因為記憶體位址對應的實體記憶體中沒有資料,産生“缺頁”異常,然後觸發資料加載。

在mmap支援下的操作流程如下:

Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹
Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹
  1. 使用者程序通過系統調用mmap函數進入核心态,發生第1次上下文切換,并建立核心緩沖區;
  2. 發生缺頁中斷,CPU通知DMA讀取資料;
  3. DMA拷貝資料到實體記憶體,并建立核心緩沖區和實體記憶體的映射關系;
  4. 建立使用者空間的程序緩沖區和同一塊實體記憶體的映射關系,由核心态轉變為使用者态,發生第2次上下文切換;
  5. 使用者程序進行邏輯處理後,通過系統調用Socket send,使用者态進入核心态,發生第3次上下文切換;
  6. 系統調用Send建立網絡緩沖區,并拷貝核心讀緩沖區資料;
  7. DMA控制器将網絡緩沖區的資料發送網卡,并傳回,由核心态進入使用者态,發生第4次上下文切換;

缺點:

  • 針對大檔案比較适合mmap,小檔案則會造成較多的記憶體碎片,得不償失
  • 當mmap一個檔案時,如果檔案被另一個程序截獲可能會因為非法通路導緻程序被SIGBUS 信号終止;解決這個問題通常使用檔案的租借鎖:首先為檔案申請一個租借鎖,當其他程序想要截斷這個檔案時,核心會發送一個實時的 RT_SIGNAL_LEASE 信号,告訴目前程序有程序在試圖破壞檔案,這樣 write 在被 SIGBUS 殺死之前,會被中斷,傳回已經寫入的位元組數,并設定 errno 為 success。

sendfile 在檔案描述符之間傳遞資料

通過sendfile,可以實作在核心态中傳遞資料,即核心态中在兩個檔案描述符之間傳遞資料,這樣就避免了使用者空間與核心之間的資料拷貝,sendfile函數原型如下:

#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
           

in_fd參數是資料源的檔案描述符,out_fd參數是待輸出的檔案描述符,in_fd必須是一個可以mmap的檔案描述符,必須指向真實的檔案,不能是socket等,而out_fd必須是一個socket。

在2.6.33之前Sendfile的out_fd必須是socket,是以sendfile幾乎成了專為網絡傳輸而設計的,限制了其使用範圍比較狹窄。2.6.33之後out_fd才可以是任何file,于是乎出現了splice。

執行流程如下:

Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹

1.使用者程序調用sendfile系統調用,進入核心态

2.CPU通知DMA将資料拷貝至核心緩沖區

3.核心自動将資料從核心緩沖區拷貝至網絡緩沖區

4.通過DMA向網卡發送資料

5.sendfile調用完成,有核心态傳回使用者态

PS:在Linux 2.4版本中,對sendfile進一步做了優化,之前從“檔案資料緩存”到“socket緩存”時候,也需要一次拷貝,優化之後,“socket緩存”中隻存儲要發送的資料在“檔案資料緩存”中的位置和偏移量,在實際發送時,根據位置和偏移量直接将“檔案資料緩存”中的資料拷貝到網卡裝置中,又省掉了一次拷貝操作。如下:
Linux 零拷貝技術使用标準I/O的痛點零拷貝技術介紹

在對sendfile進行優化之後,唯一的一次核心中的資料拷貝也可以通過DMA直接發送到網卡,整個過程中隻有兩次上下文切換和兩次DMA操作,實作了真正意義上的零拷貝!!!但是需要硬體DMA支援,雖然可以設定偏移量,但不能對資料進行任何的修改。

參考:

Linux - Zero-copy(零拷貝)

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

Linux百萬并發[零拷貝]實作原理

繼續閱讀