天天看點

大話存儲系列22——存儲系統内部IO 中

4、卷管理層IO

卷管理層在某種程度上來講是為了彌補底層存儲系統的一些不足之處的,比如LUN空間的動态管理等。卷管理層最大的任務是做Block級的映射。對于IO的處理,卷層隻做了一個将映射翻譯之後的IO向下轉發的動作以及反向過程。另外,應用程式可以直接對某個卷進行IO操作而不經過檔案系統。我們所說的不經過檔案系統,并不是說Bypass系統核心緩存的Direct IO,而是完全不需要FS處理任何塊映射關系。這時就需要由應用程式自行管理底層存儲空間,而且此時不能對這個卷進行FS格式或者其他未經應用程式運作的更改操作,一旦發生将導緻資料被破壞。

卷管理層将底層磁盤空間虛拟化為靈活管理的一塊塊卷,然後又将卷同時抽象為兩種作業系統裝置:塊裝置和字元裝置。比如在AIX系統下,/dev/lv 、/dev/fslv 、/dev/hdisk等字樣表示塊裝置,而/dev/rlv、/dev/rfslv、/dev/rhdisk等帶有r字樣的裝置一般就是字元裝置。同一個實體裝置會同時被抽象為字元和塊兩種邏輯裝置,使用者程式可以直接對塊裝置和字元裝置進行IO操作。這兩個裝置也是用于上層程式直接對卷進行通路的唯一接口,有各自的驅動——塊裝置驅動和字元裝置驅動。在IO路徑的層層調用過程中,IO

Manager 通路卷的時候其實就是通路對應的裝置驅動(這一點在系統IO子產品架構圖中并沒有展現),向他們發起SystemCall的。

1、塊裝置:

    最近一段時間一直在做一個塊裝置子產品的性能分析、優化工作,是以,對Linux檔案系統層的代碼進行了初略的閱讀,頗有心得。Linux的檔案系統的确非常龐大,層次也非常清晰。以前一直将注意力集中在了裝置管理的層面,是以,通過這次的閱讀分析,可以從使用者程式的system call開始,将讀寫請求整個貫穿到底層硬體的DMA。下面對塊裝置與檔案系統之間的接口關系進行闡述,主要解決“檔案系統是如何通路塊裝置的?”這個問題。

    塊裝置可以在兩種情況下發起probe(探測)過程,一是當總線驅動發現了塊裝置,回調該塊裝置驅動的probe函數,例如磁盤裝置的驅動;二是驅動程式主動probe塊裝置,例如軟RAID驅動程式,在insmod該程式後,會主動調用probe函數。Probe函數是裝置驅動程式的核心函數,對于一類裝置驅動而言,該函數的實作方式是雷同的,具有固定的格式。

    在塊裝置的probe過程中,首先需要執行個體化一個對象——gendisk,該對象在核心中對塊裝置進行了抽象。Gendisk對象初始化成功之後調用add_disk()函數将gendisk添加到系統中,在add_disk過程中需要生成bdev_inode結構,該結構維護了一個檔案系統所需的inode節點,以及塊裝置的更高層次抽象bdev。該結構描述如下:

struct bdev_inode {

    struct block_device bdev;   //塊裝置 

    struct inode vfs_inode;     //檔案系統inode節點

};

    如下圖所示,在inode節點中一系列ops指針,指向具體的操作函數集。

如果inode描述的是一個塊裝置,那麼ops函數集指向标準的塊裝置操作函數集。當使用者open一個塊裝置時,會找到對應的inode,生成一個file對象,并且将inode中的file_operations(i_op)指派給file對象中的f_op,進而建立了檔案系統接口與塊裝置之間的接口。

如果inode描述的是一個檔案系統,那麼inode中的操作函數集将指向檔案系統具體的操作函數集。從這個思路來看,inode是vfs層的接口,block device是一個特殊的檔案系統,具體檔案系統在注冊時,需要将回調的方法注冊到inode中。/fs/Block_dev.c檔案就是block device這一特殊檔案系統的具體實作,其中實作了與檔案系統之間接口的各種函數,包括file_operations函數集。大家感興趣不妨研究一下。

在UNIX類作業系統下,塊裝置表現為一個檔案,而且應用程式可以向塊裝置發起任何長度的IO,就像對檔案進行IO時一樣,比如512B、1500B、5000B等、IO長度可以為任何位元組,而不需要為磁盤扇區的整數倍,然而,塊裝置也是由底層實體裝置抽象而來的,而底層實體裝置所能接受的IO長度必須為扇區的整數倍,是以塊裝置具有一個比較小的緩存來專門處理這個映射轉換關系。

塊裝置一般使用Memory Mapping的方式映射到記憶體位址空間,這段空間以Page(一般以4KB)為機關,是以通路塊裝置就需要牽涉到OS缺頁處理(Page Fault)方式來讀寫資料。比如應用程式向某個塊裝置卷發起一個程度為1500B的IO讀,卷管理層接收到這個IO之後将計算這個1500B的IO所占用的扇區總數以及所落入的Page位址,并且進入缺頁處理流程從底層實體裝置将這個Page對應的扇區讀入,這裡的IO請求為1500B,是以OS會從底層實體裝置讀取對應的1個Page大小的資料進入緩存,然後緩存中再将對應的1500B傳回給應用程式。

應用程式對塊裝置發起讀IO,塊裝置就得同時向底層實體裝置發起對應的轉換後的IO,不管應用程式向塊裝置發起多少長度的IO,塊裝置向底層實體裝置所發起的IO長度是恒定的(一般為4K,即緩存Page大小)。是以塊裝置向底層裝置發起的讀IO屬性永遠是小塊IO,而且對同一個線程發起的IO不會并發隻能順序,對多個線程共同發起的IO才會并行,也就是說每個線程在底層的IO都為順序執行(限于讀IO)。這一點是塊裝置非常緻命的缺點,比如一個應用程式256KB的讀IO操作,會被塊裝置切開成64個4KB的讀IO操作,這無疑是非常浪費的,會更快的耗盡底層存儲的标稱IOPS。但對于寫IO來講,塊裝置底層會有一定的merge_request操作,既可以對寫IO進行合并、覆寫、重排扥操作。

其實UNIX類系統下的裝置與檔案系統管理下的一個檔案無異,唯一差別就是直接對塊裝置進行IO操作的話,無需執行檔案——塊映射查詢而已。

讀操作對于塊裝置來講還不至于産生太過惡劣的性能影響,而寫IO則會更加嚴重地摧殘儲存設備的性能。由于塊裝置向底層發起的所有IO均以緩存Page大小為機關,現代作業系統的Page一般為4KB大小,如果某應用程式需要寫入0.5KB資料,或者4.5KB資料,那麼塊裝置不能直接把對應長度的資料直接寫入底層裝置。其浪費可謂是驚人而且無法容忍的,我們來看一個例子。下列資料顯示了AIX系統上使用IO測試工具對一個塊裝置進行4096B、2000B、2048B、5000B寫IO時系統底層向實體磁盤的IO統計情況如下圖:

程式發起的IO時候,不對齊4KB的IO Size會導緻OS首先讀入對應的Page資料,修改,然後再寫入對應的Page資料,是以可以看到寫動作伴随了一定程度的讀動作,也就是寫懲罰。

2、字元裝置:

傳統的字元裝置本來是專指一類接收字元流的裝置比如實體終端、鍵盤燈,這種裝置的特點是可以直接對裝置進行最底層的操作而不使用緩存(但是必須有Queue),而且每次IO都必須以一個字元為機關(卷所抽象出來的字元裝置以一段連續扇區為一個機關)。是以具有這種特點的實際裝置或者抽象裝置都被稱為字元裝置。而将卷抽象為字元裝置并不是說将IO從扇區改為字元,而隻是抽象出字元裝置所具有的的特點。

在任何作業系統下,對字元裝置進行IO操作必須遵循底層的最小機關對齊原則,比如對于卷字元裝置來講,每個IO長度隻能是扇區的整數倍,如果IO長度沒有以扇區為機關對齊(比如513、1500),那麼将會收到錯誤的通知而失敗。雖然unux類作業系統下的字元裝置也表現為一個檔案,但是這個檔案卻不像塊裝置一樣可以以任意位元組進行IO,因為OS沒有為字元裝置設定任何緩存(但是存在Queue)

3、裸裝置與檔案系統之争:

字元裝置又稱為裸裝置,應用程式可以選擇使用檔案系統聽的各項功能進行對檔案的IO操作,當然也可以選擇直接對裸裝置進行IO操作,隻不過直接對裸裝置操作需要應用程式自行維護資料——扇區 映射以及預讀緩存、寫緩存、讀寫優化等。比如資料庫類程式自身都有這些功能,是以沒有必要再使用檔案系統來讀寫資料。而由于塊裝置的諸多不便和惡劣性能影響,不推薦直接使用。那基于檔案系統的IO和基于裸裝置的IO,我們可以着重讨論一下哪個好:

檔案系統擁有諸多優點是毋庸置疑的,但是對于某一類程式,FS提供的這些“友善”的功能似乎就顯得很有局限性了,比如緩存的管理等,由于檔案系統是一個公用平台,同時為多個應用程式提供服務,是以它不可能隻為一個應用程式而竭盡全力服務;況且最重要的是,FS不會感覺應用程式實際想要什麼,而且FS自身的緩存在系統異常當機的時候最容易造成資料的不一緻情況發生。其次,使用緩存的IO方式下,對于讀請求,系統IO路徑中的各個子產品需要将資料層層向上層子產品的緩存中複制,最後才會被OS複制到使用者程式緩存;對于寫請求,雖然緩存IO方式下,寫資料被OS接收後即宣告完成,但這也是造成Down機後資料丢失的主要原因之一。是以對于大資料吞吐量IO請求,

避免記憶體中多餘的資料複制步驟是必要的(此外還有另外一個原因後續介紹)。但是對于一般程式,是完全推薦使用檔案系統進行IO操作的。

另外一個最重要的原因,在使用核心緩存以及檔案系統緩存的情況下,容易發生讀寫懲罰,這事非常嚴重地浪費。

對于這類IO性能要求非常高而且對緩存要求非常高的程式,他們甯願自己直接操作底層實體裝置,也不願意将IO交給FS來處理。這類程式典型的代表就是資料庫類程式。雖然這些程式也可以使用檔案系統來進行IO操作,但是這個選擇隻會給程式帶來一個方面的好處,那就是檔案管理會友善,比如可以看到資料檔案實實在在的被放在某個目錄下,可以直接将資料檔案複制出來做備份,将檔案系統快照保護等。而選擇檔案系統所帶來的壞處也是不少的,比如最大的劣勢就是重複緩存預讀,FS預讀了資料,資料庫程式依然自己維護一個預讀緩存,這兩個緩存裡面勢必有很多資料是重複的,增加了許多空間和計算資源開銷,而且這些資料不見得都會産生Cache

Hit效果,是以這類程式甯願使用裸裝置自行管理資料存儲和資料IO,所帶來的唯一缺點就是資料管理很不友善,除了程式自身,其他程式隻看到了一塊光秃秃的裸裝置在那,裡面放的什麼東西,怎麼放得,隻有程式自己知道。

4、Directl IO與裸裝置之争

有沒有一種方法能夠結合FS和裸裝置帶來的優點呢?有的,為了即享受檔案系統管理檔案的便利同時而又不使用FS層面的緩存,将緩存和IO優化操作全部交給應用程式自行處理,FS隻負責做  檔案—扇區  的映射操作以及其他檔案管理層面的操作,節約記憶體耗費以及太高處理速度,作業系統核心提供了一類接口,也就是前文彙總出現的FILE_FLAG_NO_BUFFERING參數。當然,這個參數隻是Windows核心提供的,其他作業系統也都有類似的參數。這種Bypass系統核心緩存的IO模式稱為DIO,即Direct  IO模式。在UNIX類作業系統下,在mount某個FS的時候可以指定“-direct”參數來表示任何針對這個FS的IO操作都将不使用核心路徑中任何一處緩存。當然,也可以在應用程式層控制,比如打開檔案時給出O_SYNC或者O_DIRECT、FILE_FLAG_NO_BUFFERING之類的參數,那麼不管目标FS在mount是給出了何種參數,這個程式的IO都将不使用檔案系統緩存。