上一篇已经讲述了MQ多队列的机制,利用cpu的多核,配上多队列机制,并发的处理IO请求,提高效率。
本篇详细讲述下从bio下发到IO调度器中,MQ队列机制是如何一步步完成的。
1、MQ处理结构流图
从整个流程图可以看到,主要是分为三个部分:初始化硬件设备的target参数、初始化请求队列request_queue以及bio请求的处理过程。前面两个过程主要是完成底层存储设备向文件系统的注册,同时完成软硬队列映射关系等初始化,后一个部分是bio在MQ机制最后生成对应子请求并挂载在硬件队列上的过程。
2、scsi设备初始化
对于走scsi协议的底层存储设备,均完成此初始化过程,内核为每个scsi设备提供给一个target的设备参数,该参数主要包括:硬件队列深度,超时时间,硬件中断函数等。
同时,target的设备参数后序作为参数传入requset_queue结构体中,用来初始化硬件队列相关参数。
具体过程如下:
3、request_queue队列的初始化
之前已经提到过,linux内核支持单队列的机制,也支持多队列的机制,并且在内核中IO调用通过的调用函数都是make_request_fn,那么内核如何知道选用的是多队列机制还是单队列机制呢??
通过make_request_fn函数的注册。
初始化步骤如下:
- 当底层存储设备是单通道时,此时会向内核make_request_fn注册实体函数,函数名为blk_sq_make_request;当设备是多通道时,此时会向内核make_request_fn注册实体函数,函数名为blk_mq_make_request。
- 当注册完成后,会根据各自队列的数量,按照内核提供的映射关系,形成cpu号(软件队列号)到硬件队列号的map数组。
- 按照cpu个数和硬件队列数分别分配软硬件队列环境并进行初始化
- 将互为映射的软硬件队列环境的参数相互关联。
具体函数流程图如下:
关于队列初始化的源码阅读如下:
1.首先在driver\scsi文件夹下的scsi_scan.c文件中,scsi_alloc_sdev函数会判断当前scsi支持的块设备是否支持multiqueue:
if (shost_use_blk_mq(shost)) // multiple queue is enabled gaocm
sdev->request_queue = scsi_mq_alloc_queue(sdev); //MQ队列的定义
else
sdev->request_queue = scsi_alloc_queue(sdev);
2.若上述设备支持mq则进入scsi_mq_alloc_queue函数,该函数中主要进行设备队列的初始化,blk_mq_init_queue,在blk_mq_init_queue 函数中根据set信息进行与该设备队列相关的信息参数初始化,过程如下:
/* mark the queue as mq asap */
q->mq_ops = set->ops; //标记为MQ队列
q->queue_ctx = alloc_percpu(struct blk_mq_ctx); //获得per_cpu的地址 建立software queue环境
if (!q->queue_ctx)
goto err_exit;
q->queue_hw_ctx = kzalloc_node(nr_cpu_ids * sizeof(*(q->queue_hw_ctx)),
GFP_KERNEL, set->numa_node); //获得hardware queue上下文环境
if (!q->queue_hw_ctx)
goto err_percpu;
q->mq_map = blk_mq_make_queue_map(set); // 建立software queue和hardware queue之间的映射关系
if (!q->mq_map)
goto err_map;
blk_mq_realloc_hw_ctxs(set, q);
if (!q->nr_hw_queues)
goto err_hctxs;
INIT_WORK(&q->timeout_work, blk_mq_timeout_work);
blk_queue_rq_timeout(q, set->timeout ? set->timeout : 30 * HZ); //定义scsi设备队列超时设定
q->nr_queues = nr_cpu_ids;
q->queue_flags |= QUEUE_FLAG_MQ_DEFAULT;
if (!(set->flags & BLK_MQ_F_SG_MERGE))
q->queue_flags |= 1 << QUEUE_FLAG_NO_SG_MERGE;
q->