來自:
https://in1t.top/2020/06/04/linux%E5%86%85%E6%A0%B8%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB-%E5%9D%97%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8/
開始 fs 模組之前,我發現如果對塊裝置/字元裝置的驅動程式不瞭解的話,讀 fs 程式碼時會困難重重。為了簡化問題,本文及之後的 fs 模組都將只記錄關於塊裝置(特指硬碟)的程式碼,先弄懂一個,剩下的讀起來就輕鬆了。閱讀本文或許會一頭霧水,但和下篇文章聯絡起來看就會清楚許多了(x
塊裝置操作方式(以讀資料為例)
提到 I/O 先來看一張圖:
當程式需要從硬碟中讀取資料(read 系統呼叫)時,緩衝區管理程式會先查詢該資料塊是否已經讀入到緩衝區中。如果是,則直接將該緩衝頭(涉及高速緩衝的管理方式,下篇文章將會記錄)返回並喚醒等待此資料塊的程序;否則呼叫 ll_rw_block 函式,告訴塊裝置驅動程式(核心程式碼)現在需要讀資料塊,該函式就會為其建立一個請求項,並掛入相應裝置的請求佇列,同時發出請求的程序會被掛起(不可中斷睡眠態)。
當請求被處理時,裝置控制器根據請求項中的引數,向硬碟驅動器傳送讀指令,硬碟驅動器就會將資料讀取到裝置控制器的緩衝區中(注意此時原發出讀盤請求的程序已被掛起,CPU 正在被其他程序佔用)。當裝置控制器檢測到資料讀取完畢,就會產生一箇中斷請求訊號發往 CPU,CPU 在硬碟中斷處理程式 hd_interrupt 中呼叫 read_intr 函式將資料從裝置控制器的緩衝區搬到記憶體的高速緩衝區中,並讓裝置控制器開始處理下一個請求(如果有的話)。最後核心將高速緩衝中的資料複製到呼叫 read 函式時第二個引數指向的地址中去。用一張圖來總結:
請求項與請求佇列
請求項
請求項的資料結構如下:
1
|
// kernel/blk_drv/blk.h Line 23
|
為什麼請求項已經可以透過 next 指標構成單項鍊表了,還需要一個陣列來維護呢?採用陣列加連結串列結構其實是為了滿足兩個目的:
- 陣列結構使得在搜尋空閒請求項的時候可以進行迴圈操作,搜尋訪問時間複雜度為常數
- 連結串列結構是為了滿足電梯演算法插入請求項的操作
請求佇列
對於各種塊裝置,核心使用塊裝置表 blk_dev 來管理,每種塊裝置在塊裝置表中佔有一項,相關資料結構如下:
1
|
// blk.h Line 45
|
再來透過一張圖直觀地感受這些資料結構之間的關係:
透過之前的描述不難看出,硬碟裝置有 4 個請求,軟盤裝置有 1 個請求,虛擬盤暫無請求。下面正式開始塊裝置(僅硬碟)驅動程式部分原始碼的閱讀
blk.h
1
|
// Line 1 |