Linux kernel之Block IO系统

2023-08-25  本文已影响0人  1哥

1.背景

1.1 block device 处理流程

image.png

1.2 block layer

2. block layer 工作原理

2.0 IO处理

i. 数据组织数据结构: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 的下发;

2.1 核心数据结构 bio/bio_vec

bio :
i. main unit of I/O for the block layer and lower layers (ie drivers and stacking drivers)(官方解释)
ii. bio结构体用于表示IO 请求(1)数据在内存和磁盘的位置, (2)数据大小以及(3)IO完成情况
iii. bio 中重要的数据结构:bio_vec和bi_iter
bio_vec:描述了IO数据在内存中的组织
bi_iter: 描述了IO数据在磁盘中位置以及当前IO数据的完成情况

bio/bio_vec/bi_iter图示.png
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 之间的关系
request:可由在硬盘位置连续的bio链接起来的

image.png

2.2 bio 的remap

i. 特定分区的bio 的bi_sector 会重映射remap 到 整个磁盘设备的 IO偏移;
ii. 另外DM 设备 mapped device 的bio 根据其映射规则需要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 切分的依据
(1)q->limits.max_segments
表示请求队列request-queue中每个IO请求request的最大segment数目
(2)q->limits.max_segment_size
表示请求队列request-queue中每个segment最大的数据大小
(3)q->limits.max_sectors
表示请求队列支持一次传输request的最大扇区数目即IO请求最大size。

image.png

2.3.3 拆分图示

拆分前.png
拆分后.png

2.3.4 合并类型

前向合并.png
后向合并.png

2.3.5 合并过程-两种机制的合并
(1)与plug/unplug 机制的plug->mq_list中的request进行合并。
(2)与 IO调度器机制的schedule list (定义IO调度器)或block mq的软件queue机制ctx->rq_lists(没有定义IO调度器)上request进行合并。

2.4 bio 包装成 request

经过bio split 和bio merge, bio 还在的话,会将bio 封装到request 中,后续操作的对象由bio 转向 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 基础之上增加
(1)bitmap分组管理的功能(sbitmap);
(2)等待功能,即在没有无空闲bit时,会让队列一直等待
2.5.3 request 分配
i. 初始化阶段,根据硬件的queue 数量和队列深度,分配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以及底层驱动的私有结构)。提前分配的静态request数目为nr_hw_queues * queue_depth。

image.png

2.5.4 request 获取
先申请空闲的tag,若没有空闲的tag,则通过等待机制等空闲的tag, 然后找到对应的request,

2.6 request 的 plug/unplug

i. plug/unplug机制,通过request的延迟下发,为后续的IO增加合并和排序操作的可能,以减少request 数量。类似于蓄流和泄流。
ii. 工作流程:在开启机制后,request 会放置到plug list中(plug过程),当达到某个条件时会将request 统一下发(unplug过程)。
iii. plug的时机
plug 通过block_start_plug()开启的。对于每个线程描述符task_struct,存在成员blk_plug,若为空表示没有使能PLUG,否则表示已使能PLUG。函数blk_start_plug()进行成员初始化,同时给task_struct->plug赋值。
在开启BLOCK PLUG后,可以将通过函数blk_add_rq_to_plug()将IO请求加入到plug->mq_list中,且判断所包含的IO请求是否来自多个队列(通过成员multiple_queues)。
xi. unplug的时机
unplug的时机有三种:
(1)所积攒的IO数目达到BLK_MAX_REQUEST_COUNT(16)
(2)或遇到的IO大小超过BLK_PLUG_FLUSH_SIZE (128K)时;
(3)使用blk_finish_plug()主动冲刷;

2.7 IO 的下发路径

IO 下发处理路径
路径一:使能了plug/unplug机制,此时会等待plug池中存取足够的IO后统一往调度器插入IO,并选取IO下发;
路径二:没有使能plug/unplug机制,此时会将IO插入调度器中,并选取IO下发;
路径三:跳过调度层,直接下发IO;

image.png

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

上一篇下一篇

猜你喜欢

热点阅读