三、多队列框架代码分析 blk-mq代码在Linux-3.13(2014)内核中合入主线,在Linux-3.16中成为内核的一个完整特性,在Linux-5.0内核中,blk-sq代码(包括基于blk-sq的IO调度器,如cfq、noop)已被完全移除,MQ成为Linux Block layer的默认选项。下面基于Linux-5.6.0内核介绍blk-mq代码和关键数据结构。 request和tag分配 blk-mq中,request和tag是绑定的。首先,我们来看下两个与tag分配有关的重要数据结构--blk_mq_tags和blk_mq_tag_set。
![]()
![]() 与SQ框架一样,MQ框架中使用request结构体来描述IO请求;不同的是,SQ使用内存池来分配request结构体(参见__get_request),在request往驱动派发时分配tag(参见blk_queue_start_tag),MQ中request和tag分配是绑定在一起的(参见blk_mq_get_request), 具体表现为:
blk_mq_alloc_tag_set: 为一个或者多个请求队列分配tag和request集合(tag set可以是多个request queue共享的,例如UFS设备,一个host controller只有一个tag set,但器件可能划分成多个LU--Logical Unit,每个LU有单独的request queue, 这些不同的request queue共享一个tag set),主要流程如下:
![]() 图7. scsi-mq驱动初始化时tag set分配流程 blk_mq_get_request: 为bio分配request。MQ中request占用的内存在块设备驱动初始化时分配完成(tags->static_rqs), tag作为数组的索引获取对应的request,因此MQ中分配request即分配tag(使用sbitmap标记对应tag的是否已被使用)。该函数的主要流程如下:
![]() 图8. blk-mq bio提交时request分配流程 request_queue初始化 基于blk-mq的块设备驱动初始化时,通过调用blk_mq_init_queue初始化IO请求队列(request_queue)。例如,scsi-mq驱动中,每次添加scsi设备(scsi_device)时都会调用blk_mq_init_queue接口来初始化scsi设备的请求队列。
1.设置队列的mq_ops(q->mq_ops)为set->ops (例如scsi对应的实现是scsi_mq_ops) 2.设置request超时时间,初始化timeout_work(处理函数是blk_mq_timeout_work) 3.设置队列的make_request回调为blk_mq_make_request (bio的提交时会用到) 4.分配和初始化percpu软件队列(ctx) 5.关联request_queue和块设备的tag set 6.更新软件队列(ctx)到硬件派发队列(hctx)的映射关系(map: ctx->hctx) ![]() 图9. scsi-mq驱动创建scsi device时初始化requst_queue流程 IO的提交(submit) blk-mq中,通过调用blk_mq_make_request将上层提交的bio封装成request并提交到块设备层,它的主要流程如下:
|