1. 概述
系統能夠隨機訪問固定大小資料片的裝置稱為塊裝置,這些資料片稱作塊。另一種基本的裝置類型是字元裝置。字元裝置按照位元組流的方式被有序訪問,像串口和鍵盤都屬於字元裝置。這兩種類型的裝置的根本區別在於它們是否可以被隨機訪問,換句話說,就是能否在訪問裝置時隨意從一個位置跳到另一個位置。
字元裝置僅僅需要控制一個位置--當前位置;而塊裝置訪問的位置必須在介質的不同區間前後移動,同時塊裝置對執行效能的要求很高。如何管理塊裝置和如何管理隊塊裝置的請求,該部分在核心中被稱為塊I/O層。
2. 解剖一個塊裝置
塊裝置中最小的可定址單元式扇區。扇區最常見大小事512位元組。軟體都會用到自己的最小邏輯可定址單元--塊。塊石檔案系統的一種抽象--只能基於塊來訪問檔案系統。雖然物理磁碟定址是按照扇區級來進行的,但是核心執行的所有磁碟操作都是按照塊進行的。所以,塊只能數倍於扇區的大小,但大小不能超過一個頁面。
扇區:裝置的最小定址單元,亦稱"硬扇區"或"裝置塊"
塊:檔案系統的最小定址單元,亦稱"檔案塊"或"I/O塊"
3. 緩衝區和緩衝區頭
當一個塊被調用記憶體時,它要儲存在一個緩衝區中。每個緩衝區與一個塊對應,它相當於是磁碟塊在記憶體中的表示。
所有這些資訊都和檔案系統的控制資訊密切交融,檔案系統的控制資訊儲存在超級快中,超級塊是一種包含檔案系統資訊的資料結構。由於核心在處理資料需要相關的控制資訊,所以每個緩衝區都有一個對應的描述符。該描述符用buffer_head結構體表示,被稱為緩衝區頭,在檔案<linux/buffer_head.h>中定義。
結構體中h_count域表示緩衝區的使用技術。在操作緩衝區頭之前,應該增加緩衝區頭的引用計數,確保該緩衝區托不會再被分配出去,當完成對緩衝區的操作後,就減少引用計數。
緩衝區頭的目的在於描述磁碟塊和實體記憶體緩衝區之間的映射關係。
可是,將緩衝區頭作為I/O操作單元帶來了兩個弊端:一是緩衝區頭是一個很大且不易控制的資料結構體。對核心來說,它更傾向於操作頁面結構,因為頁面操作起來更為簡便,效率更高。二是緩衝區頭僅能描述單個緩衝區,當作為所有I/O的容器使用時,緩衝區頭會迫使核心打斷對大塊資料的I/O操作,使其成為對多個buffer_head結構體進行操作。
4. bio結構體
目前核心中塊I/O操作的基本容器由bio結構體表示,定義在<linux/bio.h>中。該結構代表了正在現場(活動)的以片段(segment)鏈表形式組織的塊I/O操作。一個片段是一小塊連續的記憶體緩衝區。通過片段來描述緩衝區,即使一個緩衝區分散在記憶體的多個位置上,bio結構體也能對核心保障I/O操作的執行。像這樣的向量I/O就是所謂的聚散I/O。
bio結構體中最重要的幾個域是bi_io_ves、bi_vcnt、和bi_idx。
總之,每一個塊I/O請求都是通過一個bio結構體表示。每個請求包含一個或多個塊,這些塊儲存在bio_vec結構體數組中。
4.1. 緩衝區頭與bio結構體對比
bio結構體代表的是I/O操作,它可以包括記憶體中的一個或多個頁;另一方面,buffer_head結構體代表的是一個緩衝區,它描述的僅僅是磁碟中的一個塊。bio結構體是輕量級的,它描述的塊可以不需要連續儲存區,並且不需要分割I/O操作。
利用bio結構體代替buffer_head結構體的好處:
1) bio結構體很容易處理高端記憶體,因為它處理的是物理頁而不是直接指標
2) bio結構體既可以代表普通頁I/O,也可以代表直接I/O
3) bio結構體便於執行分散-集中塊I/O操作
4) bio結構體比緩衝區頭屬於輕量級的結構體。因為它只需要包含塊I/O操作所需的資訊,不用包含與緩衝區本身相關的不必要資訊
但是,還是需要緩衝區頭這個概念,畢竟它還要負責描述磁碟塊到頁面的映射。
5. 請求隊列
塊裝置將它們掛起的塊I/O請求存在請求隊列中,該隊列由request_queue結構體表示,定義在<linux/blkdev.h>中,包含一個雙向請求隊列以及相關控制資訊。通過核心中像檔案系統這樣高層的代碼將請求加入到隊列中。請求隊列只要不為空白,隊列對應的塊裝置驅動程式就會從隊列頭擷取請求,然後將其送入對應的塊裝置上去。
6. I/O發送器
磁碟定址是整個電腦中最慢的操作之一,為了最佳化定址操作(盡量縮短定址時間),核心既不會簡單地按請求接收次序,也不會立即將其提交給磁碟。相反,它會在提交前,先執行名為合并與排序的預操作,在核心中負責提交I/O請求的子系統稱為I/O發送器。
I/O發送器是將磁碟I/O資源分派給系統中所有掛起的塊I/O請求。具體的說,這種資源分派時通過將請求隊列中掛起的請求合并和排序來完成的。而進程發送器的作用是將處理器資源分派給系統中的運行進程。進程發送器和I/O發送器都是將一個資源虛擬給多個對象,對於進程發送器來說,處理器被虛擬並被系統中的運行進程共用。而I/O發送器虛擬塊裝置給多個磁碟請求,以便降低磁碟定址時間,確保磁碟效能的最佳化。
6.1. I/O發送器的工作
I/O發送器的工作是管理塊裝置的請求隊列。它決定隊列中的請求排列順序以及在什麼時刻派發請求道塊裝置。這樣做有利於減少磁碟定址時間,從而提高全域(可能存在對某些請求不公)輸送量。I/O發送器通過兩種方法來減少磁碟定址時間:合并與排序。
合并指將兩個或多個請求結合成一個新請求。當檔案系統提交請求到請求隊列--從檔案中讀取一個資料區,如果此時隊列已經存在一個請求,它訪問的磁碟扇區和當前請求訪問的磁碟扇區相鄰,那麼這兩個請求可以合并為對一個對單個和多個相鄰磁碟扇區操作的新請求。因此,合并請求顯然能減少系統開銷和磁碟定址次數。
假設在讀請求被提交給請求隊列的時候,隊列中並沒有其他請求需要操作相鄰的扇區,此時就無法將當前請求與其他請求合并,這是就需要排序。排序就是如果存在一個請求,它要操作的磁碟扇區位置與當前請求的比較接近,就讓這兩個請求在隊列上也相鄰,整個請求隊列將按扇區增長方向有序排列。這樣,通過保持磁頭以直線方向移動,縮短了所有請求的磁碟定址時間。
6.2. Linus電梯
在2.4核心中,Linus電梯時預設的I/O發送器,它能執行合并與排序處理。
合并:當有新的請求排入佇列時,它首先會檢查其他每一個掛起的請求是否可以和新請求合并。如果新請求正好連在一個現存的請求前,就是向前合并;相反如果新請求直接連在一個現存的請求後,就是向後合并。
排序:如果合并失敗,就需要尋找可能的插入點(新請求在隊列中的位置必須符合請求以扇區方向有序排序的原則)。如果找到,新請求就被插入該點;如果沒有合適的位置,就被插入隊列尾部。另外,如果發現隊列中有駐留時間過長的請求,那麼新請求將被加入到隊列尾部,即使插入後也要排序。
缺陷是該演算法並非是給等待了一段時間的請求提供實質性服務,這最終會導致請求饑餓現象發生。例如,一個對磁碟同一位置操作的請求可以造成較遠位置的其他請求永遠得不到啟動並執行機會。
6.3. 最終期限I/O發送器
最終期限(deadline)I/O發送器是為瞭解決Linus電梯鎖帶來的饑餓問題而提出的。
寫操作通常是在核心有空時才將請求提交給磁碟的,寫操作和提交它的應用程式非同步執行;讀操作具有同步性,並且彼此之間往往相互依靠,所以讀請求回應時間直接影響系統效能。因此,2.6核心新引入了期限I/O調用程式。注意,減少饑餓現象必須以降低全域輸送量為代價。
在期限I/O發送器中,每個請求都有一個逾時時間。預設下,讀請求的逾時時間是500毫秒,寫請求的逾時時間是5秒。該發送器有三個隊列:一是排序隊列,以磁碟物理位置為次序來維護請求隊列。當一個新請求遞交給排序隊列時,期限I/O發送器類似Linus電梯,合并和插入請求,同時也會以請求類型為依據將它們分別插入到讀請求FIFO隊列和寫請求FIFO隊列。如果在這兩個隊列中的請求逾時,那麼最終期限I/O發送器便從FIFO隊列中提取請求進行服務。
最終期限I/O發送器的實現在drivers/block/deadline-iosched.c中。
6.4. 預測I/O發送器
最終期限I/O發送器為了降低讀操作相應時間做了很多工作,但是它降低了系統輸送量。比如,系統處於繁重的寫操作期間,每次提交讀請求,I/O發送器就迅速處理讀請求,然後返回在執行寫操作,並且對每個讀請求都重複這個過程。其中兩次定址操作卻損害了系統全域輸送量。預測(Anticipatory) I/O發送器的目標就是在保持良好的讀回應時間的同時也能提供良好的全域輸送量。
預測I/O發送器的基礎是最終期限I/O發送器。預測I/O發送器耶實現了三個隊列(加上一個派發隊列),並為每個請求設定了逾時,它的主要改進是增加了預測啟發(anticipation-heuristic)能力。試圖減少在進行I/O操作期間,處理新到的讀請求所帶來的定址數量。與最終期限I/O發送器最大的不同在於,請求提交後並不直接返回處理其他請求,而是有意空閑片刻(預設6毫秒)。這幾毫秒,對應用程式來說是個提交其他讀請求的好機會,任何對相鄰磁碟位置操作的請求都會立刻得到處理。
6.5. 完全公正的排隊I/O發送器
完全公正的排隊I/O發送器(Complete Fair Queuing,簡稱CFQ)是為了專有工作負載設計的。CFQI/O發送器把進入的I/O請求放入特定的隊列中,這種隊列是根據引起I/O請求的進程組織的。在每個隊列中,剛進入的請求與相鄰的請求合并在一起,並進行插入分類,隊列由此按扇區方式能分類。CFQI/O發送器的差異在於每一個提交I/O請求的進程都有自己的隊列。
CFQI/O發送器以時間片輪轉調度隊列,從每個隊列中選取請求數(預設是4),然後進行下一輪調度。
I/O發送器實現在drivers/block/cfq-iosched.c中。
6.6. 空操作的I/O發送器
空操作(Noop) I/O發送器,基本是一個空操作,不做什麼事。空操作I/O發送器不進行排序,只做合并。這種演算法並不是沒有意義,因為它打算用在塊裝置。如果塊裝置只有一點活沒有尋道的負擔,就沒有必要進行排序。
空操作I/O發送器實現位於drivers/block/noop-iosched.c中,它是專為隨機訪問裝置而設計的。