linux下,四種不同的IO調(diào)用方法

本文為[1]的翻譯;

當(dāng)大多數(shù)的后臺開發(fā)者考慮IO的時候,他們考慮的是網(wǎng)絡(luò)IO,因為現(xiàn)在大多數(shù)的資源都是建立在網(wǎng)絡(luò)之上的:數(shù)據(jù)庫,對象存儲,以及其他微服務(wù)。然而,數(shù)據(jù)庫的開發(fā)者也必須考慮文件IO。這篇文章描述了可用的選擇及其利弊,以及為什么Scylla選擇異步IO(AIO/DIO)作為它的IO調(diào)用方法。

操作文件的方法

通常來說,Linux 服務(wù)器重有四種方法來操作文件,read/write, mmap, Direct I/O (DIO) read/write, and asynchronous direct I/O (AIO/DIO).

read/write

傳統(tǒng)的可用方法就是去使用系統(tǒng)調(diào)用函數(shù)read 和 write。在現(xiàn)代的實現(xiàn)方式,系統(tǒng)調(diào)用函數(shù)read(or pread,readv,preadv,etc) 請求內(nèi)核讀取文件中的一段數(shù)據(jù),然后把這段數(shù)據(jù)復(fù)制到調(diào)用read 函數(shù)的進程的地址空間里取。如果請求的這段數(shù)據(jù)實在頁緩存中(page cache)中,那么內(nèi)核直接賦值,立刻返回。否則的話,內(nèi)核將會調(diào)度磁盤讀取這段數(shù)據(jù)并存入頁緩存中(page cache),同時阻塞線程,當(dāng)這個數(shù)據(jù)可以利用的時候,將會喚醒線程,拷貝數(shù)據(jù)。寫函數(shù)(write) 將把數(shù)據(jù)拷貝到頁緩存中(page cache),內(nèi)核隨后會把頁緩存的數(shù)據(jù)寫到磁盤中。

read/write

Mmap

另外一個可以選擇的,更高級一點的方法是系統(tǒng)調(diào)用函數(shù)mmap,它把文件直接映射到應(yīng)用的地址空間。這將導(dǎo)致應(yīng)用的地址空間的一部分是直接引用包含文件數(shù)據(jù)的頁緩存地址(page cache),在調(diào)用mmap之后,這個應(yīng)用可以直接通過處理器的內(nèi)存讀寫指令來讀寫文件中的數(shù)據(jù)。如果請求的數(shù)據(jù)在緩存中(hit),那么可以完全繞開內(nèi)核,讀寫速度都是內(nèi)存級別的讀寫。如果請求的數(shù)據(jù)不在緩存中(miss),那么將會產(chǎn)生頁錯誤(page error)。內(nèi)核將會讓這個線程休眠(sleep)。然后去讀取缺失的頁的數(shù)據(jù)。當(dāng)數(shù)據(jù)可用,線程wake,內(nèi)存管理單元重新編程,讓新讀取的數(shù)據(jù)對線程可用。

mmap

Direct I/O (DIO)

傳統(tǒng)的read/write 和 mmap 都涉及到內(nèi)核的頁緩存(page cache),同時都把IO的調(diào)度扔給內(nèi)核來處理。如果應(yīng)用想要自己調(diào)度IO(原因隨后解釋),它可以使用 direct IO。這涉及到打開文件的時候用到的flag--O_DIRECT; 接下來將會使用正常的read 和 write 等一系列的系統(tǒng)調(diào)用。但是他們的行為改變了: 并不讀取緩存,直接訪問磁盤,這意味著調(diào)用線程將無條件進入休眠狀態(tài)。而且,磁盤控制器(disk controller)將直接將數(shù)據(jù)復(fù)制到用戶空間,繞過內(nèi)核。

DIO

Asynchronous direct I/O (AIO/DIO)

Direct IO 的改進,異步IO、AIO和DIO 類似,但是線程不阻塞。相反,應(yīng)用線程通過io_submit 系統(tǒng)調(diào)用函數(shù)來調(diào)度IO操作,但是線程不阻塞。I/O操作與正常線程執(zhí)行并行運行。一個獨立的系統(tǒng)調(diào)用函數(shù)io_getevents ,用來等待和搜集完整的IO操作的結(jié)果。和DIO一樣,內(nèi)核的頁緩存(page cache)被繞過了,磁盤控制器(disk controller)負責(zé)將直接將數(shù)據(jù)復(fù)制到用戶空間,

AIO

Understanding the tradeoffs

不同的方法又不同的特點,Table 1 總結(jié)了這些特點

Characteristic R/W mmap DIO AIO/DIO
Cache control kernel kernel user user
Copying yes no no no
MMU activity low high none none
I/O scheduling kernel kernel mixed user
Thread scheduling kernel kernel kernel user
I/O alignment automatic automatic manual manual
Application complexity low low moderate high

Cache control

對于 read/write 和mmap 函數(shù)來說,內(nèi)存由內(nèi)核負責(zé),系統(tǒng)的大部分內(nèi)存都用作頁緩存。當(dāng)內(nèi)存跑的慢的時候,當(dāng)內(nèi)存頁需要寫回磁盤的時候,當(dāng)需要預(yù)讀的時候,內(nèi)核來決定替換哪些頁面。應(yīng)用可以通過系統(tǒng)調(diào)用函數(shù)madvise 和 fadvise 來給內(nèi)核一些指導(dǎo)。

讓內(nèi)核來控制緩存的一個比較大的優(yōu)點是,十幾年來,內(nèi)核開發(fā)者已經(jīng)付出了很多努力來對緩存算法進行調(diào)優(yōu)。這些算法被用在成千上萬的應(yīng)用中,并且十分有效。缺點就是,這些算法是通用的,并不針對某一應(yīng)用進行優(yōu)化。內(nèi)核需要猜測應(yīng)用的行為。即使應(yīng)用的行為是不同的,它也沒有辦法來幫助內(nèi)核猜測正確。導(dǎo)致錯誤的頁被替換,IO調(diào)度錯誤,預(yù)讀的數(shù)據(jù)后續(xù)不會被使用到。

Copying and MMU activity

mmap的一個優(yōu)點就是如果數(shù)據(jù)在緩存中,那么內(nèi)核就會被完全繞過。內(nèi)核不需要把數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間,然后再拷貝回來,因此,在該操作上花費的處理器周期更少,有利于加載大部分緩存中的內(nèi)容(例如 the ratio of storage size to RAM size is close to 1:1)

mmap的缺點就是當(dāng)數(shù)據(jù)不在緩存中。緩存不命中通常是由于the ratio of storage size to RAM size is significantly higher than 1:1。新頁進入緩存,另外的頁就會被替換。這些頁不得不從頁表中刪除和插入。內(nèi)核必須掃描頁表以隔離不活動的頁,使它們成為替換頁的候選者。而且mmap需要為頁表申請內(nèi)存。X86的處理器上,頁表占映射文件大小的0.2%。這通常很低,但是如果這個應(yīng)用的 ratio of storage to memory為100:1,那么頁表的空間就占整個內(nèi)存的20%(0.2% * 100)。

I/O scheduling

內(nèi)核控制緩存(read/write 和 mmap)的一個問題是應(yīng)用無法控制IO的調(diào)度。內(nèi)核調(diào)選它認為合適的數(shù)據(jù)塊,調(diào)度它進行讀寫。這可能導(dǎo)致以下問題:

  • 寫風(fēng)暴: 當(dāng)內(nèi)核調(diào)度大量的寫,磁盤一段時間將會很忙,這將會導(dǎo)致讀延遲
  • 內(nèi)核無法區(qū)分"重要的"和"不重要的"IO。屬于后臺任務(wù)的I/O會壓倒前臺任務(wù),這將影響它們的延遲。

通過繞過內(nèi)核頁緩存,應(yīng)用將自己調(diào)度IO,這并不意味著問題已經(jīng)解決了,但是這意味著,通過開發(fā)人員的思考和努力,問題可以被解決。

當(dāng)使用DIO,每一個線程控制發(fā)布IO的時間。但是,內(nèi)核控制什么時候線程運行。因此,在內(nèi)核和應(yīng)用程序之間共享發(fā)布I/O的權(quán)限。通過AIO/DIO。應(yīng)用可以完全控制IO的時間。

Thread scheduling

一個IO密集型的應(yīng)用使用mmap 和 read/write 的時候,并不會知道緩存命令率的大小,因此必須使用大量的線程(通常比機器的核心數(shù)多)。如果使用的線程過少,它們可能都在等待磁盤,使處理器無法充分利用。由于每個線程通常最多只有一個磁盤I / O未完成,因此運行線程的數(shù)量必須圍繞存儲子系統(tǒng)的并發(fā)性乘以一些小因子才能保持磁盤完全占用,但是,如果高速緩存命中率足夠高,則這些大量線程將針對有限數(shù)量的核相互競爭。

當(dāng)使用DIO的時候,這個問題有所減輕,因為應(yīng)用程序確切地知道線程何時在I / O上被阻塞以及何時可以運行,因此應(yīng)用程序可以根據(jù)運行時條件來調(diào)整運行線程的數(shù)量

通過DIO/AIO,應(yīng)用程序可以完全控制正在運行的線程和等待IO(兩者完全分離);因此可以輕松調(diào)整內(nèi)存或磁盤限制條件或他們兩者之間的任何內(nèi)容。

I/O alignment

存儲設(shè)備有一個塊大?。凰械腎O必須以此塊大小的倍數(shù)執(zhí)行,這個塊大小通常是512 或者 4196 bytes,如果使用 read/write 或者 mmap,這個內(nèi)核自動進行塊的對齊;一小塊讀或者寫將會自動被擴展正確的塊邊界,否則將會出問題。

如果使用DIO,通常由應(yīng)用來進行塊對齊。這增加了復(fù)雜性,同時也提供了一個優(yōu)勢: 即使512字節(jié)邊界足夠,內(nèi)核也會過度對齊到4096字節(jié)邊界,但使用DIO的應(yīng)用程序可以進行512字節(jié)對齊讀取,這樣可以節(jié)省項目的帶寬。

Application complexity

通過前面的討論,對于IO密集型的應(yīng)用,我們更偏愛使用AIO/DIO。但是這些方法會帶來一個巨大的問題:復(fù)雜度。將緩存管理交由應(yīng)用程序處理意味著它可以以更少的開銷做出比內(nèi)核更好的選擇,但是,這些緩存算法需要被重寫以及測試。如果使用AIO,通常要求應(yīng)用來使用回調(diào),協(xié)程,或者其他相似的技術(shù),這通常會導(dǎo)致很多高可用庫的不可用。

Scylla and AIO/DIO

對于Scylla來說,我們選擇表現(xiàn)最好的AIO/DIO。為了降低以上討論的復(fù)雜度的問題,我們寫了一個框架: Seastar,一個高性能的針對IO密集型應(yīng)用的框架。Seastar抽象執(zhí)行AIO的細節(jié),并為網(wǎng)絡(luò),磁盤和多核通信提供通用API。 它還提供適用于不同用例的狀態(tài)管理的回調(diào)和協(xié)程樣式。

Scylla的不同部分使用不同的IO:

  • 壓縮使用用戶級別的預(yù)讀和寫回技術(shù)來確保高性能。但是繞過應(yīng)用層的緩存是由于可以預(yù)測的低命中率。
  • 查詢(讀取)使用用戶層的預(yù)讀和用戶層的緩存。因為我們提前知道磁盤上數(shù)據(jù)的邊界,用戶層的預(yù)讀可防止預(yù)讀溢出,用戶層緩存不僅可以緩存從磁盤讀取的數(shù)據(jù),還可以緩存來自多個文件的數(shù)據(jù)合并到單個緩存項中的內(nèi)容。
  • 小字節(jié)讀取,通常與512字節(jié)邊界對齊,以減少數(shù)據(jù)傳輸和延遲。
  • 單獨的I / O調(diào)度類確保commitlog寫入獲得所需的帶寬,并且不受讀取或dominate read的支配
  • Seastar I / O調(diào)度程序允許我們動態(tài)控制壓縮和查詢的I / O速率,以滿足用戶服務(wù)水平協(xié)議(SLAs)

AIO / DIO是從應(yīng)用程序直接驅(qū)動NVMe drivers以進一步繞過內(nèi)核的良好開端。 這或許成為未來的Seastar功能。

[1] https://www.scylladb.com/2017/10/05/io-access-methods-scylla/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容