1. 程式人生 > >bio,request,request_queue的關係

bio,request,request_queue的關係

通用塊層的核心資料結構稱為bio描述符,它描述了塊裝置的io操作。每一個bio結構都包含一個磁碟儲存區識別符號(儲存區中的起始扇區號和扇區數目)和一個或多個描述與IO操作相關的記憶體區段(bio_vec陣列)

bio結構中的欄位

/*
 * main unit of I/O for the block layer and lower layers (ie drivers and
 * stacking drivers)
 */
struct bio {
	struct bio		*bi_next;	/* request queue link 連結到請求佇列中的下一個bio*/
	struct block_device	*
bi_bdev; /* 指向塊裝置描述符的指標 */ unsigned int bi_flags; /* status, command, etc bio的狀態標誌*/ int bi_error; unsigned long bi_rw; /* bottom bits READ/WRITE, I/O操作標誌(低位是讀寫位,高位是優先順序) * top bits priority */ struct bvec_iter bi_iter; /* Number of segments in this BIO after * physical address coalescing is performed. */
unsigned int bi_phys_segments; //合併之後bio中物理段的數目 /* * To keep track of the max segment size, we account for the * sizes of the first and last mergeable segments in this bio. */ unsigned int bi_seg_front_size; unsigned int bi_seg_back_size; atomic_t __bi_remaining; bio_end_io_t *bi_end_io;
//bio的I/O操作結束時呼叫的方法 void *bi_private; //通用塊層和塊裝置驅動程式的I/O完成方法使用的指標 #ifdef CONFIG_BLK_CGROUP /* * Optional ioc and css associated with this bio. Put on bio * release. Read comment on top of bio_associate_current(). */ struct io_context *bi_ioc; struct cgroup_subsys_state *bi_css; #endif union { #if defined(CONFIG_BLK_DEV_INTEGRITY) struct bio_integrity_payload *bi_integrity; /* data integrity */ #endif }; unsigned short bi_vcnt; /* how many bio_vec's bio的引用計數器*/ /* * Everything starting with bi_max_vecs will be preserved by bio_reset() */ unsigned short bi_max_vecs; /* max bvl_vecs we can hold bio_vec陣列中允許的最大段數*/ atomic_t __bi_cnt; /* pin count */ struct bio_vec *bi_io_vec; /* the actual vec list 存放段的陣列*/ struct bio_set *bi_pool; //備用的bio記憶體池 /* 為了避免重複申請少量的bio_vec,使用內聯一定數量的bio_vec陣列,將它放在bio結構的尾部。 * We can inline a number of vecs at the end of the bio, to avoid * double allocations for a small number of bio_vecs. This member * MUST obviously be kept at the very end of the bio. */ struct bio_vec bi_inline_vecs[0]; /*一般一個bio就一個段,bi_inline_vecs就 可滿足,省去了再為bi_io_vec分配空間*/ };

bio中的每個段是由一個bio_vec資料結構描述的,bio_vec資料結構如下

//bio段就是描述所要讀或寫的資料在記憶體中位置
struct bio_vec{
     struct page* bv_page //指向段在頁框描述符的指標
     unsigned int bv_len //段的位元組長度
     unsigned int bv_offset //頁框中資料的偏移量
} 

bio中的bi_io_vec欄位指向bio_vec陣列的第一個元素,bi_vcnt則說明了陣列當前元素的個數,而bi_max_vecs則限定了陣列的長度。 下面兩幅圖可以很好的說明bio與bio_vec的關係

在通用塊層啟動一次新的IO操作時,會呼叫bio_alloc函式分配一個新的bio結構,bio是由slab分配器分配的。核心同時也為bio_vec結構分配記憶體池。

bio與bio段的關係

一個bio可能有很多個bio段,這些bio段可能在記憶體上不連續(位於不同的頁),但他們在磁碟上對應的位置是連續的。一般上層構建bio的時候都是隻有一個bio段,可以參考_submit_bh函式。

在塊io操作期間bio的內容一直保持更新,例如,塊裝置驅動在一次分散聚集DMA操作中不能一次完成全部資料的傳送,那麼bio的bi_idx就會更新來指向待傳送的第一個段。

bio:代表了一個io請求

request:一個request中包含了一個或多個bio,為什麼要有request這個結構呢?它存在的目的就是為了進行io的排程。通過request這個輔助結構,我們來給bio進行某種排程方法的排序,從而最大化地提高磁碟訪問速度。

request_queue:每個磁碟對應一個request_queue.該佇列掛的就是request請求。

具體如下圖:(有顏色方框頭表示資料結構的名字)

在新視窗開啟

請求到達block層後,通過generic_make_request這個入口函式,在通過呼叫一系列相關的函式(具體參見我另一篇部落格)把bio變成了request。具體的做法如下:如果幾個bio要讀寫的區域是連續的,即積攢成一個request(一個request上掛多個連續的bio,就是我們通常說的“合併bio請求”),如果一個bio跟其他的bio都連不上,那它就自己建立一個新的request,把自己掛在這個request下。當然,合併bio的個數也是有限的,這個可以通過配置檔案配置。

對於上段補充一點:上層的一次請求可能跨越了多個扇區,形成不連續的扇區段,那麼該請求構造的每個bio對應著一個連續的扇區段。故一個請求可以構造出多個bio。

合併後的request放入每個裝置對應的request_queue中。之後裝置驅動呼叫peek_request從request_queue中取出request,進行下一步處理。

這裡要注意的是,在實現裝置驅動時,廠家可以直接從request_queue中拿出排隊好的request,也可以實現自己的bio排隊方法,即實現自己的make_request_fn方法,即直接拿檔案系統傳來的bio來自己進行排隊,按需設計,想怎麼排就怎麼排,像ramdisk,還有很多SSD裝置的firmware就是自己排。(這裡就和我另一篇部落格,說到為什麼generic_make_request要設計成一層遞迴呼叫聯絡起來)。

網上有個問答也是我的疑惑:這裡我寫一遍,加強記憶

bio結構中有bio_vec陣列結構,該結構的的陣列可以指向不同的page單元,那為什麼不在bio這一級就做了bio合併工作,即把多個bio合併成一個bio,何必加入一個request這麼麻煩?

答:每個bio有自己的end_bio回撥,一旦一個bio結束,就會對自己進行收尾工作,如果合併了,或許有些bio會耽誤,靈活性差。