刚提交了一篇《内核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_issue是true
  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_queueing为true
  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_thr为true,并且没有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 tree的bfqq的数量少(或者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是新创建的或者bfqq在st->idle tree该if才成立
  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_coeff是1 或者 新的bfqq->wr_coeff是1 或者 bfqq是交互式IO,这个if就会成立。除非bfqq老的bfqq->wr_coeff大于1,并且bfqq是实时性IO(这样新的bfqq->wr_coeff不是1,并且interactive是0),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_time为true
  12. arrived_in_time =  ktime_get_ns() <=
  13. bfqq->ttime.last_end_request +
  14. bfqd->bfq_slice_idle * 3;
  15. //bfqq有bfq_bfqq_in_large_burst标记则in_burst为true,in_burst表示bfqq绑定的进程有一段时间内大量派发IO请求的特性
  16. in_burst = bfq_bfqq_in_large_burst(bfqq);
  17. //soft_rt为true表示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很长时间则interactive是1,这表示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. /*如果bfqq有bfqq_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_time为true,这样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_changed置1
  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真正增大bfqq的entity权重,标记bfqq busy
  77. bfq_add_bfqq_busy(bfqd, bfqq);
  78. /*如果bfqq达到抢占bfqd->in_service_queue的条件,则令bfqd->in_service_queue因BFQQE_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_timeout在bfqq最后一个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_wr为true表示bfqq的bfqq->wr_coeff大于1,或者bfqq是交互式或者实时同步IO
  5. bool interactive,//interactive为true表示进程是交互式IO
  6. bool in_burst,//in_burst为true表示进程是普通的大量传输IO
  7. bool soft_rt)//soft_rt为true表示进程是实时性IO
  8. {
  9. //old_wr_coeff == 1 : bfqq老的wr_coeff是1,没有权重提升
  10. //bfqq正在提升权重,或者bfqq是同步IO并且想要提升权重(因为bfqq是交互式IO或者实时性IO),则wr_or_deserves_wr是1
  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_time为bfq_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,显然实时IO的bfqq权重提升系数更大
  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_coeff和bfqq->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_coeff为1
  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. /*显然,如果进程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*/
  57. bfqq->last_wr_start_finish = jiffies;
  58. }
  59. }
  60. }

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

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. ///把新的权重更新到bfqq的entity->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_finish是bfqq权重更新开始时间,bfqq->wr_cur_max_time是bfqq权重更新时间,该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是说,如果是交互式IO的bfqq的提升权重时间过了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_coeff到30,更新bfqq->wr_cur_max_time为权重提升时间
  21. switch_back_to_interactive_wr(bfqq, bfqd);
  22. //bfqq->entity.prio_changed置1表示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_samples、ttime->ttime_total、ttime->ttime_mean
  4. bfq_update_io_thinktime(bfqd, bfqq);
  5. //更新bfqq的has_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. /*elapsed是bfqq上最近一次的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*elapsed,elapsed越大,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 IO,bfqq->seek_history的bit0就置1,并且左移一位。bfqq派发的seek IO越多,bfqq->seek_history的是1的bit位越多*/
  7. bfqq->seek_history <<= 1;
  8. //根据bfqq前后派发的两个IO的扇区地址和本次派发IO的字节数,判断出本次派发的IO是seek IO的话,则把bfqq->seek_history的bit0置1
  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_time为true
  8. arrived_in_time =  ktime_get_ns() <=
  9. bfqq->ttime.last_end_request +
  10. bfqd->bfq_slice_idle * 3;
  11.     ..........................
  12. //bfqq有bfq_bfqq_in_large_burst标记则in_burst为true,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_time为true,这样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属性,如果bfqq的IO请求派发完了但是配额没消耗光,则bfq_completed_request()中可能启动idle_slice_timer 定时器,并设置bfq_mark_bfqq_wait_request。等idle_slice_timer定时时间到,执行bfq_idle_slice_timer_body(),bfqq依然没来新的IO请求,于是就令bfqq因BFQQE_TOO_IDLE而过期失效。执行到这里,bfqq消耗的配额只有不到entity->budget的五分之一,于是清理bfqq的bfq_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_ttime和bfq_bfqq_IO_bound有什么区别呢?我觉得bfq_bfqq_has_short_ttime反应的是在bfqq已经激活状态下(bfqq的entity处于st->active tree,准确说正在派发bfqq上的IO请求),进程快速向bfqq的队列插入IO请求的特性。

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

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

  1. Linux IO调度器相关算法介绍

    IO调度器(IO Scheduler)是操作系统用来决定块设备上IO操作提交顺序的方法.存在的目的有两个,一是提高IO吞吐量,二是降低IO响应时间.然而IO吞吐量和IO响应时间往往是矛盾的,为了尽量平 ...

  2. 一文深入了解Linux IO 调度器

    [推荐阅读] 浅谈linux 内核网络 sk_buff 之克隆与复制 深入linux内核架构--进程&线程 了解Docker 依赖的linux内核技术 每个块设备或者块设备的分区,都对应有自身 ...

  3. Linux的IO调度器-CFQ

    最近由于一些控制IO带宽的需求,开始研究CFQ以及对应的IO cgroup,今天baidu了一下,竟然发现没有多少中文的介绍,所以准备写一个系列,介绍一下这个调度器,粗粗想了一下,大概可以灌四篇水,包 ...

  4. 木其工作室(专业程序代写服务)[转]学写块设备驱动(三)----踢开IO调度器,自己处理bio(下)...

    优质代写程序 代写Assignment程序 定制软件 设计程序 专业代写IT 大学生程序代写 踢开IO调度器很容易,即不使用__make_request 这个系统指定的强力函数,如何不使用?其实我们从 ...

  5. 挑战360无死角讲解Linux内核 进程管理,调度器的5种实现丨C++后端开发丨C/C++Linux服务器开发丨内核开发丨网络编程

    挑战360无死角讲解 进程管理,调度器的5种实现 1. 8500行 CFS是什么 2. RT调度器使用场景 3. IDLE/Dealine调度器 视频讲解如下,点击观看: 挑战360无死角讲解Linu ...

  6. [IO系统]18 IO调度器 - CFQ

    CFQ(CompletelyFair Queuing)算法,顾名思义,绝对公平算法. 1.1   原理 CFQ试图为竞争块设备使用权的所有进程分配一个请求队列和一个时间片,在调度器分配给进程的时间片内 ...

  7. [Linux][内核学习笔记]--CFS调度器

    文章目录 1. 进程的状态转换 2. 内核调度器的发展 3. 调度策略 4. 与调度相关的系统调用 5. 优先级 6. CFS调度器的实现 6.1 相关结构体 6.1.1 sched_entity 结 ...

  8. Linux内核必懂知识—调度器分析及完全公平调度器CFS

    调度器分析 调度器 内核中安排进程执行的模块,用以切换进程状态. 做两件事:选择某些就绪进程来执行:打断某些执行的进程让其变为就绪状态. 分配CPU时间的基本依据:进程优先级. 上下文切换(conte ...

  9. BLOCK层代码分析(9)IO下发之IO下发

    看着题目是不是很奇怪,想不出好的名字,就这样将就吧. 前面bio bounce过程,bio的切分和合并,request的获取是为IO请求下发做准备工作.当这些准备工作完成后,才进入到真正的IO下发过程 ...

  10. BLOCK层代码分析(10)IO下发之IO下发函数总结

    BLOCK层IO下发涉及直接下发,调度器,没有设置调度类型以及plug/unplug等,因此下发函数纷繁复杂,这里做介绍几个主要的函数. 前面介绍了函数blk_mq_try_issue_directl ...

最新文章

  1. 从“人肉扩缩容”到云原生容量,90 后程序员的进化
  2. symfony开发步骤简述
  3. 【深度学习】60题PyTorch简易入门指南,做技术的弄潮儿
  4. CaptCha的现状与未来
  5. (转)你真的会写单例模式吗——Java实现
  6. HDU2087 剪花布条【KMP】
  7. 网页如何截屏长图:f12 Ctrl+Shift+p cap
  8. maven 本地仓库的配置
  9. win10关闭触摸板和键盘
  10. 为什么计算机专业英语很重要,英语对计算机专业的重要性及如何提高英语水平...
  11. windows下cfree5中%d输出浮点数的问题
  12. vue 判断字符串是否是英文_Vuejs Element input组件区分中英文 统计长度
  13. FLAGS 作用及用法
  14. VIVADO学习笔记之--DONT_TOUCH
  15. linux系统外接硬盘_linux下,如何挂载一块硬盘?
  16. cmd、conhost退居二线,Win 11将设置默认终端
  17. 赵匡胤为何不把皇位传给儿子却传弟弟赵光义
  18. php解决微信防盗链,PHP如何解决微信文章图片防盗链
  19. Android--(三星手机)webview嵌套H5,点击H5按钮跳转手机拍照,横屏拍照导致失败或崩溃问题
  20. 【liteOS】小白进阶之移植 LiteOS 到 STM32

热门文章

  1. cenos7开启SMB文件共享,小米摄像头无法搜索到的原因
  2. cocos 微信登陆失败(笔记)
  3. 如何正确nandflash的块地址和页地址
  4. 下载python开发环境
  5. 17AHU排位赛3 C题 (LCS)
  6. 思考篇|姜子牙观影后感
  7. 天价电费成5G建设拦路虎,多省出台政策给运营商减负
  8. tf.expand_dims函数用法详解(搭配代码理解)
  9. 该如何提高个人影响力
  10. 名人名言摘选-李嘉诚