工做學習中,沒有比圖表更好的東西了(雖然不少人在嘲笑PPT),尤爲是描述精準的圖表。當你想畫圖說明一個結構或一個流程時,必須對其已經充分理解。而在講解一張圖時,也必須對其有基本的理解。這真的不簡單,反正對我來講是這樣。關於Linux Storage架構,就有一張描述很精準的圖,「Linux Storage Stack Diagram」。這張圖總結的實在是太好了,Storage涉及的模塊都有描述,讓學習者能清晰的瞭解複雜的系統。本文試圖對該圖的各部分作個簡介,但不會涉及具體的實現。前端
https://www.thomas-krenn.com/...linux
圖中使用顏色來區分不一樣的組成部分。算法
VFS是Linux內核提供的一個虛擬文件系統層。VFS提供給用戶層一些標準的系統調用來操做文件系統,如open()、read()、write()等,讓用戶態應用無需關心底層的文件系統和存儲介質。同時VFS還要對底層文件系統進行約束,提供統一的抽象接口和操做方式。後端
Linux支持的文件系統衆多,大體能夠分爲如下幾類。緩存
Block Layer是Linux Storage系統中的中間層,鏈接着文件系統和塊設備。它將上層文件系統的讀寫請求抽象爲BIOs,經過調度策略將BIOs傳輸給設備。Block Layer包含圖中的藍綠色、黃色和中間BIOs傳輸過程。網絡
Linux系統在打開文件時能夠經過O_DIRECT標識來卻別是否使用Page cache。當帶有O_DIRECT時,I/O讀寫會繞過cache,直接訪問塊設備。不然,讀寫須要經過Page cache進行,Page cache的主要行爲以下。數據結構
BIO表明對Block設備的讀寫請求,在內核中使用一個結構體來描述。架構
struct bvec_iter { sector_t bi_sector; // 設備地址,以扇區(512字節)爲單位 unsigned int bi_size; // 傳輸數據的大小,byte unsigned int bi_idx; // 當前在bvl_vec中的索引 unsigned int bi_bvec_done; // 當前bvec中已經完成的數據大小,byte }; struct bio { struct bio *bi_next; // request隊列 struct block_device *bi_bdev; // 指向block設備 int bi_error; unsigned int bi_opf; // request標籤 unsigned short bi_flags; // 狀態,命令 unsigned short bi_ioprio; struct bvec_iter bi_iter; unsigned int bi_phys_segments; // 物理地址合併後,BIO中段的數量 /* * To keep track of the max segment size, we account for the * sizes of the first and last mergeable segments in this bio. */ unsigned int bi_seg_front_size; // 第一個可合併段的大小 unsigned int bi_seg_back_size; // 最後一個可合併段的大小 atomic_t __bi_remaining; bio_end_io_t *bi_end_io; // BIO結束時的回調函數,通常用於通知調用者該BIO的完成狀況 ...... unsigned short bi_vcnt; // bio_vec的計數 unsigned short bi_max_vecs; // bvl_vecs的最大數量 atomic_t __bi_cnt; // 使用計數 struct bio_vec *bi_io_vec; // vec list的指針 struct bio_set *bi_pool; ...... };
一個BIO構建完成後,就能夠經過generic_make_request()來建立傳輸Request,將Request加入到請求隊列中。請求隊列在內核中有結構體request_queue來描述,它包含一個雙向請求鏈表以及相關控制信息。請求鏈表中每一項都是一個Request,Request由BIOs組成,BIO中又可能包含不一樣的Segment。由於一個BIO只能連續的磁盤塊,但一個Request可能不連續的磁盤塊,因此一個Request可能包含一個或多個BIOs。儘管BIO中的磁盤塊是連續的,但它們在內存中多是不連續的,因此BIO中可能包含幾個Segments。app
讀寫數據組織成請求隊列後,就是訪問磁盤的過程,這個過程由IO調度完成。BIOs訪問的指定的磁盤扇區,首先要進行尋址的操做。尋址就是定位磁盤磁頭到特定塊上的某個位置,這個過程相對來講很慢。爲了優化尋址操做,內核既不會簡單地按請求接收次序,也不會當即將其提交給磁盤,而是在提交前,先執行名爲合併與排序的預操做,這種預操做能夠極大地提升系統的總體性能。這就是IO調度須要完成的工做。socket
當前內核中,支持兩種模式的IO調度器:single-queue和multi-queue。single-queue在圖中標識爲「I/O Scheduler」,multi-queue標識爲blkmq。兩者應該都是Scheduler,只是請求的組織方式不一樣。
single-queue經過合併和排序來減小磁盤尋址時間。合併指將多個連續請求合成一個更大的IO請求,以便充分發揮硬件性能。排序使用電梯調度,將整個請求隊列將按扇區增加方向有序排列。排列的目的不只是爲了縮短單獨一次請求的尋址時間,更重要的優化在於,經過保持磁盤頭以直線方向移動,縮短了全部請求的磁盤尋址的時間。目前single-queue使用的調度策略包括:noop、deadline、cfq等。
早先的內核只有single-queue,當時存儲設備主要時HDD,HDD的隨機尋址性能不好,single-queue就能夠知足傳輸需求。當SSD發展起來後,它的隨機尋址性能很好,傳輸的瓶頸就轉移到請求隊列上。結合多核CPU,multi-queue被設計出來。multi-queue爲每一個CPU core或socket配置一個Software queue,這也解決了single-queue中多核鎖競爭的問題。若是存儲設備支持並行多個Hardware dispatch queues,傳輸性能又會大幅度提高。目前multi-queue支持的調度策略包括:mq-deadline、bfq、kyber等。
設備文件是Linux系統訪問硬件設備的接口,驅動程序將硬件設備抽象爲設備文件,以便應用程序訪問。設備驅動加載時在/dev/下建立設備文件描述符,若是是Block設備,同時會建立一個軟連接到/dev/block/下,並根據設備號來命名。圖中將Block設備分爲如下幾類。
圖中橙色部分表示了Block設備所依賴的技術實現,多是硬件規範的軟件實現,也多是一種軟件架構。圖中把SCSI和LIO單獨圈出來,由於這兩部分相對比較複雜。SCSI包含的硬件規範不少,最經常使用的是經過libata來訪問HDD和SSD。
LIO(Linux-IO)是基於SCSI engine,實現了SCSI體系模型(SAM)中描述的SCSI Target。LIO在linux 2.6.38後引入內核,其支持的SAN技術包括Fibre Channel、FCoE、iSCSI、iSER 、SRP、USB等,同時還能爲本機生成模擬的SCSI設備,以及爲虛擬機提供基於virtio的SCSI設備。LIO使用戶可以使用相對廉價的Linux系統實現SCSI、SAN的各類功能,而不用購買昂貴的專業設備。能夠看到LIO的前端是Fabric模塊(Fibre Channel、FCoE、iSCSI等),用來訪問模擬的SCSI設備。Fabric模塊就是實現SCSI命令的傳輸協議,例如iSCSI技術就是把SCSI命令放在TCP/IP中傳輸,vhost技術就是把SCSI命令放在virtio隊列中傳輸。LIO的後端實現了訪問磁盤數據的方法。FILEIO經過Linux VFS來訪問數據,IBLOCK訪問Linux Block設備,PSCSI 直接訪問SCSI設備,Memory Copy RAMDISK用來放訪問模擬SCSI的ramdisk。
圖中天藍色部分,就是實際的硬件存儲設備。其中virtio_pci、para-virtualized SCSI、VMware's para-virtualized scsi是虛擬化的硬件設備。