BLOCK层代码分析(7)IO下发之request的分配和获取

        前面已经介绍下发过程中的bounce操作,BIO的切分和合并操作,若bio仍存在,那么会将当前的bio生成新的request,最终将request下发。但实际上并不是在下发过程中分配request的,request是提前分配好的(称为静态reqeust),在下发过程中直接从sbitmaps中获取空闲的tag,最终得到tag所对应的request。

        BLOCK层的每个tag对应一个request,两者是一一对应的关系。而tag的分配和释放是由sbitmap管理的。因此首先简单介绍一下sbitmap。

1. bitmap vs sbitmap

        对于bitmap,大家应该很熟悉。目前bitmap在内核中广泛使用,通过位操作可以迅速查询或设置某个位来表示该位对应的资源是否使用。它的基本思想是每个位代表一个资源,当第N位为1时表示第N个资源在使用,否则表示资源未被使用。由于资源一般是共享的,一般需要加锁来保证资源的访问

        但bitmap存在如下问题:当访问资源的对象较多时,这个锁会对性能影响很大,且由于CAHELINE的ping-pong效应,会导致cacheline时常更新,从而影响性能。

        BLOCK层的tag是通过sbitmap进行管理的。sibtmap对bitmap进行改进:将原来多个位组成的全局bitmap分成多个组,组的大小通常为一个CACHELINE,同时对每个组单独加锁,这样对某组中的某位做操作时不影响其他组的操作。

BLOCK层代码分析(7)IO下发之request的分配和获取_第1张图片

        如上图所示,将原来32位的bitmap分成四组,每组映射8个bit。之前32位需要共用一个锁,但变换成sbitmap后,每组(8位)抢占一个锁。将大锁简化为多个小锁。

        除了分组管理外,sbitmap_queue还增加了等待功能,即在资源全部占用无空闲时,会让队列一直等待,知道满足要求的资源才再次唤醒并获取资源。

2. request的分配

        上面已经提到,request的分配并不在IO请求下发过程,而是在初始化过程中提前分配好的。request的提前分配是通过函数blk_mq_alloc_tag_set()实现的。除了提前分配静态request,该函数会根据硬件能力(硬件队列数目和队列深度),进行硬件队列HCTX和CPU的映射(即ctx与hctx的映射,见BLOCK层代码分析(2)BLOCK MQ基本原理)。对于每个HCTX,存在total_tags和reserved_tags两个sbitmap,分别用于分配和释放tags以及reserved_tags (share tags特性除外,另外讲解)。

BLOCK层代码分析(7)IO下发之request的分配和获取_第2张图片

        可以看出,blk_mq_set_tag为全局的结构体,其中成员tags分别指向不同的hctx的blk_mq_tags,每个hctx存在一个blk_mq_tags, 管理hctx的tag和request,其中static_rq指向提前分配的request(包括scsi_command以及底层驱动的私有结构)。提前分配的静态request数目为nr_hw_queues * queue_depth。        

        NOTE:SCSI层有些驱动的控制器(如hisi_sas/mpt3sas等)并不能通过hctx id + tag id识别IO的,需要全局的tag来识别,社区称为share-tag 驱动(这个特性是国外的同事开发上传),后续再做介绍。

3. request的获取

        最早reqeust的获取比较简单,对于每个reqeust,从sbitmap中查找空闲的位使用,若没有空闲则等待。但由于每个IO请求都需要通过sbitmap获取空闲的tag,虽然sbitmap相较于bitmap做了优化,但在IO请求下发频繁时,获取和释放tag的过程时锁争抢激烈。因此社区提出了批量分配tag的方案。

   在定义plug情况下,一次分配多个tag。这要求在下发时指明,使用函数blk_start_plug_nr_ios(&plug, max_ios),其中max_ios即为批量获取tag的数量。目前仅在io_uring特性中使用。

        request的获取通过函数blk_mq_get_request()实现的。函数简单描述如下:

BLOCK层代码分析(7)IO下发之request的分配和获取_第3张图片

  1. 若当前定义plug,从缓存池中取出rq,若取出的rq对应的请求队列q与当前发送的IO的请求队列相同时,使用从缓存池中取出的rq(快速通道获取request);否则执行步骤(2);
  2. 尝试从sbitmap中获取tag,首先尝试将bio与plug->mq_list中IO请求合并,若成功不需要tag,直接返回,否则检查当前需要分配的tag个数(plug->),若超过1个,批量获取,并取出一个返回;否则执行步骤(3);
  3. 直接分配1个tag,并对tag对应的request进行初始化。

4. 小结

        为提升性能,下发IO的request结构体是在初始化时提前分配的。由于request的管理是有sbitmap来做的,因此在IO下发时去获取空闲的tag,若有空闲的tag即可得到对应的request。为进一步提升性能,可以一次获取多个tag,进行缓存,当下一次该request-queue对应的IO下发时直接从缓存中获取,进一步减少获取tag时间。

你可能感兴趣的:(BLOCK_SCSI,BLOCK,request)