之前计划先将bounce过程,但bounce过程涉及bio的切分,因此本节将bio的操作:切分和合并。前面对bio的组织和分配做了基本的介绍,bio的切分是将一个bio分成两个bio,而合并准确来说是将bio与IO请求request进行合并。

1. IO请求request和bio的关系

        下图反应了request和bio之间的关系。request是由在硬盘位置连续的bio链接起来的。图中request中有3个bio,每个分别指向硬盘中一段数据。只有硬盘上相邻的bio才可以合并组织到一个request中。

         request中bio指向所连入的bio,biotail指向最后一个bio。request中成员__sector表示request中数据在硬盘中的起始位置。

2. bio的切分

        BLOCK层对请求队列request-queue中的IO请求或bio是有限制的(queue_limits),有如下参数:

(1)q->limits.max_segments

        表示请求队列request-queue中每个IO请求request的最大segment数目,也可以通过函数blk_queue_max_segments(q, nr_segs)。默认为BLK_MAX_SEGMENTS(128)。

(2)q->limits.max_segment_size

    表示请求队列request-queue中每个segment最大的数据大小。也可以通过函数blk_queue_max_segment_size(queue, shost->max_segment_size)。默认 为 BLK_MAX_SEGMENT_SIZE(65536=64*4k):

(3)q->limits.max_sectors

        表示请求队列支持一次传输request的最大扇区数目即IO请求最大size。也可以通过函数blk_queue_max_hw_sectors(queue, max_sectors)进行设置。默认BLK_DEF_MAX_SECTORS(2560)。

         对于每个IO请求,受max_segments(最多包含的segment数目)和max_sectors(最多包含的扇区数目)限制。对于每个segment,受max_segment_size(每个segment最大的size)限制。

        若超过上述限制,最终会调用函数bio_split()将BIO切分为两个BIO。

Struct bio *bio_split(struct bio *bio, int sectors, gfp_t gfp, struct bio_set *bs)

        该函数将bio的前sectors大小的数据切分,返回新生成的bio (新生成的bio从bs缓冲池分配)。

        函数调用如下:

  1. 首先通过bioset分配split bio,并通过函数_bio_clone_fast()对部分参数拷贝,但并不分配bio_vec,而是与原来的bio共享bio_vec;
  2. 通过函数bio_advance()更新切分后bio;

        下面以如下bio为例,bio为3个bio_vec,bvec_iter->bi_idx表明当前bio有效的位置为0(没有完成):

        若将上述bio切分两部分,其中切出的部分split大小为bio_vec 0大小,这时会如下所示,其中蓝色为新分配的bio,并没有新分配bio_vec,只是通过bi_iter指示对应各自的数据:

 

 注意:这里bio_vec指向的是内存,因为這里是非连续的。bio对应的硬盘位置是连续的

3 bio的合并

        只有在硬盘上位置相邻的bio才可以合并。当合并时,可以减少新的request分配(tag的申请),一次下发即可,减少下发次数,从而提升性能。但若系统中合并的情况很少时(随机读写),尝试合并会浪费时间,反而会对性能有影响。

3.1 合并类型

        合并的类型分为:前向合并和后向合并。

        前向合并是当前bio在硬盘中的位置处于当前IO请求之前,这是会将该bio放置于bio链的最前面。如下图在上面的request基础上与new bio进行前向合并,合并后的结果如下所示:

        后向合并是当前bio在硬盘中的位置处于当前IO请求的后面,这时需要将bio放置于bio链的末尾。下图为之前request与bio进行后向合并,结果如下所示:

 3.2 合并的时机

        下发过程中存在两次合并尝试:

  1. 尝试与plug->mq_list中的request进行合并。函数blk_attempt_plug_merge()完成该功能。
  2. 尝试与schedule list (定义IO调度器)或ctx->rq_lists(没有定义IO调度器)上request进行合并。函数blk_mq_sched_bio_merge()完成该功能。

        两者最终调用blk_attemp_bio_merge()检查是否合并,何种类型的合并,以及合并操作。代码简单描述如下:

        下发过程中函数blk_attempt_plug_merge()会将bio与plug->mq_list中的request通过函数blk_attempt_bio_merge()进行合并。

        下发过程中函数blk_attempt_sched_bio_merge()会根据IO调度器中是否定义bio_merge(),若定义了直接调用调度器中的bio_merge()回调处理。若没有定义,同样调用函数blk_attempt_bio_merge()进行合并。

        函数blk_attempt_bio_merge()检查是否可以合并,若可以前向合并,调用函数bio_attempt_front_merge(),若可以后向合并,调用函数bio_attempt_back_merge(),否则结束。

 

 

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐