1. 程式人生 > >塊IO層(Linux核心原始碼分析)

塊IO層(Linux核心原始碼分析)

背景

本篇部落格重點分析塊IO層,試圖瞭解在linux原始碼中是如何實現塊IO的。

基本知識

塊裝置與字元裝置

塊裝置與字元裝置都是物理外設。簡單來說,塊裝置與字元裝置的最大區別在於塊裝置都隨機對資料片段進行讀寫的,而字元裝置都以順序對資料片段進行讀寫的。
比如磁碟、CD-ROM盤、快閃記憶體就屬於塊裝置。鍵盤、串列埠屬於字元裝置。

扇區與塊

扇區是塊裝置的最小定址單元,也就是說,是物理上的最小單元。而塊則不同,塊是檔案系統進行IO的最小單元,就是說,塊是邏輯上的最小單元。所以,對於程式設計者來說,可能更為在意的是塊的概念。
由此看來,扇區與塊的關係就很明瞭了,塊一定是扇區整數倍,而且一定要小於記憶體中一個頁的長度。通過扇區的大小是512位元組。

頁與塊

頁是記憶體中的一個基本管理單元。
塊是檔案系統的一種抽象,核心執行的所有磁碟操作都是按照塊進行的。

原始碼分析

我們可以先思考一下這個問題,從磁碟這樣的塊裝置中要讀取資料到記憶體中,從整體來看核心需要幹什麼?
首先是不是需要知道磁碟哪塊的資料要放入到記憶體中的哪個位置?
然後需要知道是如何將資料放入記憶體中,也就是以怎樣的方式?
知道了前面的之後,就需要考慮一個問題,核心中需要處理的塊IO一定不只一個,如何在一小段時間內來了許多需要進行塊IO的請求,這個時候該怎麼處理呢?難道不需要管理隨便的找塊IO請求進行處理嗎?
接下來我們就通過原始碼逐個分析這些問題。

buffer和buffer_head

從磁碟到記憶體,我們首先需要知道它們的對應關係是什麼,前面基礎裡提到,記憶體的基本管理單元是頁,磁碟的基本管理單元是塊(邏輯上來說)。所以說的具體點就是,哪個塊對應哪個頁。
當一個塊的資料被調入記憶體中,會有一個緩衝區與之對應,這個緩衝區就相當於塊在記憶體中的表示。一個緩衝區就是buffer。buffer_head結構體就是用來描述它們之間對應關係的。
接下來我們就直接看看這個結構體(位於include/linux/buffer_head中)
這裡寫圖片描述
70的b_bdev指向具體的塊裝置。
66中的b_blocknr指向邏輯塊號。我們就知道了塊是哪個塊。
64的b_page指向緩衝區位於哪個頁面之中。
68的b_data指向頁面中的緩衝區開始的位置。
67的b_size顯示了緩衝區的大小是多少。
從這幾個域我們就知道了磁碟與記憶體的對映關係了。在記憶體頁中,資料起始於b_data,到b_data+b_size。
其它還有一些域,如b_state顯示了buffer的狀態。b_count表示了這個buffer_head的被用次數,若被用了就需要給它進行原子加1操作,這樣其它地方就不能再重複使用了。
顯然,只知道記憶體與磁碟資料的對應關係還不行,我們還需要知道在記憶體中的具體的區域,也就是需要知道資料的容器。這個容器就是結構體bio。

bio結構體

我們先來直接看看bio結構體的原始碼。
這裡寫圖片描述
95行bi_io_vec指向了一個bio_vec結構體陣列。這個bio_vec結構體是一個比較關鍵的東西。我們看一下這個結構體。
這裡寫圖片描述
此結構體中的page大家應該比較熟悉,這個記憶體中的一個頁面。bv_offset表示頁中資料的偏移量,bv_len表示這個資料的長度。這下我們應該明白了。bio結構體擁有bio_vec結構體陣列,每個結構體包含了一個頁面中的一些“片段”。而這“片段”就是之前buffer_head描述的磁碟與記憶體中對應的塊的連續資料。換句話說,“片段”由連續的記憶體緩衝區組成。
現在再回過頭來看bio結構體。
既然知道了bi_io_vec指向bio_vec結構體陣列的首地址。那麼肯定就得知道陣列的長度與當前正操作的bio_vec的索引。
這就是72行bi_vcnt域和73行bi_idx域。bi_vcnt記錄陣列的數量,bi_idx記錄當前bi_vec結構體的索引數。
至此,磁碟與記憶體的對映關係的結構體我們瞭解了,塊在記憶體中的資料容器的結構體我們也瞭解了。現在有個問題,若核心中有多個塊IO請求,那麼這些IO請求就按照先來先處理的方式來進行嗎?很顯然這樣不合適,我們需要用一些IO排程程式來管理這些IO請求。具體的IO排程程式這裡就暫時先不講了。以後有時間再分析具體的IO排程程式。