1.禁用swap分區 swapoff -a && sysctl -w vm.swappiness=0 sed -ri '/^[^#]*swap/s@^@#@' /etc/fstab grep swap /etc/fstab 2. 禁用防火牆和selinux 2.1禁用防火牆,網路管理,郵箱 1 sy ...
註意事項:除了 Direct I/O,與磁碟相關的文件讀寫操作都有使用到 page cache 技術。
數據的四次拷貝與四次上下文切換
很多應用程式在面臨客戶端請求時,可以等價為進行如下的系統調用:
File.read(file, buf, len);
Socket.send(socket, buf, len);
例如消息中間件 Kafka 就是這個應用場景,從磁碟中讀取一批消息後原封不動地寫入網卡(NIC,Network interface controller)進行發送。
在沒有任何優化技術使用的背景下,操作系統為此會進行 4 次數據拷貝,以及 4 次上下文切換,如下圖所示:
如果沒有優化,讀取磁碟數據,再通過網卡傳輸的場景性能比較差:
4 次 copy:
CPU 負責將數據從磁碟搬運到內核空間的 Page Cache 中;
CPU 負責將數據從內核空間的 Socket 緩衝區搬運到的網路中;
CPU 負責將數據從內核空間的 Page Cache 搬運到用戶空間的緩衝區;
CPU 負責將數據從用戶空間的緩衝區搬運到內核空間的 Socket 緩衝區中。
4 次上下文切換:
read 系統調用時:用戶態切換到內核態;
read 系統調用完畢:內核態切換回用戶態;
write 系統調用時:用戶態切換到內核態;
write 系統調用完畢:內核態切換回用戶態。
我們不免發出抱怨:
CPU 全程負責記憶體內的數據拷貝還可以接受,因為效率還算可以接受,但是如果要全程負責記憶體與磁碟、網路的數據拷貝,這將難以接受,因為磁碟、網卡的速度遠小於記憶體,記憶體又遠遠小於 CPU;
4 次 copy 太多了,4 次上下文切換也太頻繁了。
DMA 參與下的數據四次拷貝
DMA 技術很容易理解,本質上,DMA 技術就是我們在主板上放一塊獨立的晶元。在進行記憶體和 I/O 設備的數據傳輸的時候,我們不再通過 CPU 來控制數據傳輸,而直接通過 DMA 控制器(DMA Controller,簡稱 DMAC)。這塊晶元,我們可以認為它其實就是一個協處理器(Co-Processor)。
DMAC 最有價值的地方體現在,當我們要傳輸的數據特別大、速度特別快,或者傳輸的數據特別小、速度特別慢的時候。
比如說,我們用千兆網卡或者硬碟傳輸大量數據的時候,如果都用 CPU 來搬運的話,肯定忙不過來,所以可以選擇 DMAC。而當數據傳輸很慢的時候,DMAC 可以等數據到齊了,再發送信號,給到 CPU 去處理,而不是讓 CPU 在那裡忙等待。
註意,這裡面的“協”字。DMAC 是在“協助”CPU,完成對應的數據傳輸工作。在 DMAC 控制數據傳輸的過程中,我們還是需要 CPU 的進行控制,但是具體數據的拷貝不再由 CPU 來完成。
原本,電腦所有組件之間的數據拷貝(流動)必須經過 CPU,如下圖所示:
現在,DMA 代替了 CPU 負責記憶體與磁碟以及記憶體與網卡之間的數據搬運,CPU 作為 DMA 的控制者,如下圖所示:
但是 DMA 有其局限性,DMA 僅僅能用於設備之間交換數據時進行數據拷貝,但是設備內部的數據拷貝還需要 CPU 進行,例如 CPU 需要負責內核空間數據與用戶空間數據之間的拷貝(記憶體內部的拷貝),如下圖所示:
零拷貝技術
什麼是零拷貝技術?
零拷貝技術是一個思想,指的是指電腦執行操作時,CPU 不需要先將數據從某處記憶體複製到另一個特定區域。
可見,零拷貝的特點是 CPU 不全程負責記憶體中的數據寫入其他組件,CPU 僅僅起到管理的作用。但註意,零拷貝不是不進行拷貝,而是 CPU 不再全程負責數據拷貝時的搬運工作。如果數據本身不在記憶體中,那麼必須先通過某種方式拷貝到記憶體中(這個過程 CPU 可以不參與),因為數據只有在記憶體中,才能被轉移,才能被 CPU 直接讀取計算。
零拷貝技術的具體實現方式有很多,例如:
sendfile
mmap
splice
直接 Direct I/O
不同的零拷貝技術適用於不同的應用場景,下麵依次進行 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
sendfile
snedfile 的應用場景是:用戶從磁碟讀取一些文件數據後不需要經過任何計算與處理就通過網路傳輸出去。此場景的典型應用是消息隊列。
在傳統 I/O 下,正如第一節所示,上述應用場景的一次數據傳輸需要四次 CPU 全權負責的拷貝與四次上下文切換,正如本文第一節所述。
sendfile 主要使用到了兩個技術:
DMA 技術;
傳遞文件描述符代替數據拷貝。
下麵依次講解這兩個技術的作用。
利用 DMA 技術
sendfile 依賴於 DMA 技術,將四次 CPU 全程負責的拷貝與四次上下文切換減少到兩次,如下圖所示:
DMA 負責磁碟到內核空間中的 Page cache(read buffer)的數據拷貝以及從內核空間中的 socket buffer 到網卡的數據拷貝。
傳遞文件描述符代替數據拷貝
傳遞文件描述可以代替數據拷貝,這是由於兩個原因:
page cache 以及 socket buffer 都在內核空間中;
數據傳輸過程前後沒有任何寫操作。
註意事項:只有網卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術才可以通過傳遞文件描述符的方式避免內核空間內的一次 CPU 拷貝。這意味著此優化取決於 Linux 系統的物理網卡是否支持(Linux 在內核 2.4 版本里引入了 DMA 的 scatter/gather – 分散/收集功能,只要確保 Linux 版本高於 2.4 即可)。
一次系統調用代替兩次系統調用
由於 sendfile 僅僅對應一次系統調用,而傳統文件操作則需要使用 read 以及 write 兩個系統調用。
正因為如此,sendfile 能夠將用戶態與內核態之間的上下文切換從 4 次講到 2 次。
另一方面,我們需要註意 sendfile 系統調用的局限性。如果應用程式需要對從磁碟讀取的數據進行寫操作,例如解密或加密,那麼 sendfile 系統調用就完全沒法用。這是因為用戶線程根本就不能夠通過 sendfile 系統調用得到傳輸的數據。
mmap
mmap 技術在這篇文章[1]中單獨展開,請移步閱讀。
Direct I/O
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 的優缺點:
優點:
Linux 中的直接 I/O 技術省略掉緩存 I/O 技術中操作系統內核緩衝區的使用,數據直接在應用程式地址空間和磁碟之間進行傳輸,從而使得自緩存應用程式可以省略掉複雜的系統級別的緩存結構,而執行程式自己定義的數據讀寫管理,從而降低系統級別的管理對應用程式訪問數據的影響。
與其他零拷貝技術一樣,避免了內核空間到用戶空間的數據拷貝,如果要傳輸的數據量很大,使用直接 I/O 的方式進行數據傳輸,而不需要操作系統內核地址空間拷貝數據操作的參與,這將會大大提高性能。
缺點:
由於設備之間的數據傳輸是通過 DMA 完成的,因此用戶空間的數據緩衝區記憶體頁必須進行 page pinning(頁鎖定),這是為了防止其物理頁框地址被交換到磁碟或者被移動到新的地址而導致 DMA 去拷貝數據的時候在指定的地址找不到記憶體頁從而引發缺頁錯誤,而頁鎖定的開銷並不比 CPU 拷貝小,所以為了避免頻繁的頁鎖定系統調用,應用程式必須分配和註冊一個持久的記憶體池,用於數據緩衝。
如果訪問的數據不在應用程式緩存中,那麼每次數據都會直接從磁碟進行載入,這種直接載入會非常緩慢。
在應用層引入直接 I/O 需要應用層自己管理,這帶來了額外的系統複雜性。
誰會使用 Direct I/O?
IBM的一篇文章[2]指出,自緩存應用程式( self-caching applications)可以選擇使用 Direct I/O。
自緩存應用程式
對於某些應用程式來說,它會有它自己的數據緩存機制,比如,它會將數據緩存在應用程式地址空間,這類應用程式完全不需要使用操作系統內核中的高速緩衝存儲器,這類應用程式就被稱作是自緩存應用程式( self-caching applications )。
例如,應用內部維護一個緩存空間,當有讀操作時,首先讀取應用層的緩存數據,如果沒有,那麼就通過 Direct I/O 直接通過磁碟 I/O 來讀取數據。緩存仍然在應用,只不過應用覺得自己實現一個緩存比操作系統的緩存更高效。
資料庫管理系統是這類應用程式的一個代表。自緩存應用程式傾向於使用數據的邏輯表達方式,而非物理表達方式;當系統記憶體較低的時候,自緩存應用程式會讓這種數據的邏輯緩存被換出,而並非是磁碟上實際的數據被換出。自緩存應用程式對要操作的數據的語義瞭如指掌,所以它可以採用更加高效的緩存替換演算法。自緩存應用程式有可能會在多台主機之間共用一塊記憶體,那麼自緩存應用程式就需要提供一種能夠有效地將用戶地址空間的緩存數據置為無效的機制,從而確保應用程式地址空間緩存數據的一致性。
另一方面,目前 Linux 上的非同步 IO 庫,其依賴於文件使用 O_DIRECT 模式打開,它們通常一起配合使用。
如何使用 Direct I/O?
用戶應用需要實現用戶空間內的緩存區,讀/寫操作應當儘量通過此緩存區提供。如果有性能上的考慮,那麼儘量避免頻繁地基於 Direct I/O 進行讀/寫操作。
典型案例
Kakfa
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
MySQL 的具體實現比 Kakfa 複雜很多,這是因為支持 SQL 查詢的資料庫本身比消息隊列對複雜很多。
MySQL的零拷貝技術使用方式請移步我的另一篇文章[3]。
總結
DMA 技術的推出使得記憶體與其他組件,例如磁碟、網卡進行數據拷貝時,CPU 僅僅需要發出控制信號,而拷貝數據的過程則由 DMA 負責完成。
Linux 的零拷貝技術有多種實現策略,但根據策略可以分為如下幾種類型:
減少甚至避免用戶空間和內核空間之間的數據拷貝:在一些場景下,用戶進程在數據傳輸過程中並不需要對數據進行訪問和處理,那麼數據在 Linux 的 Page Cache 和用戶進程的緩衝區之間的傳輸就完全可以避免,讓數據拷貝完全在內核里進行,甚至可以通過更巧妙的方式避免在內核里的數據拷貝。這一類實現一般是是通過增加新的系統調用來完成的,比如 Linux 中的 mmap(),sendfile() 以及 splice() 等。
繞過內核的直接 I/O:允許在用戶態進程繞過內核直接和硬體進行數據傳輸,內核在傳輸過程中只負責一些管理和輔助的工作。這種方式其實和第一種有點類似,也是試圖避免用戶空間和內核空間之間的數據傳輸,只是第一種方式是把數據傳輸過程放在內核態完成,而這種方式則是直接繞過內核和硬體通信,效果類似但原理完全不同。
內核緩衝區和用戶緩衝區之間的傳輸優化:這種方式側重於在用戶進程的緩衝區和操作系統的頁緩存之間的 CPU 拷貝的優化。這種方法延續了以往那種傳統的通信方式,但更靈活。
相關鏈接:
https://spongecaptain.cool/SimpleClearFileIO/3.%20mmap.html
https://www.ibm.com/developerworks/cn/linux/l-cn-directio/
https://spongecaptain.cool/zerocopyofmysql