美文网首页
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启动过程

    1.Linux组成 (1)Linux: kernel+rootfs kernel: rootfs:存放操作系统和应...

  • Linux 全阶段

    linux 1 一:Linux操作系统简述 Linux操作系统是由内核(Kernel)、外壳(Shell)、实...

  • 安卓回顾1

    1.android系统架构 Android系统架构(由下往上): Linux Kernel层: linux内核层,...

  • Android系统启动-综述

    一. 概述 Android系统底层基于Linux Kernel, 当Kernel启动过程会创建init进程, 该进...

  • Android 操作系统架构梳理

    架构分层 Android 系统架构从下往上依次分为 Linux kernel(Linux 内核),Hardware...

  • Linux系统架构及内核架构

    1. linux系统架构如下图所示: linux系统架构由硬件、kernel、系统调用、shell、c库、应用程序...

  • Linux时钟命令用法及演示

    linux时钟 linux时钟分为系统时钟和硬件时钟。系统时钟是指当前linux kernel中的时钟,而硬件时钟...

  • Android-系统-框架

    概述 Android基于Linux Kernel,但不是Linux,是Linux的一个变种; Android系统框...

  • Linux内核和架构, since 2020-11-05

    (2020.11.05 Thur)Linux系统分为内核(kernel)和应用程序两个部分。若细分,Linux系统...

  • Activity启动流程学习笔记

    Zygote是什么?有什么作用? Android系统底层基于Linux Kernel, 当Kernel启动过程会创...

网友评论

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

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