Linux块设备层已逐步切换到multiqueue , Linux5.0以后单队列代码已被完全移除。multiqueue核心思路是为每个CPU分配一个软件队列,为存储设备的每个硬件队列分配一个硬件派发队列,再将软件队列与硬件派发队列做绑定,减少了IO请求过程中对锁的竞争,从而提高IO性能。网上已有很多针对block multiqueue架构的文章,推荐这篇《Linux Block IO: Introducing Multi-queue SSD Access on Multi-core Systems》。这一系列主要梳理一下block multiqueue的实现,基于Linux5.x。

block mltiqueue 架构,图片引用自《Linux Block IO: Introducing Multi-queue SSD Access on Multi-core Systems》

核心数据结构

bio

block层的IO最小单元,负责在block层以及其上下层之间传递数据。bio数据结构中除了包含IO的一些基础信息,操作类型(读/写/discard...),作用的磁盘/分区,IO优先级,状态等等。bio最重要的信息是描述了磁盘上一段连续数据与内存页的映射关系,主要由bi_vcnt,bi_io_vec,bi_iter 描述。

struct bio {struct bio       *bi_next;   /* request queue link */    //一个request有多个bio时,以单向链表组织biostruct gendisk      *bi_disk;...struct bvec_iter    bi_iter;    //bvec迭代器...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 */atomic_t      __bi_cnt;   /* pin count */  //bio 引用计数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.*/struct bio_vec     bi_inline_vecs[0];
};

struct bio_vec :是描述磁盘数据与内存页映射的最小单元,每个bio_vec以offset & size形式描述一个内存页中的一段数据。每个bio 通常有多个bio_vec,bio_vec以数组的形式存储,*bi_io_vec 指向数组头,bi_vcnt记录了其个数。bi_inline_vecs是为每个bio预分配的bio_vec数组,包括4个bio_vec,若bio需要分配的bio_vec小于4可直接使用bi_inline_vecs,不需要再额外申请内存,此时bi_io_vec会指向bi_inline_vecs。

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

struct bvec_iter:用于遍历bi_io_vec数组,可由bio_for_each_segment等宏来使用。同时bio内的bvec_iter也记录了当前IO请求在磁盘上的起始扇区以及处理进度。

struct bvec_iter {sector_t       bi_sector;  /* device address in 512 bytesectors */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 incurrent bvec */
};

request

IO调度的最小单元,描述一段物理地址连续的IO请求,对应一个或者多个连续的bio。操作类型,作用磁盘,优先级等数据由相关的bio转换而来,还包含了调度器及软硬件队列管理request时需要使用的链表节点结构。

struct request {struct request_queue *q;        //所属的request_queuestruct blk_mq_ctx *mq_ctx;        //所属的软件队列struct blk_mq_hw_ctx *mq_hctx;    //所属的硬件队列unsigned int cmd_flags;      /* op and common flags */req_flags_t rq_flags;int tag;int internal_tag;/* the following two fields are internal, NEVER access directly */unsigned int __data_len;   /* total data len */   //所有bio请求数据之和 sector_t __sector;     /* sector cursor */    //请求的起始扇区,等于第一个bio的起始扇区struct bio *bio;        //起始biostruct bio *biotail;   struct list_head queuelist;    //用于在各个队列链表中索引排序......
}

__sector,__data_len: 记录当前request请求的IO在磁盘上的起始扇区以及数据长度,与所包含的bio对应。

*bio:记录request第一个bio,最初bio转换成request时,request内只有一个bio,随着后续可能出现的合并,一个request可能会包含多个bio,__sector,__data_len也跟随更新。当request内有多个bio时,以bio->bi_next组成单向链表管理。

request & bio 结构图

request_queue

这是一个庞大的数据结构,用于描述块设备的请求队列,创建块设备时通常调用blk_mq_init_queue()初始化。其中包含该块设备的软硬件队列,调度器,为上层提供的IO请求入口函数,各种限制参数。

struct request_queue {struct request     *last_merge;struct elevator_queue   *elevator;    //调度器struct blk_queue_stats   *stats;struct rq_qos        *rq_qos;make_request_fn     *make_request_fn;    //io请求入口函数dma_drain_needed_fn  *dma_drain_needed;const struct blk_mq_ops   *mq_ops;    //驱动层需要实现的操作集合/* sw queues */struct blk_mq_ctx __percpu *queue_ctx;    //每个cpu分配一个软件队列unsigned int      nr_queues;unsigned int      queue_depth;/* hw dispatch queues */struct blk_mq_hw_ctx    **queue_hw_ctx;    //硬件队列unsigned int       nr_hw_queues;        //硬件队列数量与硬件及驱动相关,目前大部分存储设备是1....../** queue settings*/unsigned long     nr_requests;    /* Max # of requests */......struct queue_limits    limits;    //根据底层驱动的limits设置...struct list_head requeue_list;...struct blk_mq_tag_set   *tag_set;    //描述底层硬件struct list_head   tag_set_list;struct bio_set     bio_split;......
}

elevator_queue/elevator_type

struct elevator_queue:IO调度器实例,创建块设备或切换IO调度器时,调用所选调度器init_sched()方法初始化。elevator_queue主要包含其对应的调度器类,以及私有数据。

struct elevator_type:调度器类,调度器模块加载时初始化,加入全局调度器类链表。

struct elevator_type
{/* managed by elevator core */struct kmem_cache *icq_cache;/* fields provided by elevator implementation */struct elevator_mq_ops ops;    //调度器需要实现的方法集合......  struct list_head list;    //全局调度器类链表节点
};
struct elevator_queue
{struct elevator_type *type;    //调度器类void *elevator_data;        //当前queue的私有数据,包含等待派发的rq队列struct kobject kobj;struct mutex sysfs_lock;unsigned int registered:1;DECLARE_HASHTABLE(hash, ELV_HASH_BITS);
};

blk_mq_ctx

描述软件队列,创建request_queue时在blk_mq_init_allocated_queue()中初始化。主要信息是记录了其关联的硬件队列。

struct blk_mq_ctx {struct {spinlock_t        lock;struct list_head   rq_lists[HCTX_MAX_TYPES]; //当前request_queue无调度器时,存放等待派发的rq} ____cacheline_aligned_in_smp;unsigned int        cpu;      //cpu indexunsigned short     index_hw[HCTX_MAX_TYPES];     //关联的硬件队列中软件队列编号     struct blk_mq_hw_ctx     *hctxs[HCTX_MAX_TYPES];    //关联的硬件队列...struct request_queue *queue;        //所属request_queuestruct blk_mq_ctxs      *ctxs;        //所属软件队列集合struct kobject      kobj;
} ____cacheline_aligned_in_smp;

rq_lists[HCTX_MAX_TYPES]:  当request_queue无调度器时,用于存放待派发的rq。每个CPU分配一个blk_mq_ctx,该rq_lists也有多个,在并发IO高时这种设计可减少insert_requests流程内锁的竞争,从而提高性能。若指定了调度器会按照调度器insert_requests()方法执行insert_requests,目前只有kyber对多队列进行了处理。

index_hw[HCTX_MAX_TYPES]:软件队列内不仅保存了其关联的硬件队列,由于一个硬件队列可关联多个软件队列,软件队列还保存了其在关联的硬件队列内的编号。主要用于运行过程中标识硬件队列在处理哪些软件队列的IO请求。

blk_mq_hw_ctx

描述硬件队列,创建request_queue时在blk_mq_init_allocated_queue()中初始化。主要记录了其关联的软件队列,分配及管理rq的blk_mq_tags,以及一些调度用到的状态数据。

struct blk_mq_hw_ctx {struct {spinlock_t     lock;struct list_head   dispatch;    //存放等待派发的rqunsigned long       state;      /* BLK_MQ_S_* flags */} ____cacheline_aligned_in_smp;struct delayed_work    run_work;       //处理IO的工作队列...void          *sched_data;        //调度器使用的私有数据,目前只有kyber用到struct request_queue *queue;     //所属的request_queue...struct sbitmap     ctx_map;        //用于标识当前硬件队列在处理哪些软件队列的rq,仅在不设置调度器时使用struct blk_mq_ctx   *dispatch_from;   //记录上一次dispatch派发的软件队列,仅在不设置调度器时使用unsigned int        dispatch_busy;    //记录当前是否busy,仅在不设置调度器时使用unsigned short        type;unsigned short     nr_ctx;       //关联软件队列数量struct blk_mq_ctx   **ctxs;       //关联软件队列链表...struct blk_mq_tags   *tags;            //继承底层硬件struct blk_mq_tags    *sched_tags;      //有设置调度器时使用...unsigned int        numa_node;unsigned int      queue_num;      //在request_queue硬件队列链表的编号atomic_t       nr_active;......
};

struct blk_mq_tags: 用于管理rq的集合,由blk_mq_alloc_rq_map()及blk_mq_alloc_rqs() 初始化。硬件队列上支持的并发rq可预先设定,这样不用每次都申请释放内存,并且集合内还记录的rq的使用情况,可用于并发控制。

*tags: 继承底层blk_mq_tag_set的tags,创建块设备时调用blk_mq_alloc_tag_set()对其每个硬件队列初始化tags,支持的rq个数nr_tags即为其硬件队列深度queue_depth。

*sched_tags: 初始化或切换调度器时初始化该tags,支持的rq个数为request_queue->nr_requests,可以动态修改。

IO进入block层需要转换为rq,获取rq时根据当前是否指定了调度器,选择从*tags 或者 *sched_tags中获取rq。

struct blk_mq_tags {unsigned int nr_tags;            //包含rq的个数unsigned int nr_reserved_tags;atomic_t active_queues;    struct sbitmap_queue bitmap_tags;  struct sbitmap_queue breserved_tags;struct request **rqs;            //正在使用的rqstruct request **static_rqs;    //预分配的nr_tags个rqstruct list_head page_list;
};

blk_mq_tag_set

描述一个块设备硬件相关的数据结构,包含硬件队列的数量,队列深度,分配给每个硬件队列的rq管理集合,还包含了软件队列与硬件队列的映射表。blk_mq_tag_set可以被多个request_queue共享,其中也包含了共享tag_set的request_queue链表

struct blk_mq_tag_set {/** map[] holds ctx -> hctx mappings, one map exists for each type* that the driver wishes to support. There are no restrictions* on maps being of the same size, and it's perfectly legal to* share maps between types.*/struct blk_mq_queue_map map[HCTX_MAX_TYPES];        //软硬件队列映射表unsigned int      nr_maps;    /* nr entries in map[] */    //映射表数量const struct blk_mq_ops *ops;        //驱动实现的操作集合,会被request_queue继承unsigned int       nr_hw_queues;   /* nr hw queues across maps */    //硬件队列数量unsigned int      queue_depth;    /* max hw supported */       //硬件队列深度...struct blk_mq_tags  **tags;           //为每个硬件队列分配一个rq集合struct mutex     tag_list_lock;struct list_head  tag_list;        //使用该tag_set的request_queue 链表
};

struct blk_mq_queue_map:  描述软硬件队列映射关系,创建块设备时在blk_mq_alloc_tag_set()中初始化,通过blk_mq_map_queues()设置映射关系。大致策略是:

  • cpu数大于硬件队列数:多个软件队列映射到一个硬件队列。
  • cpu数等于硬件队列数:一对一映射。
  • cpu数小于硬件队列数:nr_hw_queues设置为cpu数,一对一映射。

创建request_queue时blk_mq_init_allocated_queue()内创建完软硬件队列后,再调用blk_mq_map_swqueue(),根据blk_mq_queue_map将软硬件队列相互关联起来。

struct blk_mq_queue_map {unsigned int *mq_map;        //每个cpu分配一个,其中数值为映射的硬件队列编号unsigned int nr_queues;       //硬件队列数量unsigned int queue_offset;    //从第几个队列开始映射
};

 软硬件队列 结构图

Block multi-queue 架构解析(一)数据结构相关推荐

  1. Artemis架构解析

    目录 前言 1.Artemis Broker 1.1 外部工具与接口 1.1.1 命令行工具 1.1.2 RESTful API 1.1.3 JMX 1.1.4 管理控制页面 1.2 Artemis核 ...

  2. 深入HBase架构解析(二)【转】

    转自:http://www.blogjava.net/DLevin/archive/2015/08/22/426950.html 前言 这是<深入HBase架构解析(一)>的续,不多废话, ...

  3. vSAN架构解析与6.7功能介绍

    vSAN架构解析与6.7功能介绍 2018-11-16 09:43:40 来源: IT大咖说举报 2 分享至 阅读字数:6759 | 17分钟阅读 Overview of vSAN About vSA ...

  4. [强烈推荐] 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

    新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析 1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基 ...

  5. 支持百亿数据场景,海量高性能列式数据库HiStore技术架构解析

    支持百亿数据场景,海量高性能列式数据库HiStore技术架构解析 HiStore介绍 HiStore是阿里中间件团队研发的数据库产品,是一款基于独特的知识网格技术的列式数据库,定位于海量数据高压缩比列 ...

  6. 技术干货 | 网易云信大规模聊天室系统架构解析

    导读:聊天室是一类非常重要的 IM 系统,不同于单聊和群聊,聊天室是一种大规模的实时消息分发系统.本文我们来详细介绍一下网易云信大规模聊天室系统的具体架构以及实践应用案例. 文|曹佳俊 网易云信资深服 ...

  7. NVIDIA VPI架构解析

    VPI 架构解析 文章目录 VPI 架构解析 概述 支持的平台 算法 算法负载 无负载算法 后端 CPU CUDA PVA VIC NVENC OFA 流 缓冲器 Images 图像视图 锁 图像格式 ...

  8. [HBase进阶]--深入HBase架构解析(二)

    感谢原文链接:http://www.blogjava.net/DLevin/archive/2015/08/22/426950.html 前言 这是 <深入HBase架构解析(一)> 的续 ...

  9. VGGNet架构解析

    VGGNet架构解析 1.VGGNet简介 2.VGGNet模型结构 3.VGG16架构 3.1 模型结构 3.2 各block块参数说明 3.3 VGG16模型复现 References 参考论文: ...

  10. Netty框架架构解析+API+运行流程+网络编程文章集锦

    新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析 <!-- 作者区域 --><div class="author"><a class=& ...

最新文章

  1. Mac 上使用 Clion 阅读C++源码的一些操作
  2. 【转】Java基础知识整理
  3. ADB server didn't ACK
  4. Charles+Android模拟器里抓http请求(配置模拟器教程)
  5. css --- 使用媒体查询当屏幕宽度小于某个值时,隐藏掉某个类
  6. beego——模板处理
  7. 手机界面常见的的九宫格
  8. ubuntu 改屏幕分辨率命令_Ubuntu被曝严重漏洞!!!
  9. php批量添加excel数据库表,php 把excel批量导入到数据库代码
  10. MS SQL入门基础:打开游标
  11. 80psi等于多少kpa_压力单位PSI与kpa换算
  12. Token是什么 Token登录认证
  13. 医学统计学计算机操作课后答案,医学统计课后习题答案.doc
  14. 用c语言实现字母排列组合,C语言字母排列组合的实现.pdf
  15. Android 中的WiFi学习笔记(转载)----WIFI启动 代码流程走读---网络连接流程
  16. [转载]Buffon投针实验:究竟为什么是pi?
  17. html不同域名显示不同内容,前端基础面试题(HTML+CSS部分)
  18. stm32正常运行流程图_STM32单片机学习笔记(超详细整理143个问题,学习必看)...
  19. Navicat ORA-12737 ZHS16GBK
  20. autom4te.cache简介及可能引发的问题

热门文章

  1. Android微信分享网络图片缩略图
  2. 编译安装samtools
  3. java变量的类型转换基本知识_Java基础知识(一):注释、关键字、标识符、数据类型、常量、变量、数据类型转换...
  4. js中:0.1 +0.2不等于0.3的原因
  5. 【工作型ppt应该这样做】s3
  6. ubuntu(21):fatal error: filesystem: No such file or directory,fatal error: filesystem: 没有那个文件或目录
  7. 老板VS员工 新猫鼠关系
  8. 通达信分时决策指标公式_通达信公式
  9. 小白也能看懂的踩坑日记--ubuntu(rk3588)安装LXR(阅读源码工具)服务器
  10. 20230317华清远见作业