前言:這個系列主要是記錄本身學習Linux塊IO子系統的過程,其中代碼分析皆基於Linux3.10.0版本,若有描述錯誤或不妥之處,敬請指出!
參考書籍:存儲技術原理分析--基於Linux 2.6內核源代碼(敖青雲著)
概述
塊設備(Block Device)是支持以固定長度的塊爲單位讀寫數據的存儲設備的統稱。塊設備一般是支持隨機訪問和尋道的硬件設備,如磁盤、軟盤、CDROM、內存區域等,或者是基於其餘塊設備之上的邏輯設備,如分區、MD(multi-disk)、Device Mapper等。
Linux內核中負責提交對塊設備IO請求的子系統被稱爲塊IO子系統,也被稱爲Linux塊層。塊IO子系統能夠被分爲下面三層:
- 通用塊層爲各類類型的塊設備創建了一個統一的模型,它主要的工做是接收上層發出的磁盤請求,並最終發出IO請求。該層隱藏了底層硬件塊設備的特性,爲塊設備提供了一個通用的抽象視圖。
- IO調度層:接收通用塊層發出的IO請求,緩存請求並試圖合併相鄰的請求(若是請求在磁盤上面是相鄰的),並根據設置好的算法,回調驅動層提供的請求處理函數,以處理具體的IO請求。
- 塊設備驅動層:具體的IO處理交給塊設備驅動層來完成,視塊設備的不一樣。對於大多數邏輯塊設備,塊設備驅動多是一個純粹的軟件層,並不須要直接和硬件打交道,只是機械地重定向IO。對於SCSI塊設備,其塊設備驅動即爲SCSI磁盤驅動,爲SCSI子系統的高層驅動,從而將塊IO子系統和SCSI子系統聯繫了起來。
塊IO子系統的通常IO處理流程是:上層調用通用塊層提供的接口向塊IO子系統提交IO請求,這些請求首先被放入IO調度層的調度隊列,通過合併和排序,最終將轉換後的IO請求派發到具體的塊設備的等待隊列,由後者的驅動進一步處理。這個過程涉及兩種形式的IO請求:一種是通用塊層的IO請求,即上層提交的IO請求,在Linux內核中以bio結構描述;另外一種是塊設備驅動層的IO請求,即通過IO調度層轉換後的IO請求,在Linux內核中以request描述。
IO簡單來說,就是將數據從磁盤讀入內存或者從內存寫入磁盤。可是,爲了提高系統性能,塊IO子系統採用了聚散IO(scatter/gather IO)這樣一種機制:將對磁盤上連續,但內存中不連續的的數據訪問由單次操做便可完成。也就是說,在單次操做中,從磁盤上的連續扇區中的數據讀取到幾個物理上不連續的內存空間或者將物理上不連續的內存空間的數據寫入磁盤的連續扇區。前者叫分散讀,後者叫彙集寫。
上層向通用塊層提交的IO請求是基於聚散IO的,它包含多個「請求段(segment)」,一個「請求段」是一段連續的內存區域,其中包含了和其餘「請求段」處於連續扇區的數據。
若是說SCSI磁盤驅動是鏈接塊IO子系統和SCSI子系統之間的橋樑,那麼也能夠這樣認爲:塊設備是聯繫塊IO子系統和文件系統之間的紐帶。bio表示上層發給通用塊層的請求,稱爲通用塊層請求,它關注的是請求的應用層面,即讀取(或寫入)哪一個塊設備,讀取(或寫入)多少字節的數據,讀取(或寫入)到哪一個目標緩衝區等、request表示通用塊層爲底層塊設備驅動準備的請求,稱做塊設備驅動層IO請求,或塊設備驅動請求,它關注的是請求的實施層面,即構造哪一種類型的SCSI命令。
一個塊設備驅動層請求可能包含多個通用塊層請求,也就是說,一次SCSI命令能夠服務多個上層請求,這就是所謂的請求合併。在Linux內核實現中,請求合併就是將多個bio鏈入到同一個request。此外,塊IO子系統還涉及不一樣的請求隊列,包括IO調度隊列和派發隊列。IO調度隊列是塊IO子系統用於對通用塊層請求進行合併和排序的隊列。派發隊列是針對塊設備驅動的,即塊IO子系統嚴格按照隊列順序提交塊設備驅動層請求給塊設備驅動處理。通常來講,每一個塊設備都有一個派發隊列,IO子系統又爲它內部維護了一個IO調度隊列,不一樣的塊設備能夠採用不一樣的IO調度算法。
通用塊層請求到達塊IO子系統時,首先在IO調度隊列中進行合併和排序,變成爲塊設備驅動層的請求。以後塊設備驅動層請求按照特定的算法被轉移到派發隊列,從而被提交到塊設備驅動。在Linux內核中,IO調度隊列和派發隊列都反應在request_queue結構中。理清各類請求隊列以及各類請求之間的關係,是透徹瞭解塊IO子系統的另外一個關鍵所在。