通用塊層bio詳解


Linux Block 層在 Linux 內核設計之初就作為幾大子系統存在,當然這也是得益於他的前輩 Unix 等優秀的設計。作為 IO 子系統的中間層,他為上層輸出接口,為下層提供數據,像個勤勞的小蜜蜂,本文介紹通用塊層中的最具傳奇色彩的 bio,他就像是一個原子,是在整個 block 層的最小單位,不可分割。

bio 的組成

作為最小單位以及傳輸介質,那么具體應該長得如何?他又承載着那些信息?

struct bio { struct bio *bi_next; /* request queue link */ struct block_device *bi_bdev; unsigned int bi_flags; /* status, command, etc */ int bi_error; unsigned long bi_rw; /* 末尾 bit 表示 READ/WRITE, * 起始 bit 表示優先級 */ struct bvec_iter bi_iter; /* 當完成物理地址合並之后剩余的段的數量 */ unsigned int bi_phys_segments; /* * 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; /* 關聯 bio 的數量 */ atomic_t __bi_remaining; bio_end_io_t *bi_end_io; void *bi_private; unsigned short bi_vcnt; /* how many bio_vec's */ /* * Everything starting with bi_max_vecs will be preserved by bio_reset() */ unsigned short bi_max_vecs; /* max bvl_vecs we can hold */ /* 當前 bio 的引用計數,當該數據為 0 時才可以 free */ atomic_t __bi_cnt; /* pin count */ struct bio_vec *bi_io_vec; /* the actual vec list */ struct bio_set *bi_pool; /* * 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. * 表示跟在 bio 后面的數據集合 */ struct bio_vec bi_inline_vecs[0]; }; 

bio 結構體包含了大量的基礎信息,這些都是一個基本單元的屬性,他們代表着當前這個 bio 的狀態,比如是讀還是寫或者是一些特殊的操作命令等,在 bio 的尾部攜帶了一個 bi_inline_vecs 數組1,這就是一個 bio 數據所在部分,相當於這個結構體描述的都只是元數據部分,實際數據都包含在緊跟其后的 bio_vec 中,那么這個 bio_vec 何許人也?

最小數據單位 bio_vec

struct bio_vec { struct page *bv_page; unsigned int bv_len; unsigned int bv_offset; }; 

顧名思義,bio_vec 就是一個 bio 的數據容器,專門用來保存 bio 的數據,當然他是這個 bio 大集體的一個最小項,剛剛說了 bio 是通用塊層的最小集,而這個 bio_vec 則是組成 bio 數據的最小單位,他包含了一塊數據所在的頁,這塊數據所在的頁內偏移以及長度,通過這些信息就可以很清晰的描述數據具體位於什么位置,通過對這些數據的整合,可以將他們添加到 SGL(散列表) 中直接發送給后端硬件設備。

迭代器 bvec_iter

尋遍整個 bio 發現居然沒有攜帶需要下盤的扇區編號以及當前 bio 的大小,這個很尷尬,但確實如此,相當於 bio 作為一輛汽車,他攜帶了貨物但是沒告訴他目的地這不是完蛋了嗎?不是,真正的目的地保存在這個 bvec_iter 中,作為一個迭代器,自然他的使命就是用來遍歷 bvec,也就是 bio 數據區。那么他好比就是這輛 bio 汽車的貨物分揀員,自然我的目的地不必貼到車上,直接告訴分揀員也是可以的,因為后面的事情可不是這輛汽車再做,而是分揀員需要逐個卸貨的時候用。一起來看看迭代器長什么樣?

struct bvec_iter { sector_t bi_sector; /* device address in 512 byte sectors */ unsigned int bi_size; /* residual I/O count */ unsigned int bi_idx; /* current index into bvl_vec */ unsigned int bi_bvec_done; /* number of bytes completed in current bvec */ }; 

bio 的邏輯架構圖

關於 bi_io_vec 和 bi_inline_vecs 的關系

細心的朋友會發現,一個 bio 會有兩個字段用來描述 bio 攜帶的數據,對於沒有深入了解的人來講會比較困惑,其實也比較容易理解,bio 結構在申請內存的時候會多申請 4 個 bvec 的位置跟隨 bio 結構體上,這就是 bi_inline_vecs ,他是用來存放內聯數據(不能太多,因為申請了不用就會造成內存浪費),當往該 bio 注入數據時,小塊的 bio 會直接使用這個內聯的數據區域保存小於 4 個 bvec 的信息,而無需重新調用 kmalloc 申請內存,減少了一次內存申請操作,犧牲一小部分內存達到對小 bio 的加速(內存申請過程很長且相對較慢),然后為了兼容后端的其他接口,會直接將 bi_io_vec 指針直接指向 bi_inline_vecs 所在的位置,目的就是告訴后端,數據存放在 bi_inline_vecs 位置上。如果超過 4 個以上的 bvec 才足夠完整描述本次 IO 的全部數據,那么 bi_inline_vecs 字段是被直接忽略的,盡管他內存很早就申請好了,但是並不會被采用,而是需要重新自內存管理單元重新申請足額的內存保存這一次的 IO 數據,這就是前面說的一點點的內存浪費的原因。

 

from https://www.byteisland.com/linux-%E9%80%9A%E7%94%A8%E5%9D%97%E5%B1%82-bio-%E8%AF%A6%E8%A7%A3/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM