1.背景
1.1 block device 處理流程

-
VFS
VFS 將調(diào)用用戶系統(tǒng)調(diào)用API read() 處理轉(zhuǎn)換成對應的內(nèi)核系統(tǒng)調(diào)用服務程序,并將對應的read 操作重定向到具體的文件系統(tǒng)的操作實現(xiàn)。 -
FS
本質(zhì)上,文件系統(tǒng)最小訪問單位為block, 對于文件系統(tǒng)來說,文件由一個個block 構(gòu)成,文件系統(tǒng)通過file block number定位數(shù)據(jù)在文件中的位置(相對于文件的起始位置)。而磁盤同樣有一個個固定大小的block 構(gòu)成,文件系統(tǒng)通過logical block number(相對于磁盤開始位置)定位磁盤的位置。
1)FS根據(jù)文件系統(tǒng)最小可訪問單位block size,確定訪問的數(shù)據(jù)的內(nèi)容的 file block number(相對于文件起始位置);
2)根據(jù)1)要訪問的數(shù)據(jù)的file block number,調(diào)用具體文件系統(tǒng)的函數(shù),確定要訪問的數(shù)據(jù)映射在磁盤中的位置logical block number(相對于磁盤); -
Block layer
block layer 為所有類型的塊設(shè)備建立統(tǒng)一的模型,抽象底層HW 的細節(jié),為塊設(shè)備提供一個通用的視角。block layer 接收上層(文件系統(tǒng))對塊設(shè)備的磁盤操作請求,最終發(fā)送IO 情況 -
device driver
通過發(fā)送對應的command 給HW 以驅(qū)動實際數(shù)據(jù)的傳輸。
1.2 block layer
- block layer 可劃分為兩層:bio layer , request layer
image.png
2. block layer 工作原理
2.0 IO處理
i. 數(shù)據(jù)組織數(shù)據(jù)結(jié)構(gòu):bio/bio_vec
ii. bio 的處理
(1)bio 的 split 和 merge;
(2)bio 的bounce;
(3)bio 包裝成 request;
iii. request 的處理
(1)request 的分配和獲??;
(2)request 的 plug/unplug;
(3)request 的下發(fā);
2.1 核心數(shù)據(jù)結(jié)構(gòu) bio/bio_vec
bio :
i. main unit of I/O for the block layer and lower layers (ie drivers and stacking drivers)(官方解釋)
ii. bio結(jié)構(gòu)體用于表示IO 請求(1)數(shù)據(jù)在內(nèi)存和磁盤的位置, (2)數(shù)據(jù)大小以及(3)IO完成情況。
iii. bio 中重要的數(shù)據(jù)結(jié)構(gòu):bio_vec和bi_iter
bio_vec:描述了IO數(shù)據(jù)在內(nèi)存中的組織
bi_iter: 描述了IO數(shù)據(jù)在磁盤中位置以及當前IO數(shù)據(jù)的完成情況

struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
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 */
};
iiii. bio 和request 之間的關(guān)系
request:可由在硬盤位置連續(xù)的bio鏈接起來的

2.2 bio 的remap
i. 特定分區(qū)的bio 的bi_sector 會重映射remap 到 整個磁盤設(shè)備的 IO偏移;
ii. 另外DM 設(shè)備 mapped device 的bio 根據(jù)其映射規(guī)則需要remap 到target device的 bio.
/*
* Remap block n of partition p to block n+start(p) of the disk.
*/
static int blk_partition_remap(struct bio *bio)
{
struct block_device *p = bio->bi_bdev;
if (unlikely(should_fail_request(p, bio->bi_iter.bi_size)))
return -EIO;
if (bio_sectors(bio)) {
bio->bi_iter.bi_sector += p->bd_start_sect;
trace_block_bio_remap(bio, p->bd_dev,
bio->bi_iter.bi_sector -
p->bd_start_sect);
}
bio_set_flag(bio, BIO_REMAPPED);
return 0;
}
2.3 bio 的 split 和 merge
2.3.1 基本概念
bio的切分:將一個bio分成兩個bio;
bio的合并:將bio與IO請求request進行合并。
2.3.2 切分的依據(jù)
(1)q->limits.max_segments
表示請求隊列request-queue中每個IO請求request的最大segment數(shù)目
(2)q->limits.max_segment_size
表示請求隊列request-queue中每個segment最大的數(shù)據(jù)大小
(3)q->limits.max_sectors
表示請求隊列支持一次傳輸request的最大扇區(qū)數(shù)目即IO請求最大size。

2.3.3 拆分圖示


2.3.4 合并類型


2.3.5 合并過程-兩種機制的合并
(1)與plug/unplug 機制的plug->mq_list中的request進行合并。
(2)與 IO調(diào)度器機制的schedule list (定義IO調(diào)度器)或block mq的軟件queue機制ctx->rq_lists(沒有定義IO調(diào)度器)上request進行合并。
2.4 bio 包裝成 request
經(jīng)過bio split 和bio merge, bio 還在的話,會將bio 封裝到request 中,后續(xù)操作的對象由bio 轉(zhuǎn)向 request。
2.5 request 的分配和獲取
2.5.1 request 的分配
i. request 分配: 在初始化階段就分配好
ii. request 申請: runtime 時通過申請一個空閑的tag獲取一個空閑的request 。
iii. tag 和 request 一一對應。
xi. tag 管理:通過sbitmap_queue 管理
2.5.2 tag 管理-sbitmap_queue
sbitmap_queue 在bitmap 基礎(chǔ)之上增加
(1)bitmap分組管理的功能(sbitmap);
(2)等待功能,即在沒有無空閑bit時,會讓隊列一直等待
2.5.3 request 分配
i. 初始化階段,根據(jù)硬件的queue 數(shù)量和隊列深度,分配nr_hw_queues 個hctx, 每個hctx 對應一套blk_mq_tags,
ii. 每個HCTX,存在total_tags和reserved_tags兩個sbitmap,分別用于分配和釋放tags以及reserved_tags及與之對應的request。
iii. static_rq指向提前分配的request(包括(nvme/scsi)command以及底層驅(qū)動的私有結(jié)構(gòu))。提前分配的靜態(tài)request數(shù)目為nr_hw_queues * queue_depth。

2.5.4 request 獲取
先申請空閑的tag,若沒有空閑的tag,則通過等待機制等空閑的tag, 然后找到對應的request,
2.6 request 的 plug/unplug
i. plug/unplug機制,通過request的延遲下發(fā),為后續(xù)的IO增加合并和排序操作的可能,以減少request 數(shù)量。類似于蓄流和泄流。
ii. 工作流程:在開啟機制后,request 會放置到plug list中(plug過程),當達到某個條件時會將request 統(tǒng)一下發(fā)(unplug過程)。
iii. plug的時機
plug 通過block_start_plug()開啟的。對于每個線程描述符task_struct,存在成員blk_plug,若為空表示沒有使能PLUG,否則表示已使能PLUG。函數(shù)blk_start_plug()進行成員初始化,同時給task_struct->plug賦值。
在開啟BLOCK PLUG后,可以將通過函數(shù)blk_add_rq_to_plug()將IO請求加入到plug->mq_list中,且判斷所包含的IO請求是否來自多個隊列(通過成員multiple_queues)。
xi. unplug的時機
unplug的時機有三種:
(1)所積攢的IO數(shù)目達到BLK_MAX_REQUEST_COUNT(16)
(2)或遇到的IO大小超過BLK_PLUG_FLUSH_SIZE (128K)時;
(3)使用blk_finish_plug()主動沖刷;
2.7 IO 的下發(fā)路徑
IO 下發(fā)處理路徑
路徑一:使能了plug/unplug機制,此時會等待plug池中存取足夠的IO后統(tǒng)一往調(diào)度器插入IO,并選取IO下發(fā);
路徑二:沒有使能plug/unplug機制,此時會將IO插入調(diào)度器中,并選取IO下發(fā);
路徑三:跳過調(diào)度層,直接下發(fā)IO;

參考block layer 系列文章:https://blog.csdn.net/flyingnosky/article/details/121341392
