热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Blockmultiqueue架构解析(二)流程与机制

主要流程request_queue初始化块设备初始化时通过blk_mq_init_queue()创建request_queue并初始化,主要功能包含:reque

主要流程


request_queue初始化

块设备初始化时通过blk_mq_init_queue()创建request_queue并初始化,主要功能包含:


  • request_queue与块设备的blk_mq_tag_set相互绑定,根据blk_mq_tag_set设置一些参数。
  • 创建软硬件队列及进行绑定。
  • 设置io请求入口函数make_request_fn 为 blk_mq_make_request()。


IO提交/转换request

IO请求的入口为blk_mq_make_request(),其中首先判断IO是否可以跟其他request合并,若无法合并再将IO转换为request进一步处理。


request获取

request是事先在硬件队列的tags或者sched_tags中分配好的,通过blk_mq_get_request()获取,期间有可能会因获取不到request而被io_schedule,进入iowait状态。(扩展阅读IOwait 到底在wait什么)


request加入队列

IO在blk_mq_make_request()中转换成request后,根据不同情况做处理,最终去向主要有三种:加入plug队列,加入调度队列,直接派发。


request派发

blk_mq_run_hw_queue() 用来启动一个硬件队列的request派发,触发派发的情况很多,大致归纳下是:需要直接处理request,plug/调度队列flush,硬件队列启动/停止,blk_mq_get_tags中获取不到tags,kyber_domain_wake等等。

调用__blk_mq_run_hw_queue() 从可能的三个队列中选取request进行派发,他们是:硬件队列的dispatch,request_queue的调度器队列,硬件队列关联的软件队列的rq_list。最终在blk_mq_dispatch_rq_list()中调用q->mq_ops->queue_rq()完成向下层驱动派发request。

 


request完成

硬件驱动完成一个IO请求后,调用scsi_cmnd->scsi_done回调,针对block multi-queue 实现了统一接口scsi_mq_done()。处理IO完成中断的CPU与发起request的CPU可能不一致,scsi_mq_done()中的策略是尽量在发起request的CPU上处理request完成。

最终都会调用q->mq_ops->complete回调对request进行完成处理,统一实现的接口是scsi_softirq_done()。其中主要调用路径是scsi_softirq_done()->scsi_finish_command()->scsi_io_completion()->scsi_end_request()->__blk_mq_end_request()... ,执行request提交过程中注册的各种回调,最终释放request,完成一次IO请求。


机制


plug/unplug

Linux块设备层早期就引入了plug/unplug机制,为每个task分配一个plug list,当task提交IO时不立马处理,而是积攒一定量再统一加入下一级派发队列,减少对派发队列的竞争,从而提高性能,block multi-queue也继承了这一机制,与单队列的实现基本一致,网络上已有大量分析,这里就不再赘述。


IO 合并

request由bio转化而来,最初一个request内只包含一个bio,若有新提交的bio与已经在排队等待处理的request物理上连续,就直接将该bio合入request。推荐这篇文章https://blog.csdn.net/juS3Ve/article/details/80576965,已经分析的十分透彻了。block multi-queue 新增的部分是,当不使用调度器时request会在软件队列的rq_list上等待派发,提交IO时也会尝试与rq_list中的request进行合并。对于block mulit-queue架构,可能发生IO合并的队列有三个:plug list ,调度器内部队列(使用调度器),软件队列的rq_list(不使用调度器)。


软硬件队列映射

在之前的数据结构分析中提到block multi-queue的软硬件映射关系是由硬件相关的blk_mq_tag_set->map决定,设置其映射关系的函数为blk_mq_map_queues()。

int blk_mq_map_queues(struct blk_mq_queue_map *qmap)
{unsigned int *map = qmap->mq_map;unsigned int nr_queues = qmap->nr_queues;unsigned int cpu, first_sibling, q = 0;for_each_possible_cpu(cpu)map[cpu] = -1;/** Spread queues among present CPUs first for minimizing* count of dead queues which are mapped by all un-present CPUs*/for_each_present_cpu(cpu) {//优先映射online的cpuif (q >= nr_queues)//若硬件队列数量不够直接跳出break;map[cpu] = queue_index(qmap, nr_queues, q++);}for_each_possible_cpu(cpu) {//检查所有cpuif (map[cpu] != -1)continue;/** First do sequential mapping between CPUs and queues.* In case we still have CPUs to map, and we have some number of* threads per cores then map sibling threads to the same queue* for performance optimizations.*/if (q }

具体映射策略是:


  • 优先保障online的cpu映射到不同的硬件队列
  • 若cpu数量大于硬件队列,将同一硬件核的cpu映射到同一硬件队列
  • 没有同硬件核的cpu顺序轮转映射硬件队列

并发管理

block multi-queue通过控制request的获取,来限制一个硬件队列在block层最大并发request数量。由之前的数据结构分析可知,request是预先分配好的保存在struct blk_mq_tags结构中。运行时根据是否使用调度器,决定从硬件队列的tags或是sched_tags中获取request,所以最大并发request数也要分是否使用调度器来讨论。获取request的函数为blk_mq_get_request()。

使用调度器流程:


  • 找到当前软件队列映射的硬件队列
  • 调用调度器limit_depth方法(若有实现),设置shallow_depth
  • sched_tags有空闲的request且分配的request总数
  • 若分配不成功,调用blk_mq_run_hw_queue()启动request派发,再重试获取request,期间可能进入iowait

不使用调度器流程:


  • 找到当前软件队列映射的硬件队列
  • 若硬件blk_mq_tag_set支持共享,增加tags的active_queues
  • 若硬件blk_mq_tag_set支持共享,且当前硬件队列已分配的request超过均分上限,分配失败
  • tags有空闲的request,分配成功
  • 若分配不成功,调用blk_mq_run_hw_queue()启动request派发,再重试获取request,期间可能进入iowait

总结一下,每个硬件队列的最大并发request数是:

使用调度器:request_queue的nr_request,与调度器limit_depth方法返回值的较小值。

不使用调度器且不支持共享tag_set:硬件队列queue_depth。

不使用调度器支持共享tag_set:queue_depth个request,各active_queue均分。


调度器

block multi-queue最开始是不支持调度器的,调度器只在单队列中生效,随着单队列代码逐步移出内核,而调度器对于hdd这种慢速设备又很有必要,调度器框架也加入了block multi-queue。从代码也能看出multi-queue框架中是否使用调度器,处理流程差异很大。Linux5.x后支持mq-deadline,bfq,kyber三种调度器,其中只有kyber有针对multi-queue框架中的多软硬队列做了处理,也被称为是真正意义上的多队列调度器。

 


推荐阅读
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
author-avatar
我是王健值得信赖
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有