Linux作業系統的存儲子系統應該是Linux中最為複雜的子系統了。其實很多子系統都認為自己是最複雜的子系統,比如記憶體子系統和網絡子系統也這麼說。無論如何,存儲子系統在Linux中是比較複雜的。今天我們就介紹一下Linux的存儲子系統中的硬碟與RAID的相關内容,後面再寫一篇關于LVM與檔案系統的内容。
硬碟
在Linux的存儲子系統中,最底層的就是硬碟了。這裡的硬碟并不是指我們看到的硬碟硬體,而是指在Linux内部看到的硬碟裝置,或者說是塊裝置。如果我們在/dev目錄執行以下ls指令,就可以看到很多裝置。在這些裝置中以sd開頭的就是基于SCSI協定的硬碟。

圖1 Linux中的塊裝置
無論是基于SAS、iSCSI還是FC的磁盤裝置,大概都是這個樣子。形似dm-X的是Device Map塊裝置,也就是通過LVM進行管理的裝置,這種裝置是一種邏輯裝置。
在Linux作業系統中塊裝置的種類很多,有本地磁盤裝置、有SAN裝置還有基于網絡的塊裝置。在虛拟機中塊裝置又呈現為另外一種檔案名,比如在Xen虛拟機中為xvdX。
雖然名稱差異很大,但是在Linux作業系統核心中的實作卻非常簡單。在核心中任何磁盤塊裝置都是通過調用add_disk函數完成的。在《Linux裝置驅動程式》這本書對塊裝置進行了詳細的介紹,并且可以通過非常簡單的代碼實作一個自己的塊裝置。
圖2 最簡單的塊裝置驅動
這裡面有2個函數,也就是alloc_disk和add_disk。前一個函數是配置設定一個通用塊的結構體,後者則是将該塊裝置添加到核心,也就是在/dev目錄下生成一個“檔案”。以上述代碼為例,執行後會生成如下塊裝置。
brw-rw---- 1 root disk 251, 0 Jun 16 09:13 /dev/sbulla
這裡我們自定義了一個裝置名稱sbulla。其實我們看到的SCSI裝置也是這樣定義的,隻不過其定義名稱的時候是通過sd字元。
以上述代碼為例,在塊裝置中比較重要的地方是初始化了一個隊列處理函數(sbull_full_request)。所有從上層通路該塊裝置的請求都會轉發到該處理函數進行處理。
所有塊裝置都要初始化這個隊列,并且提供一個請求處理函數。不同的塊裝置的請求處理函數略有不同。比如常見的SCSI塊裝置,其處理函數初始化過程如下:
q = __scsi_alloc_queue(sdev->host, scsi_request_fn);
而nbd(網絡塊裝置,通過網絡的方式将服務端的檔案映射為用戶端的塊裝置)裝置的初始化隊列的代碼如下所示:
disk->queue = blk_init_queue(do_nbd_request, &nbd_lock);
類似的例子還很多,本文不再一一介紹。這裡我們需要了解一點,核心問題在于注冊處理請求的回調函數,以及通過add_disk就可以在/dev目錄下面建立一個塊裝置。
另外一點,對于任何類型的塊裝置,無論是本地硬碟,還是經過網絡的NBD和iSCSI,還是FC裝置,最後都是/dev目錄下的一個檔案,而這個檔案其實就是塊裝置。我們可以通過對該檔案的讀寫實作對塊裝置的通路。
RAID
作為普通使用者使用單個硬碟是沒有任何問題的,但是作為企業應用使用單個硬碟存在很大的風險。這時因為硬碟随時有可能損壞,是以我們需要一種機制來保證即使出現硬碟故障的情況下,資料不會丢失,且業務仍然可以正常工作。
RAID正是解決上述問題的技術。RAID的全稱為廉價備援磁盤陣列(Redundant Array of Inexpensive Disks),從字面可以看出其基本原理就是通過廉價的磁盤組成一組磁盤。RAID不僅僅可以通過備援的方式解決資料可靠性的問題,還可以提高性能。其主要原理就是将請求拆分到多個實體硬碟來執行,性能自然比一個硬碟快了。
在Linux作業系統層面,其實就是将實體磁盤通過軟體抽象為邏輯磁盤。以RAID1(兩塊磁盤存儲相同的資料,在出現一塊磁盤故障的情況下,資料不丢失)為例,通過Linux核心中的軟體建立一個虛拟的塊裝置,而該塊裝置中記錄了底層對應的實體裝置及相關參數。
圖3 RAID1 示意圖
是以,從使用者層面來看就是一塊普通的磁盤裝置,而在底層卻是2個獨立的實體硬碟。當使用者向邏輯磁盤寫資料的時候,其中的軟體會通過參數進行計算,并将資料重新定向到底層的實體裝置。通過這種方法可以保證即使出現某個實體磁盤損壞,使用者的資料仍然完好無損。
除了上面說的RAID1外,還有很多RAID類型。不同的RAID類型實作不同的功能。比如RAID0實作條帶化,主要是提升性能;RAID1則是實作資料的備援,防止磁盤故障導緻的資料丢失;由于上述RAID隻能解決一方面的問題,是以有人講兩者結合,出現了RAID10和RAID01,這樣既能保證資料的可靠性,又能提升性能。
由于RAID1是一份資料寫到兩個裝置,是以隻有50%的有效資料。為了提高有效資料率,于是發明了RAID5和RAID6等類型。其中RAID5通過增加一個校驗資料來保證資料的可靠性,以5塊盤的RAID5為例,其中有效數占4塊盤的空間,有效資料80%。但是RAID5有個問題,就是一組磁盤中隻能壞一塊,如果損壞的磁盤超過1塊就會導緻資料丢失。RAID6的算法與RAID5類似,它的特點是可以容忍2塊磁盤故障。
在實作層面,Linux的RAID實作在使用者态和核心态都有涉及。其中使用者态主要進行RAID的管理,而核心态一方面配合使用者态進行RAID管理,另外一方面則實作對IO的處理,這部分才是RAID最為核心的内容。
圖4 軟體架構
對于基于SCSI實體磁盤的RAID來說,Linux環境下整個軟體架構如圖4所示。其中虛線以上的為使用者态的軟體子產品,虛線以下的為核心态的軟體子產品。這裡比較核心的是RAID公共層,在這裡主要建立md裝置,該裝置是一個邏輯裝置,也是使用者可以看到的RAID裝置。其下則是具體的RAID子產品,用于實作不同的RAID級别(算法)。
再往下就是通用SCSI驅動層了,也就是圖中的SCSI磁盤驅動這一層的内容。該層其實是SCSI系統的上層驅動(SCSI子系統分為上中下三層)。RAID子產品通過調用該層的資料通路接口就可以實作實體磁盤資料讀寫了。