美文网首页
Linux kernel之Block IO系统

Linux kernel之Block IO系统

作者: 1哥 | 来源:发表于2023-08-25 17:36 被阅读0次

    1.背景

    1.1 block device 处理流程

    image.png
    • VFS
      VFS 将调用用户系统调用API read() 处理转换成对应的内核系统调用服务程序,并将对应的read 操作重定向到具体的文件系统的操作实现。
    • FS
      本质上,文件系统最小访问单位为block, 对于文件系统来说,文件由一个个block 构成,文件系统通过file block number定位数据在文件中的位置(相对于文件的起始位置)。而磁盘同样有一个个固定大小的block 构成,文件系统通过logical block number(相对于磁盘开始位置)定位磁盘的位置。
      1)FS根据文件系统最小可访问单位block size,确定访问的数据的内容的 file block number(相对于文件起始位置);
      2)根据1)要访问的数据的file block number,调用具体文件系统的函数,确定要访问的数据映射在磁盘中的位置logical block number(相对于磁盘);
    • Block layer
      block layer 为所有类型的块设备建立统一的模型,抽象底层HW 的细节,为块设备提供一个通用的视角。block layer 接收上层(文件系统)对块设备的磁盘操作请求,最终发送IO 情况
    • device driver
      通过发送对应的command 给HW 以驱动实际数据的传输。

    1.2 block layer

    • block layer 可划分为两层:bio layer , request layer
      image.png

    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

    相关文章

      网友评论

          本文标题:Linux kernel之Block IO系统

          本文链接:https://www.haomeiwen.com/subject/yybecrtx.html