詳談塊裝置的內部框架

2021-06-18 14:21:31 字數 4794 閱讀 6629

塊裝置提出原因:

對於塊裝置的操作:讀取塊->修改讀出的資料-->擦除塊-->把資料寫入塊。為了提高效率,我們對於塊裝置採取一種措施:先合併請求,然後在根據合併後的請求來操作塊裝置,這樣可以使得塊裝置的操作次數降到最低。例如對同一塊的不同扇區讀取n此,通過和並就變為1次,那麼對於前面提到的四個步驟可以得到很高的效率。

塊裝置h和使用者互動:使用者層-->檔案系統--->塊裝置驅動。

基於塊裝置提出的原因:就是要實現塊裝置合併相同請求之後再處理,所用的演算法叫做:電梯排程演算法。

第一件事情就是討論核心如何合併和處理請求的。

1、如何合併請求?合併成功的條件是什麼?

使用請求佇列和bio結構體:

struct request_queue

struct request

struct bio

struct bio_vec ;

每個請求就有乙個bio結構體,如果請求滿足合併要求,就通過bio的結構體的bi_next把這些同請求bio鏈結為一條鍊錶,這條鍊錶由乙個請求佇列request 的bio成員指標指向。

所以我們操作請求的時候就可以通過操作request來操作滿足同一請求的bio了,這就是合併的結果。並且請求佇列也有多條,也是鍊錶,煉表頭由request_queue->queue_head指向。

當然,對於塊裝置的資料操作要先封裝為bio結構體,把bio結構體和其他待處理bio結構體進行合併,如果可以合併,萬事大吉;如果不行那麼就需要產生乙個新的請求request了,最後就是掃面所有request鍊錶,執行裡面的bio了。

如何封裝乙個bio:

這個核心已經幫我們解決,當我們操作乙個快裝置節點,那麼檔案系統會轉換為對ll_rw_block的操作,在這個函式裡面會呼叫submit_bh幫我們封裝乙個bio並提交,具體見下面:

int submit_bh(int rw, struct buffer_head * bh)  //bh->b_data指向資料區,bh->b_size表示buffer_head對映的大小,bh->b_page指向buffer_head對映的頁位址

bio = bio_alloc(gfp_noio, 1);  //分配乙個bio結構體

bio->bi_io_vec[0].bv_page = bh->b_page;  //指向buffer_head對映的頁位址

bio->bi_io_vec[0].bv_len = bh->b_size; 

bio->bi_io_vec[0].bv_offset = bh_offset(bh);  //有效資料相對於buffer_head對映的頁位址的偏移值

submit_bio(rw, bio);  //提交bio

2、提交之後,我們接下來就需要在提交函式裡合併同個請求:

submit_bio(int rw, struct bio *bio)

generic_make_request(bio);

__generic_make_request(bio);

ret = q->make_request_fn(q, bio);  //合併請求函式__make_request,見下面

對於make_request_fn需要我們使用者呼叫blk_init_queue初始化佇列,該初始化過程重點有提供了(很重要):

init_timer(&q->unplug_timer);  //初始化定時器,用於觸發執行請求函式

init_work(&q->unplug_work, blk_unplug_work);  //工作佇列:blk_unplug_work就是請求執行函式

q->request_fn= rfn; //請求處理函式,後面有用到

blk_queue_make_request(q, __make_request); 

q->make_request_fn = mfn; //這個就是用來合併請求函式,看看如何合併的。

__make_request

el_ret = elv_merge(q, &req, bio);  //使用電梯演算法嘗試合併bio,如果失敗就要新增新請求佇列,見下面

init_request_from_bio(req, bio);  //初始化新佇列,並且把bio掛上去

add_request(q, req);  //請求佇列放到鍊錶

具體操作可參考文章:這篇文章分析很好。

這裡說說合併成功的要求是什麼:

if ((bio->bi_rw & req_discard) != (rq->bio->bi_rw & req_discard))  //資料沒有被設定為丟棄標誌

return 0;

if (bio_data_dir(bio) != rq_data_dir(rq))  //讀寫方向一樣

return 0;

if (rq->rq_disk != bio->bi_bdev->bd_disk || rq->special) //合併的請求屬於同乙個裝置

return 0;

還有其他,看不懂,具體看elv_rq_merge_ok函式;

3、處理請求,所有的請求佇列是連成一條鍊錶的,具體看:

struct request

struct request_queue

執我們在處理請求的時候就遍歷鍊錶,然後把鍊錶的每乙個請求裡的所有bio按統一請求處理:

第一種情況:(每一次新建請求佇列的時候,可能會觸發。條件是請求佇列數達到q->unplug_thresh)

__make_request是我們在把bio合併到請求的時候呼叫的。當我們的請求佇列佇列數達到q->unplug_thresh書目的時候,就呼叫執行請求:

__make_request

add_request(q, req);

__elv_add_request(q, req, elevator_insert_sort, 0);

elv_insert(q, rq, where);

if (nrq >= q->unplug_thresh)

__generic_unplug_device(q);

q->request_fn(q);

generic_unplug_device

__generic_unplug_device(q);

q->request_fn(q); //這個在前面說過了,見blk_init_queue函式

blk_init_queue函式的rfn引數。  這個函式屬於程式設計者提供的。

第二種情況:

猜想要有個定時器,時間一到就不過請求佇列過少都必須執行(如果我們的請求佇列沒有達到那麼大),這是必然的,具體如下:

我們在初始化佇列的時候:

blk_init_queue

blk_init_queue_node

blk_alloc_queue_node

init_work(&q->unplug_work, blk_unplug_work);//工作佇列

工作佇列啟動條件:

void blk_unplug_timeout(unsigned long data)

blk_unplug_timeout是乙個定時函式,

在blk_init_queue呼叫的時候呼叫:

blk_queue_make_request(q, __make_request);

q->unplug_timer.function = blk_unplug_timeout;

那什麼時候啟動?當然在bio合併到請求的時候:

__make_request

blk_plug_device(q);

mod_timer(&q->unplug_timer, jiffies + q->unplug_delay);

總結一下:

對於塊裝置而言,使用者空間的資料經過檔案系統傳給塊裝置驅動的時候,檔案系統會把操作通過ll_rw_block函式吧這些資料封裝為乙個bio,然後通過__make_request函式去遍歷request_queue的queue_head煉表頭指向的清求佇列request,尋求和相同請的bio合併,如果bio可以合併到佇列的就ok,合併如果不能的就新建新的請求佇列並且串入之前存在的佇列鍊錶。對於請求佇列的執行在__make_request函式裡啟動乙個定時器,這個定時器時間一到就執行請求佇列,當然還有一種情況之情請求佇列就是佇列數達到unplug_thresh時候也會執行。

到這裡很清楚,對於初始化佇列函式blk_init_queue可以說是我們程式設計的核心函式:

init_timer(&q->unplug_timer);  //初始化定時器,用於觸發執行請求函式

init_work(&q->unplug_work, blk_unplug_work);  //工作佇列:blk_unplug_work就是請求執行函式

q->request_fn = rfn; //請求處理函式

blk_queue_make_request(q, __make_request); 

q->make_request_fn = mfn; //這個就是__make_request函式。

對於塊裝置的程式設計:

1、分配(alloc_disk)、設定(主次裝置號、操作函式集)和提交結構體gendisk(add_disk);

2、blk_init_queue函式呼叫,傳入引數有乙個是請求處理函式

3、實現第二步的請求處理函式。

4、設定容量

5、設定柱面、扇區資訊。

總結:對於裝置檔案的操作最終轉為請求佇列上的bio結構體,

執行真正的操作就是執行操作請求佇列鍊錶的每條請求佇列上的所有相同請求的bio。(在我們的請求處理函式裡實現),例子如下:

static void do_viocd_request(struct request_queue *q)

rwreq++;}}

塊裝置驅動框架

以ram模擬乙個分割槽為例 框架入口原始檔 armblock.c 可根據入口原始檔,再按著框架到核心走一遍 核心版本 linux 2.6.22.6 硬體平台 jz2440 以下是驅動 armblock.c include include include include include include...

塊裝置驅動程式框架

韋東山老師幫我們把框架搭建起來了,我們先來看一下 框架 檔案的讀寫 檔案系統 vfat,ext2,ext3,yaffs2,jffs2 把檔案的讀寫轉換為扇區的讀寫 ll rw block 扇區的讀寫 1.把 讀寫 放入佇列 2.呼叫佇列的處理函式 優化 調順序 合併 塊裝置驅動程式 硬體 硬碟,fl...

字元裝置和塊裝置的區別

系統中能夠隨機 不需要按順序 訪問固定大小資料片 chunks 的裝置被稱作塊裝置,這些資料片就稱作塊。最常見的塊裝置是硬碟,除此以外,還有軟盤驅動器 cd rom驅動器和快閃儲存器等等許多其他塊裝置。注意,它們都是以安裝檔案系統的方式使用的 這也是塊裝置的一般訪問方式。另一種基本的裝置型別是字元裝...