天天看點

DMA 與零拷貝技術

原文連結:DMA 與零拷貝技術
注意事項:除了 Direct I/O,與磁盤相關的檔案讀寫操作都有使用到 page cache 技術。

很多應用程式在面臨用戶端請求時,可以等價為進行如下的系統調用:

File.read(file, buf, len);

Socket.send(socket, buf, len);

例如消息中間件 Kafka 就是這個應用場景,從磁盤中讀取一批消息後原封不動地寫入網卡(NIC,Network interface controller)進行發送。

在沒有任何優化技術使用的背景下,作業系統為此會進行 4 次資料拷貝,以及 4 次上下文切換,如下圖所示:

DMA 與零拷貝技術

如果沒有優化,讀取磁盤資料,再通過網卡傳輸的場景性能比較差:

4 次 copy:

實體裝置 <-> 記憶體:

CPU 負責将資料從磁盤搬運到核心空間的 Page Cache 中;

CPU 負責将資料從核心空間的 Socket 緩沖區搬運到的網絡中;

記憶體内部拷貝:

CPU 負責将資料從核心空間的 Page Cache 搬運到使用者空間的緩沖區;

CPU 負責将資料從使用者空間的緩沖區搬運到核心空間的 Socket 緩沖區中;

4 次上下文切換:

read 系統調用時:使用者态切換到核心态;

read 系統調用完畢:核心态切換回使用者态;

write 系統調用時:使用者态切換到核心态;

write 系統調用完畢:核心态切換回使用者态;

我們不免發出抱怨:

CPU 全程負責記憶體内部的資料拷貝還可以接受,因為記憶體的資料拷貝效率還行(不過還是比 CPU 慢很多),但是如果要 CPU 全程負責記憶體與磁盤、記憶體與網卡的資料拷貝,這将難以接受,因為磁盤、網卡的 I/O 速度遠小于記憶體;

4 次 copy 太多了,4 次上下文切換也太頻繁了;

DMA 技術很容易了解,本質上,DMA 技術就是我們在主機闆上放一塊獨立的晶片。在進行記憶體和 I/O 裝置的資料傳輸的時候,我們不再通過 CPU 來控制資料傳輸,而直接通過 DMA 控制器(DMA Controller,簡稱 DMAC)。這塊晶片,我們可以認為它其實就是一個協處理器(Co-Processor)。

DMAC 的價值在如下情況中尤其明顯:當我們要傳輸的資料特别大、速度特别快,或者傳輸的資料特别小、速度特别慢的時候。

比如說,我們用千兆網卡或者硬碟傳輸大量資料的時候,如果都用 CPU 來搬運的話,肯定忙不過來,是以可以選擇 DMAC。而當資料傳輸很慢的時候,DMAC 可以等資料到齊了,再發送信号,給到 CPU 去處理,而不是讓 CPU 在那裡忙等待。

這裡面的“協”字。DMAC 是在“協助”CPU,完成對應的資料傳輸工作。在 DMAC 控制資料傳輸的過程中,DMAC 還是被 CPU 控制,隻是資料的拷貝行為不再由 CPU 來完成。

原本,計算機所有元件之間的資料拷貝(流動)必須經過 CPU。以磁盤讀寫為例,如下圖所示:

DMA 與零拷貝技術

現在,DMAC 代替了 CPU 負責記憶體與磁盤、記憶體與網卡之間的資料搬運,CPU 作為 DMAC 的控制者,如下圖所示:

DMA 與零拷貝技術

但是 DMAC 有其局限性,DMAC 僅僅能用于裝置間交換資料時進行資料拷貝,但是裝置内部的資料拷貝還需要 CPU 來親力親為。例如, CPU 需要負責核心空間與使用者空間之間的資料拷貝(記憶體内部的拷貝),如下圖所示:

DMA 與零拷貝技術

零拷貝技術是一個思想,指的是指計算機執行操作時,CPU 不需要先将資料從某處記憶體複制到另一個特定區域。

可見,零拷貝的特點是 CPU 不全程負責記憶體中的資料寫入其他元件,CPU 僅僅起到管理的作用。但注意,零拷貝不是不進行拷貝,而是 CPU 不再全程負責資料拷貝時的搬運工作。如果資料本身不在記憶體中,那麼必須先通過某種方式拷貝到記憶體中(這個過程 CPU 可以僅僅負責管理,DMAC 來負責具體資料拷貝),因為資料隻有在記憶體中,才能被轉移,才能被 CPU 直接讀取計算。

零拷貝技術的具體實作方式有很多,例如:

sendfile

mmap

直接 Direct I/O

splice

不同的零拷貝技術适用于不同的應用場景,下面依次進行 sendfile、mmap、Direct I/O 的分析。

不過,我們不妨先在這裡做一個前瞻性的技術總結。

DMA 技術:DMA 負責記憶體與其他元件之間的資料拷貝,CPU 僅需負責管理,而無需負責全程的資料拷貝;

使用 page cache 的 zero copy:

sendfile:一次代替 read/write 系統調用,通過使用 DMA 技術以及傳遞檔案描述符,實作了 zero copy;

mmap:僅代替 read 系統調用,将核心空間位址映射為使用者空間位址,write 操作直接作用于核心空間。通過 DMA 技術以及位址映射技術,使用者空間與核心空間無須資料拷貝,實作了 zero copy;

不使用 page cache 的 Direct I/O:讀寫操作直接在磁盤上進行,不使用 page cache 機制,通常結合使用者空間的使用者緩存使用。通過 DMA 技術直接與磁盤/網卡進行資料互動,實作了 zero copy;

snedfile 的應用場景是:使用者從磁盤讀取一些檔案資料後不需要經過任何計算與處理就通過網絡傳輸出去。此場景的典型應用是消息隊列。

在傳統 I/O 下,正如第一節所示,上述應用場景的一次資料傳輸需要四次 CPU 全權負責的拷貝與四次上下文切換,正如本文第一節所述。

sendfile 主要使用到了兩個技術:

DMA 技術;

傳遞檔案描述符代替資料拷貝;

利用 DMA 技術。sendfile 依賴于 DMA 技術,将四次 CPU 全程負責的拷貝與四次上下文切換減少到兩次,如下圖所示,DMA 負責磁盤到核心空間中的 Page cache(read buffer)的資料拷貝以及從核心空間中的 socket buffer 到網卡的資料拷貝。

DMA 與零拷貝技術

傳遞檔案描述符代替資料拷貝。傳遞檔案描述可以代替資料拷貝,這是由于兩個原因:

page cache 以及 socket buffer 都在核心空間中;

資料在傳輸中沒有被更新;

DMA 與零拷貝技術
隻有網卡支援 SG-DMA(The Scatter-Gather Direct Memory Access)技術才可以通過傳遞檔案描述符的方式避免核心空間内的一次 CPU 拷貝。這意味着此優化取決于 Linux 系統的實體網卡是否支援(Linux 在核心 2.4 版本裡引入了 DMA 的 scatter/gather – 分散/收集功能,隻要確定 Linux 版本高于 2.4 即可)。
DMA 與零拷貝技術

MMAP

Direct I/O 即直接 I/O。其名字中的“直接”二字用于區分使用 page cache 機制的緩存 I/O。

緩存檔案 I/O:使用者空間要讀寫一個檔案并不直接與磁盤互動,而是中間夾了一層緩存,即 page cache;

直接檔案 I/O:使用者空間讀取的檔案直接與磁盤互動,沒有中間 page cache 層;

“直接”在這裡還有另一層語義:其他所有技術中,資料至少需要在核心空間存儲一份,但是在 Direct I/O 技術中,資料直接存儲在使用者空間中,繞過了核心。

Direct I/O 模式如下圖所示:

DMA 與零拷貝技術

Direct I/O 的讀寫非常有特點:

Write 操作:由于其不使用 page cache,是以其進行寫檔案,如果傳回成功,資料就真的落盤了(不考慮磁盤自帶的緩存);

Read 操作:由于其不使用 page cache,每次讀操作是真的從磁盤中讀取,不會從檔案系統的緩存中讀取。

事實上,即使 Direct I/O 還是可能需要使用作業系統的 fsync 系統調用。為什麼?

這是因為雖然檔案的資料本身沒有使用任何緩存,但是檔案的中繼資料仍然需要緩存,包括 VFS 中的 inode cache 和 dentry cache 等。

在部分作業系統中,在 Direct I/O 模式下進行 write 系統調用能夠確定檔案資料落盤,但是檔案中繼資料不一定落盤。如果在此類作業系統上,那麼還需要執行一次 fsync 系統調用確定檔案中繼資料也落盤。否則,可能會導緻檔案異常、中繼資料确實等情況。MySQL 的 O_DIRECT 與 O_DIRECT_NO_FSYNC 配置是一個具體案例。

Direct I/O 的缺點:

由于裝置之間的資料傳輸是通過 DMA 完成的,是以使用者空間的資料緩沖區記憶體頁必須進行 page pinning(頁鎖定),這是為了防止其實體頁框位址被交換到磁盤或者被移動到新的位址而導緻 DMA 去拷貝資料的時候在指定的位址找不到記憶體頁進而引發缺頁錯誤,而頁鎖定的開銷并不比 CPU 拷貝小,是以為了避免頻繁的頁鎖定系統調用,應用程式必須配置設定和注冊一個持久的記憶體池,用于資料緩沖。

如果通路的資料不在應用程式緩存中,那麼每次資料都會直接從磁盤進行加載,這種直接加載會非常緩慢。

在應用層引入直接 I/O 需要應用層自己管理,這帶來了額外的系統複雜性;

誰會使用 Direct I/O?自緩存應用程式( self-caching applications)可以選擇使用 Direct I/O。對于某些應用程式來說,它會有它自己的資料緩存機制,比如,它會将資料緩存在應用程式位址空間,這類應用程式完全不需要使用作業系統核心中的高速緩沖存儲器,這類應用程式就被稱作是自緩存應用程式( self-caching applications )。

例如,應用内部維護一個緩存空間,當有讀操作時,首先讀取應用層的緩存資料,如果沒有,那麼就通過 Direct I/O 直接通過磁盤 I/O 來讀取資料。緩存仍然在應用,隻不過應用覺得自己實作一個緩存比作業系統的緩存更高效。

<b>資料庫管理系統是這類應用程式的一個代表。</b>自緩存應用程式傾向于使用資料的邏輯表達方式,而非實體表達方式;當系統記憶體較低的時候,自緩存應用程式會讓這種資料的邏輯緩存被換出,而并非是磁盤上實際的資料被換出。自緩存應用程式對要操作的資料的語義了如指掌,是以它可以采用更加高效的緩存替換算法。自緩存應用程式有可能會在多台主機之間共享一塊記憶體,那麼自緩存應用程式就需要提供一種能夠有效地将使用者位址空間的緩存資料置為無效的機制,進而確定應用程式位址空間緩存資料的一緻性。

page cache 是 Linux 為所有應用提供的緩存機制,但是資料庫應用太特殊了,page cache 影響了資料對特性的追求。

Kafka 作為一個消息隊列,涉及到磁盤 I/O 主要有兩個操作:

Provider 向 Kakfa 發送消息,Kakfa 負責将消息以日志的方式持久化落盤;

Consumer 向 Kakfa 進行拉取消息,Kafka 負責從磁盤中讀取一批日志消息,然後再通過網卡發送;

Kakfa 服務端接收 Provider 的消息并持久化的場景下使用 mmap 機制,能夠基于順序磁盤 I/O 提供高效的持久化能力,使用的 Java 類為 java.nio.MappedByteBuffer。

Kakfa 服務端向 Consumer 發送消息的場景下使用 sendfile 機制,這種機制主要兩個好處:

sendfile 避免了核心空間到使用者空間的 CPU 全程負責的資料移動;

sendfile 基于 Page Cache 實作,是以如果有多個 Consumer 在同時消費一個主題的消息,那麼由于消息一直在 page cache 中進行了緩存,是以隻需一次磁盤 I/O,就可以服務于多個 Consumer;

使用 mmap 來對接收到的資料進行持久化,使用 sendfile 從持久化媒體中讀取資料然後對外發送是一對常用的組合。但是注意,你無法利用 sendfile 來持久化資料,利用 mmap 來實作 CPU 全程不參與資料搬運的資料拷貝。

MySQL 的具體實作比 Kakfa 複雜很多,這是因為支援 SQL 查詢的資料庫本身比消息隊列對複雜很多。

MySQL 的零拷貝技術

DMA 技術使得記憶體與其他元件,例如磁盤、網卡進行資料拷貝時,CPU 僅僅需要發出控制信号,而拷貝資料的過程則由 DMAC 負責完成。

Linux 的零拷貝技術有多種實作政策,但根據政策可以分為如下幾種類型:

<b>減少甚至避免使用者空間和核心空間之間的資料拷貝</b>:在一些場景下,使用者程序在資料傳輸過程中并不需要對資料進行通路和處理,那麼資料在 Linux 的 Page Cache 和使用者程序的緩沖區之間的傳輸就完全可以避免,讓資料拷貝完全在核心裡進行,甚至可以通過更巧妙的方式避免在核心裡的資料拷貝。這一類實作一般是是通過增加新的系統調用來完成的,比如 Linux 中的 mmap(),sendfile() 以及 splice() 等。

<b>繞過核心的直接 I/O</b>:允許在使用者态程序繞過核心直接和硬體進行資料傳輸,核心在傳輸過程中隻負責一些管理和輔助的工作。這種方式其實和第一種有點類似,也是試圖避免使用者空間和核心空間之間的資料傳輸,隻是第一種方式是把資料傳輸過程放在核心态完成,而這種方式則是直接繞過核心和硬體通信,效果類似但原理完全不同。

<b>核心緩沖區和使用者緩沖區之間的傳輸優化</b>:這種方式側重于在使用者程序的緩沖區和作業系統的頁緩存之間的 CPU 拷貝的優化。這種方法延續了以往那種傳統的通信方式,但更靈活。