内核block层IO调度器—bfq算法深入探索2

刚提交了一篇《内核block层IO调度器—bfq算法之深入探索1》,较为深入的介绍了一些bfq算法的知识点,本文再继续介绍一下bfq算法的其他知识点:主要讲解bfq_bfqq_has_short_ttime、bfq_bfqq_IO_bound、bfq_bfqq_in_large_burst 、bfq_better_to_idle、bfqq->wr_coeff有关的bfqq权重提升。

在看本文前,希望读者先看下我之前写的几篇介绍bfq算法的文章,打个基础。本文基于centos 8.3,内核版本4.18.0-240.el8,详细源码注释见 https://github.com/dongzhiyan-stack/linux-4.18.0-240.el8。

1:bfq_better_to_idle函数源码讲解

bfq_better_to_idle()函数多处都有调用,比如:

  1. static bool bfq_bfqq_must_idle(struct bfq_queue *bfqq)
  2. {
  3.     return RB_EMPTY_ROOT(&bfqq->sort_list) && bfq_better_to_idle(bfqq);
  4. }
  5. static void bfq_completed_request(struct bfq_queue *bfqq, struct bfq_data *bfqd)
  6. {
  7.     .........
  8.     if (bfqd->in_service_queue == bfqq) {
  9.         //bfqq上没有IO请求但是可能很快就有新的IO请求来,bfqq还不能过期失效,而是启动 idle timer定时器
  10.         if (bfq_bfqq_must_idle(bfqq)) {
  11.             if (bfqq->dispatched == 0)
  12.                 bfq_arm_slice_timer(bfqd);
  13.         }
  14.     }
  15.     .........
  16. }

这个场景是说:bfqq上没有要派发的IO请求了,但有较大概率bfqq绑定的进程很快还有新的IO请求要来,故bfqq还不能立即过期失效,而是进入idle状态,启动idle timer定时器等待可能马上来的新的IO请求。怎么判定bfqq绑定的进程可以进入idle状态而等待新的IO请求来呢?就是靠bfq_better_to_idle()函数返回true,看下它的源码:

  1. static bool bfq_better_to_idle(struct bfq_queue *bfqq)
  2. {
  3.     struct bfq_data *bfqd = bfqq->bfqd;
  4.     bool idling_boosts_thr_with_no_issue, idling_needed_for_service_guar;
  5.     ...............
  6.     //异步bfqq或者idle调度算法的bfqq直接返回false
  7.     if (bfqd->bfq_slice_idle == 0 || !bfq_bfqq_sync(bfqq) ||
  8.        bfq_class_idle(bfqq))
  9.         return false;
  10.     //bfqd没有一个bfqq的权重提升了并且当前的bfqq绑定的进程向bfqq插入IO请求很快,则idling_boosts_thr_with_no_issuetrue
  11.     idling_boosts_thr_with_no_issue =
  12.         idling_boosts_thr_without_issues(bfqd, bfqq);
  13.     //当前的bfqq权重提升了并且正在磁盘驱动层传输的IO请求比较多等等则返回true
  14.     idling_needed_for_service_guar =
  15.         idling_needed_for_service_guarantees(bfqd, bfqq);
  16.     return idling_boosts_thr_with_no_issue ||
  17.         idling_needed_for_service_guar;
  18. }

可以发现主要是调用idling_boosts_thr_without_issues()和idling_needed_for_service_guarantees()两个函数,如果二者的返回值有一个是true,则bfq_better_to_idle()函数就返回true。简单总结,有以下两个条件有一个成立则bfq_better_to_idle()返回true :

1:bfqd没有一个bfqq的权重提升了并且当前的bfqq绑定的进程有频繁向bfqq的队列插入IO请求的特性。  

2:当前的bfqq权重提升了并且正在磁盘驱动层传输的IO请求比较多等等则返回true 。简单说,当前的bfqq还不能过期失效,有较大概率bfqq绑定的进程很快还有IO要传输。

下边重点看下idling_boosts_thr_without_issues()和idling_needed_for_service_guarantees()两个函数。

  1. //bfqd没有一个bfqq的权重提升了并且当前的bfqq绑定的进程有频繁向bfqq的队列插入IO请求的特性,该函数返回true
  2. static bool idling_boosts_thr_without_issues(struct bfq_data *bfqd,
  3.                          struct bfq_queue *bfqq)
  4. {
  5.     //用的是SATA盘并且bfq总的已派发但还没完成的IO请求数很少,则rot_without_queueingtrue
  6.     bool rot_without_queueing =
  7.         !blk_queue_nonrot(bfqd->queue) && !bfqd->hw_tag,
  8.         bfqq_sequential_and_IO_bound,
  9.         idling_boosts_thr;
  10.     /* No point in idling for bfqq if it won't get requests any longer */
  11.     if (unlikely(!bfqq_process_refs(bfqq)))
  12.         return false;
  13.     //就是说,bfqq绑定的进程需要大量连续快速传输IO请求
  14.     bfqq_sequential_and_IO_bound = !BFQQ_SEEKY(bfqq) &&
  15.         bfq_bfqq_IO_bound(bfqq) && bfq_bfqq_has_short_ttime(bfqq);
  16. /*idling_boosts_thr true,有两种情况。1:用的是SATA盘并且bfq总的已派发但还没完成的IO请求数很少 2:bfqq绑定的进程需要大量连续快速传输IO请求,并且用的SATA(或者IO请求在磁盘驱动传输的比较慢)*/
  17.     idling_boosts_thr = rot_without_queueing ||
  18.     //SATA 或者 bfq总的已派发但还没完成的IO请求数比较多,说明IO在磁盘驱动传输的很慢
  19.         ((!blk_queue_nonrot(bfqd->queue) || !bfqd->hw_tag) &&
  20.               //bfqq绑定的进程需要大量连续快速传输IO请求
  21.               bfqq_sequential_and_IO_bound);
  22.     //idling_boosts_thrtrue,并且没有bfqq的权重提升了则返回true
  23.     return idling_boosts_thr && bfqd->wr_busy_queues == 0;
  24. }

首先是bfqq_sequential_and_IO_bound = !BFQQ_SEEKY(bfqq) &&bfq_bfqq_IO_bound(bfqq) && bfq_bfqq_has_short_ttime(bfqq)。

1: !BFQQ_SEEKY(bfqq)为true表示bfqq派发的随机IO请求不多(磁盘是SATA),或者bfqq派发的每个IO请求传输的数据量不多(磁盘是ssd)。关于BFQQ_SEEKY()的理解,会在本文最后一节详细介绍。

2: bfq_bfqq_IO_bound 表示bfqq大量派发IO请求标记,如果bfqq没有大量派发IO请求而消耗完配额,就会清理掉bfq_bfqq_IO_bound标记

3: bfq_bfqq_has_short_ttime :bfqq有bfq_bfqq_has_short_ttime标记,说明进程向bfqq->sort_list插入IO请求很快。有可能bfq_better_to_idle()->idling_boosts_thr_without_issues()返回true。这样的话,IO请求传输完成执行到bfq_completed_request(),因为bfq_better_to_idle()返回true,并且bfqq派发的IO请求都传输完了(bfqq->dispatched 是0),则启动idle timer。先不让bfqq过期失效,而是等一小段时间,看bfqq是否会来新的IO请求,没有的话再令bfqq过期失效。

bfq_bfqq_IO_bound(bfqq) 和bfq_bfqq_has_short_ttime(bfqq)两个宏定义的作用,本文最后会重点讲解。

接着是idling_boosts_thr = rot_without_queueing || ((!blk_queue_nonrot(bfqd->queue) || !bfqd->hw_tag) && bfqq_sequential_and_IO_bound)。源码里说的比较清楚,这里再贴下:idling_boosts_thr 为true,有两种情况。1:用的是SATA盘并且bfq总的已派发但还没完成的IO请求数很少 2:bfqq绑定的进程需要大量连续快速传输IO请求,并且用的SATA盘(或者IO请求在磁盘驱动传输的比较慢)。

最后,只要idling_boosts_thr是true并且没有bfqq的权重提升了,则idling_boosts_thr_without_issues()返回true。我简单总结下:bfqd没有一个bfqq的权重提升了并且当前的bfqq绑定的进程有频繁向bfqq的队列插入IO请求的特性,则idling_boosts_thr_without_issues()返回true

接着看下bfq_better_to_idle()里调用的idling_needed_for_service_guarantees()函数,

  1. static bool idling_needed_for_service_guarantees(struct bfq_data *bfqd,
  2.                          struct bfq_queue *bfqq)
  3. {
  4.     /* No point in idling for bfqq if it won't get requests any longer */
  5.     if (unlikely(!bfqq_process_refs(bfqq)))
  6.         return false;
  7.     //bfqq权重提升了并且 权重提升的bfqq数量比在st->active treebfqq的数量少(或者bfq在磁盘驱动传输的IO请求个数很多)
  8.     return (bfqq->wr_coeff > 1 &&
  9.              (bfqd->wr_busy_queues < bfq_tot_busy_queues(bfqd) || bfqd->rq_in_driver >= bfqq->dispatched + 4)
  10.             ) ||
  11.            bfq_asymmetric_scenario(bfqd, bfqq);//????这个不知道啥用
  12. }

idling_needed_for_service_guarantees()作用,我的简单总结是:当前的bfqq权重提升了并且正在磁盘驱动层传输的IO请求比较多等等则返回true。

2:bfqq->wr_coeff与进程权重提升

进程的权重是靠bfqq的entity->weight变量体现,而entity->weight=默认权重*bfqq->wr_coeff。bfqq->wr_coeff是权重系数,默认是1,就是说默认情况进程不会提升权重。进程提升权重的过程是什么?首先要增大bfqq->wr_coeff,这个函数过程是:__bfq_insert_request()->bfq_add_request()->bfq_bfqq_handle_idle_busy_switch()->bfq_update_bfqq_wr_on_rq_arrival()函数,在bfq_update_bfqq_wr_on_rq_arrival()函数会增大bfqq->wr_coeff。什么情况下会执行这个函数过程呢?

1:进程第一次传输IO请求,分配了新的bfqq,向bfqq算法队列添加IO请求。执行到__bfq_insert_request()->bfq_add_request(),if (!bfq_bfqq_busy(bfqq))成立,则执行bfq_bfqq_handle_idle_busy_switch()->bfq_update_bfqq_wr_on_rq_arrival(),但是分析不符合bfqq->wr_coeff增大条件,就是说bfq_update_bfqq_wr_on_rq_arrival()里不会增大bfqq->wr_coeff。

2:bfqq原本是idle状态(因为bfqq过期失效而处于st->idle tree)。现在bfqq又来了新的IO请求,要激活bfqq,并且bfqq的entity从st->idle tree移动到st->active tree。此时执行到__bfq_insert_request()->bfq_add_request()->bfq_bfqq_handle_idle_busy_switch()->bfq_update_bfqq_wr_on_rq_arrival()函数,有较大概率增大bfqq->wr_coeff。

然后,把bfqq的entity移动到st->active tree,函数流程是:bfq_bfqq_handle_idle_busy_switch()->bfq_add_bfqq_busy->bfq_activate_bfqq->bfq_activate_requeue_entity->__bfq_activate_requeue_entity->__bfq_activate_entity->bfq_update_fin_time_enqueue->__bfq_entity_update_weight_prio()函数。在__bfq_entity_update_weight_prio()中执行形如entity->weight = 默认权重 *bfqq->wr_coeff,真正增大进程的权重。

下边详细说说bfqq->wr_coeff与进程权重提升相关函数源码,先看下bfq_add_request()函数。

  1. static void bfq_add_request(struct request *rq)
  2. {
  3.     //通过rq->elv.priv[1]得到保存的bfqq
  4.     struct bfq_queue *bfqq = RQ_BFQQ(rq);
  5.     struct bfq_data *bfqd = bfqq->bfqd;
  6.     struct request *next_rq, *prev;
  7.     unsigned int old_wr_coeff = bfqq->wr_coeff;
  8.     bool interactive = false;
  9.     ...........
  10.     //bfq_add_request()中把IO请求添加到bfqq->sort_list链表
  11.     elv_rb_add(&bfqq->sort_list, rq);
  12.     ...........
  13.     //bfqq是新创建的或者bfqqst->idle treeif才成立
  14.     if (!bfq_bfqq_busy(bfqq))//激活bfqq,把bfqq添加到st->active tree
  15.         bfq_bfqq_handle_idle_busy_switch(bfqd, bfqq, old_wr_coeff,
  16.                          rq, &interactive);
  17.     else {
  18.         if (bfqd->low_latency && old_wr_coeff == 1 && !rq_is_sync(rq) &&
  19.             time_is_before_jiffies(
  20.                 bfqq->last_wr_start_finish +
  21.                 bfqd->bfq_wr_min_inter_arr_async)) {//测试这里基本没成立过
  22.             //更新bfqq->wr_coeff
  23.             bfqq->wr_coeff = bfqd->bfq_wr_coeff;
  24.             bfqq->wr_cur_max_time = bfq_wr_duration(bfqd);
  25.             bfqd->wr_busy_queues++;
  26.             bfqq->entity.prio_changed = 1;
  27.         }
  28.         //bfqq->next_rq发生了变化,执行bfq_updated_next_req()根据新的bfqq->next_rq消耗的配额和bfqq->max_budget更新bfqq权重
  29.         if (prev != bfqq->next_rq)
  30.               bfq_updated_next_req(bfqd, bfqq);
  31.     }
  32.     /*这个if很容易成立。bfqd->low_latency默认是1,其他条件只要bfqq老的 bfqq->wr_coeff1 或者 新的bfqq->wr_coeff1 或者 bfqq是交互式IO,这个if就会成立。除非bfqq老的bfqq->wr_coeff大于1,并且bfqq是实时性IO(这样新的bfqq->wr_coeff不是1,并且interactive0)if才不会成立。*/
  33.     if (bfqd->low_latency &&
  34.         (old_wr_coeff == 1 || bfqq->wr_coeff == 1 || interactive))
  35.                 bfqq->last_wr_start_finish = jiffies;
  36. }

bfq_add_request()函数一般是把IO请求添加到bfqq->sort_list链表,可能会激活bfqq而把bfqq的entity添加到st->active tree,重点是执行bfq_bfqq_handle_idle_busy_switch(),这个函数下边有讲解。该函数最后if (bfqd->low_latency && (old_wr_coeff == 1 || bfqq->wr_coeff == 1 || interactive))里还会更新bfqq->last_wr_start_finish,bfqq->last_wr_start_finish表示bfqq的权重提升时间或者权重结束时间。这个if判断挺复杂的,bfqd->low_latency默认是1,那什么情况下该if会成立呢?

1:bfqq的bfqq->wr_coeff一直是1,那每次向bfqq插入一个IO请求就bfqq->last_wr_start_finish=jiffies,bfqq->last_wr_start_finish此时记录的是每次向bfqq插入IO请求的时间点。

2:bfqq的bfqq->wr_coeff是1,但是在上边的bfq_bfqq_handle_idle_busy_switch()->bfq_update_bfqq_wr_on_rq_arrival()函数里,把bfqq->wr_coeff增大到30或更大,此时bfqq->last_wr_start_finish = jiffies记录的是bfqq权重提升开始时间

3:bfqq的权重已经提升了,bfqq->wr_coeff已经大于1,但是bfqq是交互式IO特性,interactive是1,此时也bfqq->last_wr_start_finish = jiffies,此时记录的是每次向bfqq插入IO请求的时间点。

最后,在派发IO请求时,bfq_dispatch_rq_from_bfqq()->bfq_update_wr_data->bfq_bfqq_end_wr() 在bfqq权重提升结束时也会执行bfqq->last_wr_start_finish = jiffies,此时bfqq->last_wr_start_finish记录的是权重更新结束时间

下边重点讲解bfq_bfqq_handle_idle_busy_switch()函数,它里边根据进程的IO特性增大bfqq->wr_coeff,之后才会增大进程的权重。

2.1 burst型IO、实时性IO、交互式IO

在讲解bfq_bfqq_handle_idle_busy_switch()前,有必要说下bfq算法里出现的几种进程IO特性:burst型IO、实时性IO、交互式IO。burst型IO的进程是默认的,bfq_bfqq_in_large_burst(bfqq)为true就说明bfqq绑定的进程是burst型IO,burst型IO的进程是一段时间内大量传输IO请求,对低延迟应该没什么要求。

交互式IO的进程需要短时间内快速传输IO请求,要求低延时。实时性IO的进程,我的理解是需要连续的短时间大量派发IO请求,并且要求低延迟。交互式IO和实时性IO有什么区别?我的理解是,交互式IO的进程只会偶尔短时间快速派发IO请求,比如vim查看一个文件,一次要读取的数据量不会太大,但是要求快速的读取文件数据并且显示出来,低延时;实时性IO的进程,比如音视频类应用,需要一段时间内连续快速读取音视频文件进行解码,对延迟很敏感。

交互式IO和实时性IO都要求低延迟,都会先增大bfqq->wr_coeff权重系数。然后执行到bfq_update_fin_time_enqueue()时把bfqq的entity插入st->active tree时,先执行__bfq_entity_update_weight_prio()里的entity->weight = 默认权重*bfqq->wr_coeff,增大权重。接着执行bfq_calc_finish(entity, entity->budget),因为entity->weight很大,计算出来的entity->finish很小,这样就可以把entity尽可能靠左插入st->active tree。从而保证该entity尽可能早的被bfq调度器用到,接着派发entity对应的bfqq的IO请求。

饶了一大圈,先有增大bfqq->wr_coeff权重系数,接着根据bfqq->wr_coeff计算得到更大的entity->weight权重,entity->weight很大又保证entity更靠左的插入st->active tree,这样保证entity更早被bfq调度器调度用到。进而很快派发该entity对应bfqq上的IO请求,这应该是交互式IO和实时性IO因bfqq->wr_coeff增大后,可以保证IO低延时的原因的吧。

注意,进程IO特性:burst型IO、实时性IO、交互式IO,本文也有几处说burst型IO的bfqq,实时性IO的bfqq、交互式IO的bfqq。每一个进程都绑定了一个唯一bfqq,进程和bfqq是一回事。怎么计算进程是burst型IO?实时性IO?交互式IO?重点在bfq_bfqq_handle_idle_busy_switch()函数。

  1. static void bfq_bfqq_handle_idle_busy_switch(struct bfq_data *bfqd,
  2.                          struct bfq_queue *bfqq,
  3.                          int old_wr_coeff,
  4.                          struct request *rq,
  5.                          bool *interactive)
  6. {
  7.     bool soft_rt, in_burst, wr_or_deserves_wr,
  8.         bfqq_wants_to_preempt,
  9.         //bfqq处于st->idle tree已经很长时间
  10.         idle_for_long_time = bfq_bfqq_idle_for_long_time(bfqd, bfqq),
  11.     //bfqq在传输完最后一个IO请求后的bfqd->bfq_slice_idle * 3时间内又来新的IO请求则arrived_in_timetrue
  12.     arrived_in_time =  ktime_get_ns() <=
  13.             bfqq->ttime.last_end_request +
  14.             bfqd->bfq_slice_idle * 3;
  15.     //bfqqbfq_bfqq_in_large_burst标记则in_bursttrue,in_burst表示bfqq绑定的进程有一段时间内大量派发IO请求的特性
  16.     in_burst = bfq_bfqq_in_large_burst(bfqq);
  17.     //soft_rttrue表示bfqq绑定的进程是实时性IO
  18.     soft_rt = bfqd->bfq_wr_max_softrt_rate > 0 &&
  19.         !BFQQ_TOTALLY_SEEKY(bfqq) &&
  20.         !in_burst &&
  21.         time_is_before_jiffies(bfqq->soft_rt_next_start) &&
  22.         bfqq->dispatched == 0;
  23.     /*bfqq没有bfq_bfqq_in_large_burst标记,并且bfqq处于st->idle tree很长时间则interactive1,这表示bfqq绑定的进程是交互式IO,这种进程一次性派发的IO不多,但是要求低延迟。下边执行bfq_update_bfqq_wr_on_rq_arrival()bfqq->wr_coeff=30,将来提升bfqq的权重30*/
  24.     *interactive = !in_burst && idle_for_long_time;
  25.     wr_or_deserves_wr = bfqd->low_latency &&
  26.         (bfqq->wr_coeff > 1 ||
  27.          (bfq_bfqq_sync(bfqq) && bfqq->bic && (*interactive || soft_rt)));
  28.     /*如果bfqqbfqq_non_blocking_wait_rq标记,说明之前bfqq配额足够但是没有要派发的IO请求而失效。但是在arrived_in_time时间内该bfqq又来了新的IO请求,于是该函数成立返回true,这样该bfqq有较大概率抢占bfqd->in_service_queue而尽可能快被调度使用作为新的bfqd->in_service_queue,这样就可以尽可能块派发该bfqq上新的IO请求*/
  29.     bfqq_wants_to_preempt =
  30.         bfq_bfqq_update_budg_for_activation(bfqd, bfqq,
  31.                             arrived_in_time);
  32.     /*如果bfqq不是新创建的(就是说是处于st->ilde tree),同时bfqq空闲了idle_for_long_time很长时间,并且在bfqq最后一个IO请求传输完成的时间点bfqq->budget_timeout后,过了10s+ bfqq才来了新的IO请求而激活它,于是清理bfqq_in_large_burst标记。*/
  33.     if (likely(!bfq_bfqq_just_created(bfqq)) &&//bfqq不是新创建的(就是说是处于st->ilde tree)
  34.         idle_for_long_time &&//bfqq空闲很长时间
  35.  //bfqq->budget_timeout + 10s < jiffies,就是说 bfqq->budget_timeout后已经过了10s+
  36.         time_is_before_jiffies(bfqq->budget_timeout + msecs_to_jiffies(10000))) {
  37.         //bfqq->burst_list_node链表剔除
  38.             hlist_del_init(&bfqq->burst_list_node);
  39.             bfq_clear_bfqq_in_large_burst(bfqq);
  40.     }
  41.     bfq_clear_bfqq_just_created(bfqq);
  42.     //如果bfqq被清理了bfq_bfqq_IO_bound标记
  43.     if (!bfq_bfqq_IO_bound(bfqq)) {
  44.         /*bfqq在派发完最后一个IO请求后(被移动到st->idle tree)bfqd->bfq_slice_idle * 3时间内又来新的IO请求则arrived_in_timetrue,这样bfqq->requests_within_timer就加1。如果这样连续持续120次,bfqq->requests_within_timer大于120,那就再对bfqq设置bfq_bfqq_IO_bound标记*/
  45.         if (arrived_in_time) {
  46.             bfqq->requests_within_timer++;
  47.             //bfqd->bfq_requests_within_timer默认120
  48.             if (bfqq->requests_within_timer >=                bfqd->bfq_requests_within_timer)
  49.                    bfq_mark_bfqq_IO_bound(bfqq);
  50.         } else
  51.                   bfqq->requests_within_timer = 0;
  52.     }
  53.     if (bfqd->low_latency) {
  54.         if (unlikely(time_is_after_jiffies(bfqq->split_time)))
  55.             /* wraparound */
  56.             bfqq->split_time =
  57.                 jiffies - bfqd->bfq_wr_min_idle_time - 1;
  58.         //一般情况bfqq->split_time负无穷大,这个if大部分情况都成立
  59.         if (time_is_before_jiffies(bfqq->split_time +
  60.                        bfqd->bfq_wr_min_idle_time)) {//这里成立
  61.             //根据进程是IO属性(burst IO、交互式IO、实时性IO)调整bfqq->wr_coeff
  62.             bfq_update_bfqq_wr_on_rq_arrival(bfqd, bfqq,
  63.                              old_wr_coeff,
  64.                              wr_or_deserves_wr,
  65.                              *interactive,
  66.                              in_burst,
  67.                              soft_rt);
  68.             //如果bfqq->wr_coeff变化了,于是把bfqq->entity.prio_changed1
  69.             if (old_wr_coeff != bfqq->wr_coeff)
  70.                 bfqq->entity.prio_changed = 1;
  71.         }
  72.     }
  73.     bfqq->last_idle_bklogged = jiffies;
  74.     bfqq->service_from_backlogged = 0;
  75.     bfq_clear_bfqq_softrt_update(bfqq);
  76.     //bfqq插入到st->active tree,根据bfqq->wr_coeff真正增大bfqqentity权重,标记bfqq busy
  77.     bfq_add_bfqq_busy(bfqd, bfqq);
  78.     /*如果bfqq达到抢占bfqd->in_service_queue的条件,则令bfqd->in_service_queueBFQQE_PREEMPTED而过期失效。但是并不能保证立即被调度bfqq使用,bfqq只是前边执行bfq_add_bfqq_busy()加入了st->active tree而已!*/
  79.     if (bfqd->in_service_queue &&
  80.         ((bfqq_wants_to_preempt &&//bfqq_wants_to_preempt抢占条件是1
  81.           bfqq->wr_coeff >= bfqd->in_service_queue->wr_coeff) ||
  82.          bfq_bfqq_higher_class_or_weight(bfqq, bfqd->in_service_queue)) &&
  83.         next_queue_may_preempt(bfqd))
  84.         //因抢占导致的bfqq失效
  85.         bfq_bfqq_expire(bfqd, bfqd->in_service_queue,false, BFQQE_PREEMPTED);
  86. }
  87. //bfqq派发的IO请求数全传输完成,并且bfqq处于st->idle tree已经很长时间则返回true
  88. static bool bfq_bfqq_idle_for_long_time(struct bfq_data *bfqd,
  89.                     struct bfq_queue *bfqq)
  90. {
  91.     /*bfqq->budget_timeoutbfqq最后一个IO请求完成被赋值jiffies,此时bfqq已经过期失效处于st->idle tree。然后过了大于等于bfqd->bfq_wr_min_idle_time时间(bfqq->budget_timeout+ bfqd->bfq_wr_min_idle_time < jiffies)bfqq绑定的进程要传输新的IO请求。这就是说bfqq已经idle很长时间了。*/
  92.     return bfqq->dispatched == 0 &&
  93.         time_is_before_jiffies(//bfqq->budget_timeout+ bfqd->bfq_wr_min_idle_time < jiffies返回true
  94.             bfqq->budget_timeout +
  95.             bfqd->bfq_wr_min_idle_time);
  96. }

首先再说明一下,执行bfq_bfqq_handle_idle_busy_switch()函数的流程一般是__bfq_insert_request()->bfq_add_request()->bfq_bfqq_handle_idle_busy_switch(),有两种情况:

1:进程第一次传输IO请求,分配新的bfqq,而新分配bfqq默认就有bfq_bfqq_in_large_burst标记。

2:bfqq因配额消耗光等原因而过期失效,从st->active tree移动到st->idle tree,之后bfqq就处于idle状态。然后等来了新的IO请求,执行该函数流程把bfqq激活,从st->idle tree移动到st->active tree。

当然,执行到bfq_bfqq_handle_idle_busy_switch(),就要判断bfqq绑定进程的IO传输特性,判断它是burst型IO?实时性IO?还是交互式IO?这些代码整理如下:

1:burst型IO的判定:代码是in_burst = bfq_bfqq_in_large_burst(bfqq),bfqq有bfq_bfqq_in_large_burst标记则表示bfqq绑定的进程有一段时间内大量派发IO请求的特性,就是burst型IO,这个判断比较简单。

2:实时性IO的判定:代码是soft_rt = bfqd->bfq_wr_max_softrt_rate > 0 &&!BFQQ_TOTALLY_SEEKY(bfqq) &&!in_burst &&time_is_before_jiffies(bfqq->soft_rt_next_start) &&bfqq->dispatched == 0。这个判断就复杂多了:bfqd->bfq_wr_max_softrt_rate默认大于0;!BFQQ_TOTALLY_SEEKY(bfqq)返回true,说明bfqq传输的IO是顺序IO(磁盘是sata)或者bfqq每次传输的数据量很少(磁盘是ssd),这个分析不一定合理,本文最后会详细介绍;!in_burst是说bfqq没有bfq_bfqq_in_large_burst标记,即不是burst型IO;time_is_before_jiffies(bfqq->soft_rt_next_start)是说,bfqq过期失效,从st->active tree移动到st->idle tree,bfqq处于idle状态。在过了bfqq->soft_rt_next_start设定的时间后,bfqq又来了新的IO请求,则time_is_before_jiffies(bfqq->soft_rt_next_start)返回true。这个是判断实时型IO的关键,在后续的文章在详细介绍;bfqq->dispatched == 0是说bfqq之前的IO请求全传输完成了。

3:交互式IO的判定:代码是*interactive = !in_burst && idle_for_long_time。就是说,bfqq不是in_burst型IO。idle_for_long_time为true的判定比较复杂,它是bfq_bfqq_idle_for_long_time()的返回值,源码前文有贴。简单说,在bfqq处于idle状态(处于st->idle tree)后,在bfqq最后一个IO请求传输完成后,过了bfqd->bfq_wr_min_idle_time毫秒后,bfqq来了新的IO请求。感觉交互式IO的判断主要是说,bfqq没有大量IO传输的特性,但是会突然来了IO请求,必须低延时。

ok,除了这几点,bfq_bfqq_handle_idle_busy_switch()函数中还有其他几个关键要点。

arrived_in_time 变量: bfqq->ttime.last_end_request是bfqq最后一个IO请求传输完成的时间,arrived_in_time是true,说明bfqq最后一个IO请求传输完成后(此时bfqq已经过期失效),在bfqd->bfq_slice_idle * 3这段空闲时间内,bfqq绑定的进程再次派发IO请求而执行该函数激活bfqq。为了bfqq绑定的进程IO延迟低,就要用bfqq抢占bfqd->in_service_queue,从而尽可能快的派发该bfqq绑定的进程新的IO请求。arrived_in_time此时是true,下边执行bfq_bfqq_update_budg_for_activation()用到它,如果bfqq再有bfq_bfqq_non_blocking_wait_rq标记,则该函数返回true,说明bfqq很大可能会抢占bfqd->in_service_queue而尽可能快被调度使用作为新的bfqd->in_service_queue,这样就可以尽可能块派发bfqq新的IO请求。

bfq_bfqq_in_large_burst标记:代码是in_burst = bfq_bfqq_in_large_burst(bfqq)。bfq_bfqq_in_large_burst 我的理解是,表示一段bfqq一段时间内大量派发IO请求,如果它派发IO后空闲了很长一段时间,那就说明bfqq不再具有bfqq_in_large_burst标记了,就清理掉。

bfq_bfqq_IO_bound标记 : 代码是if (!bfq_bfqq_IO_bound(bfqq))里边。表示向bfqq队列插入IO请求很快。如果bfqq在传输完最后一个IO请求后(被移动到st->idle tree)的bfqd->bfq_slice_idle * 3时间内,又来新的IO请求则arrived_in_time为true。这样bfqq->requests_within_timer就加1,如果这样持续120次,bfqq->requests_within_timer大于120,那就再对bfqq设置bfq_bfqq_IO_bound标记。

最后,重点是bfq_bfqq_handle_idle_busy_switch()函数里执行bfq_update_bfqq_wr_on_rq_arrival()函数。这是根据根据进程的IO特性(burst型IO、实时性IO、还是交互式IO),决定调整权重系数bfqq->wr_coeff。这个函数下边讲解。

2.2 调整权重系数bfqq->wr_coeff和真正提升进程bfqq的权重entity->weight

先看下bfq_update_bfqq_wr_on_rq_arrival()函数源码

  1. static void bfq_update_bfqq_wr_on_rq_arrival(struct bfq_data *bfqd,
  2.                          struct bfq_queue *bfqq,
  3.                          unsigned int old_wr_coeff,//bfqq老的的wr_coeff值,该函数里会更新它
  4.                          bool wr_or_deserves_wr,//wr_or_deserves_wrtrue表示bfqqbfqq->wr_coeff大于1,或者bfqq是交互式或者实时同步IO
  5.                          bool interactive,//interactivetrue表示进程是交互式IO
  6.                          bool in_burst,//in_bursttrue表示进程是普通的大量传输IO
  7.                          bool soft_rt)//soft_rttrue表示进程是实时性IO
  8. {
  9.     //old_wr_coeff == 1 : bfqq老的wr_coeff1,没有权重提升
  10.     //bfqq正在提升权重,或者bfqq是同步IO并且想要提升权重(因为bfqq是交互式IO或者实时性IO),则wr_or_deserves_wr1
  11.     if (old_wr_coeff == 1 && wr_or_deserves_wr) {
  12.         /* start a weight-raising period */
  13.         //走这个分支说明bfqq是交互式IO
  14.         if (interactive) {//测试这里成立
  15.             bfqq->service_from_wr = 0;
  16.             //权重提升系数bfqq->wr_coeff更新为30,之后__bfq_entity_update_weight_prio()中根据bfqq->wr_coeff增加bfqq的权重
  17.             bfqq->wr_coeff = bfqd->bfq_wr_coeff;
  18.             //更新bfqq的权重提升时间bfqq->wr_cur_max_timebfq_wr_duration()
  19.             bfqq->wr_cur_max_time = bfq_wr_duration(bfqd);
  20.         } else {
  21.         //走这个分支说明bfqq是实时IO
  22.             //bfqq->wr_start_at_switch_to_srt赋值负无穷大
  23.             bfqq->wr_start_at_switch_to_srt = bfq_smallest_from_now();
  24.             //bfqq->wr_coeff更新 30*BFQ_SOFTRT_WEIGHT_FACTOR,显然实时IObfqq权重提升系数更大
  25.             bfqq->wr_coeff = bfqd->bfq_wr_coeff *
  26.                 BFQ_SOFTRT_WEIGHT_FACTOR;
  27.             //更新bfqq->wr_cur_max_time为实时性IO最大的权重提升时间bfqd->bfq_wr_rt_max_time
  28.             bfqq->wr_cur_max_time =
  29.                 bfqd->bfq_wr_rt_max_time;
  30.         }
  31.         bfqq->entity.budget = min_t(unsigned long,
  32.                         bfqq->entity.budget,
  33.                         2 * bfq_min_budget(bfqd));
  34. }
  35. //走这个分支说明bfqq老的权重提升系数bfqq->wr_coeff大于1
  36.     else if (old_wr_coeff > 1)
  37.     {
  38.         //走这个分支说明bfqq是交互式IO,再次更新bfqq->wr_coeffbfqq->wr_cur_max_time
  39.         if (interactive) {
  40.             bfqq->wr_coeff = bfqd->bfq_wr_coeff;
  41.             bfqq->wr_cur_max_time = bfq_wr_duration(bfqd);
  42.         } else if (in_burst)
  43.            //走这个分支说明bfqq是普通的大量传输IO,则还原权重提升系数bfqq->wr_coeff1
  44.             bfqq->wr_coeff = 1;
  45.         else if (soft_rt) {//走这个分支说明bfqq是实时性IO
  46.             //如果bfqq->wr_cur_max_time不是实时性IO的最大的权重提升时间bfqd->bfq_wr_rt_max_time
  47.             if (bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time)
  48.             {
  49.                 //bfqq->wr_start_at_switch_to_srt更新为bfqq上次权重提升时间
  50.                 bfqq->wr_start_at_switch_to_srt = bfqq->last_wr_start_finish;
  51.                 //bfqq->wr_cur_max_time更新为实时性IO最大的权重提升时间
  52.                 bfqq->wr_cur_max_time = bfqd->bfq_wr_rt_max_time;
  53.                 //bfqq->wr_coeff更新 30*BFQ_SOFTRT_WEIGHT_FACTOR
  54.                 bfqq->wr_coeff = bfqd->bfq_wr_coeff * BFQ_SOFTRT_WEIGHT_FACTOR;
  55.             }
  56.            
  57.             /*显然,如果进程bfqq是实时性IO并且bfqq->wr_coeff在执行bfq_add_request()->bfq_bfqq_handle_idle_busy_switch()->             bfq_update_bfqq_wr_on_rq_arrival()函数时bfqq->wr_coeff已经大于1,则更新bfqq->last_wr_start_finish=jiffies。就是说,实时性IO的进程bfqq权重提升后,每次执行到bfq_update_bfqq_wr_on_rq_arrival()都更新bfqq->last_wr_start_finish*/
  58.             bfqq->last_wr_start_finish = jiffies;
  59.         }
  60.     }
  61. }

注释已经写的比较清楚,这里再做个整体总结:

1:该函数的执行流程是: __bfq_insert_request ()->bfq_add_request()->bfq_bfqq_handle_idle_busy_switch()->bfq_update_bfqq_wr_on_rq_arrival(),该函数的执行时机是bfqq是新创建的 或者 bfqq原本是idle状态(bfqq的entity处于处于st->idle tree)。现在bfqq来了新的IO请求而要激活bfqq,首先在bfq_bfqq_handle_idle_busy_switch()根据bfqq的idle时间、bfqq的bfq_bfqq_in_large_burst标记等等,判定判断bfqq的IO特性,是burst型IO(in_burst是1)? 实时性IO(soft_rt是1)? 交互式IO(interactive是1)?然后执行bfq_update_bfqq_wr_on_rq_arrival()根据bfqq的IO特性,更新bfqq->wr_coeff、bfqq->wr_cur_max_time、bfqq->last_wr_start_finish。

2:bfqq->wr_coeff是bfqq的权重提升系数,回到 bfq_bfqq_handle_idle_busy_switch()函数,执行 bfq_add_bfqq_busy->bfq_activate_bfqq->bfq_activate_requeue_entity->

__bfq_activate_requeue_entity->__bfq_activate_entity->bfq_update_fin_time_enqueue->__bfq_entity_update_weight_prio(),在__bfq_entity_update_weight_prio()中令bfqq的默认权重乘以bfqq->wr_coeff,就是增大bfqq的权重。

3:bfqq->wr_cur_max_time是进程的权重提升时间。派发IO请求时bfq_dispatch_rq_from_bfqq->bfq_update_wr_data,如果进程权重提升时间到了,则可能要执行bfq_bfqq_end_wr()就要令bfqq结束提升权重。

需要特别说明几点

1:bfq_update_bfqq_wr_on_rq_arrival()的执行时机一般都是bfqq原本处于idle状态(处于st->active tree),bfqq有了新的IO请求,激活bfqq(把bfqq移入到st->active tree)。bfqq激活后,再向进程的bfqq添加IO请求,此时并不会执行到bfq_update_bfqq_wr_on_rq_arrival(),除非bfqq再次过期失效,处于idle状态,被移入st->ilde tree。

2:bfqq->wr_start_at_switch_to_srt的更新时机,是bfq_update_bfqq_wr_on_rq_arrival()里因bfqq先被判定交互式IO。if (interactive)成立则bfqq->wr_cur_max_time = bfq_wr_duration(bfqd)和bfqq->wr_coeff=30。如果bfqq被判定是实时性IO,下边的if (bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time)成立,则更新bfqq->wr_start_at_switch_to_srt = bfqq->last_wr_start_finish。

最后补充一点,if (bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time) 这个if什么情况下成立呢?我的分析是,bfqq原本处于idle状态(处于st->ilde tree),然后bfqq的进程来了新的IO请求,执行到bfq_add_request()->bfq_bfqq_handle_idle_busy_switch()->bfq_update_bfqq_wr_on_rq_arrival()函数,判断bfqq是交互式IO,if (interactive)成立,则执行bfqq->wr_coeff=30和bfqq->wr_cur_max_time = bfq_wr_duration(bfqd)。然后bfqq派发完IO请求,过期失效,再次处于idle状态。

接着bfqq绑定的进程又来的新的IO请求,还是执行bfq_add_request()->bfq_bfqq_handle_idle_busy_switch()->bfq_update_bfqq_wr_on_rq_arrival(),但是bfqq绑定的进程被判定是实时性IO,下边的if (bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time)成立,则再次增大bfqq->wr_coeff和bfqq->wr_cur_max_time。

前文提过,__bfq_insert_request ()->bfq_add_request()->bfq_bfqq_handle_idle_busy_switch()->bfq_update_bfqq_wr_on_rq_arrival()只会增大bfqq的权重系数bfqq->wr_coeff,真正提升权重是在紧接着执行的bfq_bfqq_handle_idle_busy_switch()->bfq_add_bfqq_busy->bfq_activate_bfqq->bfq_activate_requeue_entity->__bfq_activate_requeue_entity->__bfq_activate_entity->bfq_update_fin_time_enqueue->__bfq_entity_update_weight_prio()函数,源码不复杂,如下:

  1. //主要是根据bfqq->wr_coeff计算bfqq新的权重
  2. struct bfq_service_tree * __bfq_entity_update_weight_prio(struct bfq_service_tree *old_st,//entity所属调度器st
  3.                 struct bfq_entity *entity,
  4.                 bool update_class_too)
  5. {
  6.     ...................
  7.     //老的调度器st->wsum减去entity->weight
  8.     old_st->wsum -= entity->weight;
  9.     if (entity->new_weight != entity->orig_weight)
  10.     {
  11.         if (entity->new_weight < BFQ_MIN_WEIGHT ||entity->new_weight > BFQ_MAX_WEIGHT) {
  12.             //更新entity->new_weight
  13.             if (entity->new_weight < BFQ_MIN_WEIGHT)
  14.                 entity->new_weight = BFQ_MIN_WEIGHT;
  15.             else
  16.                 entity->new_weight = BFQ_MAX_WEIGHT;
  17.         }
  18.         //更新entity->orig_weight
  19.         entity->orig_weight = entity->new_weight;
  20.         //更新bfqq->ioprio
  21.         if (bfqq)
  22.             bfqq->ioprio = bfq_weight_to_ioprio(entity->orig_weight);
  23.         .................
  24.         new_st = bfq_entity_service_tree(entity);
  25.         prev_weight = entity->weight;
  26.         //根据bfqq->wr_coeff计算bfqq新的权重,很明显bfqq->wr_coeff越大计算出的权重越大
  27.         new_weight = entity->orig_weight * (bfqq ? bfqq->wr_coeff : 1);
  28.         ...............
  29.         ///把新的权重更新到bfqqentity->weight
  30.         entity->weight = new_weight;
  31.         ..........
  32.         //调度器的st->wsum累加entity新的权重entity->weight
  33.         new_st->wsum += entity->weight;
  34.     }
  35.     //测试new_st old_st是同一个
  36.     return new_st;
  37. }

2.3 结束提升进程bfqq的权重

进程bfqq的权重不是一直提升的,总有结束的时刻,什么时间会结束呢?在派发IO请求的过程bfq_dispatch_rq_from_bfqq->bfq_update_wr_data(),有概率结束进程bfqq的权重,下边看下bfq_update_wr_data()源码:

  1. //如果bfqq的权重提升时间用完了 或者 bfqq因权重提升消耗的配额达到了限制,则结束bfqq权重提升,bfqq->wr_coeff恢复为1
  2. static void bfq_update_wr_data(struct bfq_data *bfqd, struct bfq_queue *bfqq)
  3. {
  4.     struct bfq_entity *entity = &bfqq->entity;
  5.     if (bfqq->wr_coeff > 1) { /* queue is being weight-raised */
  6.         //bfqq拥有bfq_bfqq_in_large_burst标记的话,就不能再权重提升了,要结束权重提升
  7.         if (bfq_bfqq_in_large_burst(bfqq))
  8.             //bfqq结束权重提升,bfqq->wr_coeff bfqq->last_wr_start_finish恢复到权重提升前的状态等等
  9.             bfq_bfqq_end_wr(bfqq);
  10.         /*bfqq->last_wr_start_finishbfqq权重更新开始时间,bfqq->wr_cur_max_timebfqq权重更新时间,该if成立说明bfqq的权重更新时间用完了,则可能就需要令bfqq->wr_coeff bfqq->last_wr_start_finish恢复到权重提升前的状态等等*/
  11.         else if (time_is_before_jiffies(bfqq->last_wr_start_finish +
  12.                         bfqq->wr_cur_max_time))
  13.         {
  14.             /*这个if是说,如果是交互式IObfqq的提升权重时间过了bfqq->wr_cur_max_time毫秒,或者bfqq由交互式IO特性切换到实时性IO特性而进一步提升了权重,又过了bfq_wr_duration(bfqd)毫秒,则执行bfq_bfqq_end_wr()bfqq结束权重提升*/
  15.             if (bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time ||
  16.                 time_is_before_jiffies(bfqq->wr_start_at_switch_to_srt + bfq_wr_duration(bfqd)))
  17.                 //bfqq结束权重提升,bfqq->wr_coeff bfqq->last_wr_start_finish恢复到权重提升前的状态等等
  18.                 bfq_bfqq_end_wr(bfqq);
  19.             else {
  20.                 //bfqq回到权重提升状态,增大bfqq->wr_coeff30,更新bfqq->wr_cur_max_time为权重提升时间
  21.                 switch_back_to_interactive_wr(bfqq, bfqd);
  22.                 //bfqq->entity.prio_changed1表示bfqq->wr_coeff更新了
  23.                 bfqq->entity.prio_changed = 1;
  24.             }
  25.         }
  26.         /*bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time表示bfqq是交互式IO而提升权重,见bfq_update_bfqq_wr_on_rq_arrival()bfqq->service_from_wr > max_service_from_wr 表示因bfqq权重提升而消耗的配额超过上限。这个if判断是说,如果交互式IO的进程在权重提升后消耗的配额超过max_service_from_wr则要结束bfqq的权重提升了。这样的话,实时性IO进程权重提升是没有配额限制的,交互式IO进程权重提升是有配额限制的*/
  27.         if (bfqq->wr_coeff > 1 &&
  28.             bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time &&
  29.             bfqq->service_from_wr > max_service_from_wr) {
  30.             //bfqq结束权重提升,bfqq->wr_coeff bfqq->last_wr_start_finish恢复到权重提升前的状态等等
  31.                  bfq_bfqq_end_wr(bfqq);
  32.           }
  33.     }
  34.     if ((entity->weight > entity->orig_weight) != (bfqq->wr_coeff > 1))
  35.         //这里是重点,主要是根据最新的bfqq->wr_coeff计算bfqq新的权重
  36.         __bfq_entity_update_weight_prio(bfq_entity_service_tree(entity),
  37.                         entity, false);
  38. }

注释写的比较清楚,有几个重点再说下:

if (bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time || time_is_before_jiffies(bfqq->wr_start_at_switch_to_srt + bfq_wr_duration(bfqd)))的判断,有两种情况if成立:

1:bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time表示进程bfqq是交互式IO而提升权重。

2:time_is_before_jiffies(bfqq->wr_start_at_switch_to_srt + bfq_wr_duration(bfqd))说明进程bfqq由交互式特性IO切换到是实时性IO,更新bfqq->wr_start_at_switch_to_srt(见bfq_update_bfqq_wr_on_rq_arrival()),并把bfqq->wr_coeff 进一步更新到 30*BFQ_SOFTRT_WEIGHT_FACTOR,然后把bfqq激活。之后派发bfqq上的IO请求,执行到bfq_dispatch_rq_from_bfqq->bfq_update_wr_data(),此时过了bfq_wr_duration(bfqd)毫秒,则该if成成立。

if ((entity->weight > entity->orig_weight) != (bfqq->wr_coeff > 1)),if成立有两种情况:

1:(entity->weight > entity->orig_weight) 并且bfqq->wr_coeff == 1 

2:(entity->weight<=entity->orig_weight)并且bfqq->wr_coeff > 1

第1种情况是说,bfqq原本权重提升,bfqq->wr_coeff >1,但是bfqq的权重提升时间用完了或者bfqq因权重提升消耗的配额达到了限制,则上边执行bfq_bfqq_end_wr()结束bfqq权重提升而令bfqq->wr_coeff=1,此时if成立,执行__bfq_entity_update_weight_prio()真正减小bfqq的权重。第2种情况,是bfqq还没有提升权重,但是bfqq->wr_coeff > 1,if成立,执行__bfq_entity_update_weight_prio()真正增大bfqq的权重。

3:bfq_update_io_thinktime

在向bfqq插入IO请求的过程,会执行bfq_update_io_thinktime()、bfq_update_has_short_ttime()、bfq_update_io_seektime()函数更新thinktime、short_ttime、seektime几个重要的参数,源码如下:

  1. static bool __bfq_insert_request(struct bfq_data *bfqd, struct request *rq)
  2. {
  3.     //更新ttime->ttime_samplesttime->ttime_totalttime->ttime_mean
  4.     bfq_update_io_thinktime(bfqd, bfqq);
  5.     //更新bfqqhas_short_ttime状态
  6.     bfq_update_has_short_ttime(bfqd, bfqq, RQ_BIC(rq));
  7.     //这里边更新bfqq->seek_history
  8.     bfq_update_io_seektime(bfqd, bfqq, rq);
  9.     ..........
  10.     //IO请求添加到bfqq->sort_list链表,并选择合适的IO请求赋于bfqq->next_rq。接着可能激活bfqq,把bfqq添加到st->active tree
  11.     bfq_add_request(rq);
  12. }

这里先看下bfq_update_io_thinktime()里对thinktime的更新:

  1. //__bfq_insert_request()->bfq_update_io_thinktime()
  2. static void bfq_update_io_thinktime(struct bfq_data *bfqd,
  3.                     struct bfq_queue *bfqq)
  4. {
  5.     struct bfq_ttime *ttime = &bfqq->ttime;
  6.     /*elapsedbfqq上最近一次的IO请求传输完成到添加本次IO请求到bfqq的时间,这段时间bfqq是空闲状态,这就是thinktime吧。elapsed应该可以理解成bfqq每传输的两个IO请求之间的空闲时间,越大说明bfqq绑定的进程向bfqq插入IO请求越慢*/
  7.     u64 elapsed = ktime_get_ns() - bfqq->ttime.last_end_request;
  8.     //取最小时间
  9.     elapsed = min_t(u64, elapsed, 2ULL * bfqd->bfq_slice_idle);
  10.     //ttime->ttime_samples应该可以这样理解,每次执行到该函数令ttime->ttime_samples增加8
  11.     ttime->ttime_samples = (7*bfqq->ttime.ttime_samples + 256) / 8;
  12.     //ttime->ttime_total应该可以理解成每次增加8*elapsedelapsed越大,ttime->ttime_total越大
  13.     ttime->ttime_total = div_u64(7*ttime->ttime_total + 256*elapsed,  8);
  14.     //ttime->ttime_mean显然是ttime->ttime_total除以ttime->ttime_samples
  15.     ttime->ttime_mean = div64_ul(ttime->ttime_total + 128,
  16.                      ttime->ttime_samples);
  17. }

什么是thinktime?thinktime有什么意义?需要先看下有关的ttime->ttime_samples、ttime->ttime_total、ttime->ttime_mean的3个参数。

bfq_update_io_thinktime()的执行时机是在向bfqq插入IO请求时。每插入一个IO请求,bfqq->ttime.ttime_samples增加8,ttime->ttime_total是累加时间差elapsed*8,ttime->ttime_mean是ttime->ttime_samples除以ttime->ttime_mean。因此,简单理解下,bfqq->ttime.ttime_samples是每派发一个IO请求则加8,ttime->ttime_total是累加bfqq最近一次IO请求传输完成的时间与插入本次IO请求到bfqq的时间,ttime->ttime_mean是ttime->ttime_total/bfqq->ttime.ttime_samples。

再简单理解,ttime->ttime_samples可以理解成是bfqq传输的IO请求数,ttime->ttime_total是bfqq每传输的两个IO请求之间的空闲时间之和,ttime->ttime_mean是平均bfqq每传输的两个IO请求之间的空闲时间。ttime->ttime_mean越大,说明bfqq绑定的进程向bfqq插入IO请求越慢

4:bfq_bfqq_has_short_ttime和bfq_update_io_seektime

bfqq默认就有bfq_bfqq_has_short_ttime标记,是在bfqq创建时。它的更新是在bfq_update_has_short_ttime()函数,如下:

  1. //__bfq_insert_request()->bfq_update_has_short_ttime()
  2. static void bfq_update_has_short_ttime(struct bfq_data *bfqd,
  3.                        struct bfq_queue *bfqq,
  4.                        struct bfq_io_cq *bic)
  5. {
  6.     //has_short_ttime默认是true
  7.     bool has_short_ttime = true, state_changed;
  8.     ....................
  9.     //这个if可能会成立吗?正常情况bfqq->split_time负无穷大,该if正常应该不会成立
  10.     if (time_is_after_eq_jiffies(bfqq->split_time +
  11.                      bfqd->bfq_wr_min_idle_time))
  12.           return;
  13.     /*ttime->ttime_mean越大,说明bfqq绑定的进程向bfqq插入IO请求越慢。该if此时bfqq->ttime.ttime_mean > bfqd->bfq_slice_idle成立。bfq_sample_valid(bfqq->ttime.ttime_samples)true说明传输的IO请求数大于80。二者都成立,说明,进程向bfqq->sort_list插入IO请求太慢了,于是has_short_ttime = false,下边则会bfq_clear_bfqq_has_short_ttime*/
  14. if (atomic_read(&bic->icq.ioc->active_ref) == 0 ||
  15.      //bfqq->ttime.ttime_samples大于80
  16.         (bfq_sample_valid(bfqq->ttime.ttime_samples) &&
  17.         //bfqq->ttime.ttime_mean大于8ms
  18.          bfqq->ttime.ttime_mean > bfqd->bfq_slice_idle))
  19.                 has_short_ttime = false;
  20.     ....................
  21.     state_changed = has_short_ttime != bfq_bfqq_has_short_ttime(bfqq);
  22.     if (has_short_ttime)
  23.          bfq_mark_bfqq_has_short_ttime(bfqq);
  24.     else
  25.           bfq_clear_bfqq_has_short_ttime(bfqq);
  26.     ....................
  27. }

bfq_bfqq_has_short_ttime应该是说bfqq拥有短时间快速向bfqq插入IO请求的一种属性,bfqq默认有这种属性,但是如果向bfqq插入IO请求太慢就会清理掉该属性。bfq_bfqq_has_short_ttime标记的应用应该是在idling_boosts_thr_without_issues()函数比较明显,说明进程向bfqq->sort_list插入IO请求很快,在bfqq派发IO请求没有了,bfqq->sort_list是空,先不让bfqq过期失效,而是启动idle timer,等一小段时间,看bfqq有没有来新的IO请求,没有的话再令bfqq过期失效,这样可以提升性能。

seektime与bfqq的BFQQ_SEEKY属性有关,相关源码如下:

  1. //__bfq_insert_request()->bfq_update_io_seektime()
  2. static void
  3. bfq_update_io_seektime(struct bfq_data *bfqd, struct bfq_queue *bfqq,
  4.                struct request *rq)
  5. {
  6.     /*bfqq->seek_history左移一位,每派发一个seek IObfqq->seek_historybit0就置1,并且左移一位。bfqq派发的seek IO越多,bfqq->seek_history的是1bit位越多*/
  7.     bfqq->seek_history <<= 1;
  8.     //根据bfqq前后派发的两个IO的扇区地址和本次派发IO的字节数,判断出本次派发的IOseek IO的话,则把bfqq->seek_historybit01
  9.     bfqq->seek_history |= BFQ_RQ_SEEKY(bfqd, bfqq->last_request_pos, rq);
  10.     if (bfqq->wr_coeff > 1 &&
  11.         bfqq->wr_cur_max_time == bfqd->bfq_wr_rt_max_time &&
  12.         BFQQ_TOTALLY_SEEKY(bfqq))
  13.               bfq_bfqq_end_wr(bfqq);
  14. }

判断seek io以及相关的宏定义在下边:

  1. //判断是否是seek io
  2. #define BFQ_RQ_SEEKY(bfqd, last_pos, rq) (get_sdist(last_pos, rq) > BFQQ_SEEK_THR &&   \
  3.            (!blk_queue_nonrot(bfqd->queue) ||blk_rq_sectors(rq) < BFQQ_SECT_THR_NONROT))
  4. //计算bfqq->seek_history有多少个bit位是1
  5. #define BFQQ_SEEKY(bfqq)    (hweight32(bfqq->seek_history) > 19)
  6. #define BFQQ_TOTALLY_SEEKY(bfqq)    (bfqq->seek_history & -1)

重点是BFQ_RQ_SEEKY,它正是判断seek io的。而BFQQ_SEEKY为true说明bfqq派发的IO中有大于19个seek io。只要bfqq有一个seek io则BFQQ_TOTALLY_SEEKY返回true,如果bfqq派发的IO没有一个seek io,BFQQ_TOTALLY_SEEKY才会返回false。

因此,重点是判断seek io的BFQ_RQ_SEEKY宏定义。它返回true有两种情况:

1:磁盘是ssd时,前后两次传输的IO请求前后扇区地址相差大于800个扇区并且本次传输的IO请求扇区数小于64

2:磁盘是sata时,前后两次传输的IO请求前后扇区地址相差大于800个扇区

我对seek io做个简单总结,不一定合理:磁盘是sata时派发的IO是随机IO,则是seek io;磁盘是ssd时派发的IO传输的数据量(即扇区数)很少。

5:bfq_bfqq_IO_bound 和 bfq_bfqq_in_large_burst再谈谈

bfqq的bfq_bfqq_IO_bound 和 bfq_bfqq_in_large_burst标记,前文已经说下,这里再总结下。在把bfqq激活插入st->active tree时执行的bfq_bfqq_handle_idle_busy_switch()函数里,有概率执行bfq_clear_bfqq_in_large_burst和bfq_mark_bfqq_IO_bound。

  1. static void bfq_bfqq_handle_idle_busy_switch(struct bfq_data *bfqd,
  2.                          struct bfq_queue *bfqq,
  3.                          int old_wr_coeff,
  4.                          struct request *rq,
  5.                          bool *interactive)
  6. {
  7.     //bfqq在传输完最后一个IO请求后的bfqd->bfq_slice_idle * 3时间内又来新的IO请求则arrived_in_timetrue
  8.     arrived_in_time =  ktime_get_ns() <=
  9.             bfqq->ttime.last_end_request +
  10.             bfqd->bfq_slice_idle * 3;
  11.     ..........................
  12.     //bfqqbfq_bfqq_in_large_burst标记则in_bursttrue,in_burst表示bfqq绑定的进程有一段时间内大量派发IO请求的特性
  13.     in_burst = bfq_bfqq_in_large_burst(bfqq);
  14.     ..........................
  15.     /*如果bfqq不是新创建的(就是说是处于st->ilde tree),并且bfqq空闲很长时间idle_for_long_time,并且在bfqq最后一个IO请求传输完成的时间点bfqq->budget_timeout后,过了10s+ bfqq才来了新的IO请求而激活它,于是清理bfqq_in_large_burst标记。bfq_bfqq_in_large_burst 我的理解是,表示一段bfqq一段时间内大量派发IO请求,如果它派发IO后空闲了很长一段时间,那就说明bfqq不再具有bfqq_in_large_burst标记了,该清理掉*/
  16.     if (likely(!bfq_bfqq_just_created(bfqq)) &&//bfqq不是新创建的(就是说是处于st->ilde tree)
  17.         idle_for_long_time &&//bfqq空闲很长时间
  18.         time_is_before_jiffies(//bfqq->budget_timeout + 10s < jiffies,就是说 bfqq->budget_timeout后已经过了10s+
  19.             bfqq->budget_timeout +
  20.             msecs_to_jiffies(10000))) {
  21.                 //bfqq->burst_list_node链表剔除
  22.                 hlist_del_init(&bfqq->burst_list_node);
  23.                 //清理掉 bfq_bfqq_in_large_burst 标记
  24.                 bfq_clear_bfqq_in_large_burst(bfqq);
  25.     }
  26.     .........................
  27.     //如果bfqq被清理了bfq_bfqq_IO_bound标记
  28.     if (!bfq_bfqq_IO_bound(bfqq)) {
  29.         /*bfqq在传输完最后一个IO请求后(被移动到st->idle tree)bfqd->bfq_slice_idle * 3时间内又来新的IO请求则arrived_in_timetrue,这样bfqq->requests_within_timer就加1。如果这样持续120次,bfqq->requests_within_timer大于120,那就再对bfqq设置bfq_bfqq_IO_bound标记。但是又一次不成立,就对bfqq->requests_within_timer*/
  30.         if (arrived_in_time) {
  31.               bfqq->requests_within_timer++;
  32.               //bfqd->bfq_requests_within_timer默认120
  33.               if (bfqq->requests_within_timer >=bfqd->bfq_requests_within_timer)
  34.                    bfq_mark_bfqq_IO_bound(bfqq);
  35.         } else
  36.              bfqq->requests_within_timer = 0;
  37.     }
  38. }

bfq_bfqq_in_large_burst表示bfqq大量快速传输IO请求,不会空闲较长时间。burst IO就是靠它判定的。如果bfqq过期失效被移入st->idle tree,过了很长时间bfqq才来新的IO请求,就要清理掉bfqq bfq_bfqq_in_large_burst标记。显然,bfqq空闲时间太长了,不再具备大量快速传输IO请求特性了。

bfqq默认就有bfq_bfqq_IO_bound标记,如果bfqq因为BFQQE_TOO_IDLE过期失效而执行bfq_bfqq_expire()函数,并且bfqq只消耗了很少的配额,则执行bfq_clear_bfqq_IO_bound清理掉bfq_bfqq_IO_bound标记。如下:

  1. void bfq_bfqq_expire(struct bfq_data *bfqd,
  2.              struct bfq_queue *bfqq,
  3.              bool compensate,
  4.              enum bfqq_expiration reason)
  5. {
  6. //如果bfqq超时原因是BFQQE_TOO_IDLE,并且bfqq只消化了很小一部分配额则令clear bfq_clear_bfqq_IO_bound
  7.     /*bfqq创建时默认就有bfq_bfqq_IO_bound属性,如果bfqqIO请求派发完了但是配额没消耗光,则bfq_completed_request()中可能启动idle_slice_timer 定时器,并设置bfq_mark_bfqq_wait_request。等idle_slice_timer定时时间到,执行bfq_idle_slice_timer_body()bfqq依然没来新的IO请求,于是就令bfqqBFQQE_TOO_IDLE而过期失效。执行到这里,bfqq消耗的配额只有不到entity->budget的五分之一,于是清理bfqqbfq_bfqq_IO_bound属性。因此,可以看出来,bfq_bfqq_IO_bound应该表示bfqq表示短时间大量传输IO请求的属性,如果bfqq没有短时间大量传输IO请求就过期失效,就清理掉该属性。*/
  8.     if (reason == BFQQE_TOO_IDLE &&
  9.         entity->service <= 2 * entity->budget / 10)
  10.               bfq_clear_bfqq_IO_bound(bfqq);
  11. }

什么情况下bfqq会因为BFQQE_TOO_IDLE过期失效呢?注释里说的比较清楚。如果bfqq的IO请求派发完了但是配额没消耗光,在bfqq最后一个IO请求传输完成,执行bfq_completed_request()函数。在该函数里没有立即令bfqq过期失效,因为bfqq可能马上就来了新的IO请求。于是启动idle_slice_timer 定时器,并设置bfq_mark_bfqq_wait_request标记。等idle_slice_timer定时时间到,执行定时器函数bfq_idle_slice_timer_body(),bfqq依然没来新的IO请求,于是就令bfqq因BFQQE_TOO_IDLE而过期失效。

执行bfq_mark_bfqq_IO_bound对bfqq加上bfq_bfqq_IO_bound标记是在bfq_bfqq_handle_idle_busy_switch()函数。注释说的比较清楚,简单说bfqq处于ilde状态(st->idle  tree)并且最后一个IO请求传输完成,在bfqd->bfq_slice_idle * 3时间内bfqq来了新的IO请求。这样持续多次,就bfq_mark_bfqq_IO_bound对bfqq加上bfq_bfqq_IO_bound标记。idling_boosts_thr_without_issues()会根据bfq_bfqq_IO_bound标记,判断bfqq是否可能会来新的IO请求,会来的话则该函数返回true。

因此,觉得bfq_bfqq_IO_bound标记是说,在bfqq空闲时,很快就会来新的IO请求并插入到bfqq的队列。那bfq_bfqq_has_short_ttimebfq_bfqq_IO_bound有什么区别呢?我觉得bfq_bfqq_has_short_ttime反应的是在bfqq已经激活状态下(bfqqentity处于st->active tree,准确说正在派发bfqq上的IO请求),进程快速向bfqq的队列插入IO请求的特性。

本文是我阅读bfq源码的一些理解,可能有理解不对的地方。水平有限,本文如有错误请指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值