刚提交了一篇《内核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()函数多处都有调用,比如:
- static bool bfq_bfqq_must_idle(struct bfq_queue *bfqq)
- {
- return RB_EMPTY_ROOT(&bfqq->sort_list) && bfq_better_to_idle(bfqq);
- }
- static void bfq_completed_request(struct bfq_queue *bfqq, struct bfq_data *bfqd)
- {
- .........
- if (bfqd->in_service_queue == bfqq) {
- //bfqq上没有IO请求但是可能很快就有新的IO请求来,bfqq还不能过期失效,而是启动 idle timer定时器
- if (bfq_bfqq_must_idle(bfqq)) {
- if (bfqq->dispatched == 0)
- bfq_arm_slice_timer(bfqd);
- }
- }
- .........
- }
这个场景是说:bfqq上没有要派发的IO请求了,但有较大概率bfqq绑定的进程很快还有新的IO请求要来,故bfqq还不能立即过期失效,而是进入idle状态,启动idle timer定时器等待可能马上来的新的IO请求。怎么判定bfqq绑定的进程可以进入idle状态而等待新的IO请求来呢?就是靠bfq_better_to_idle()函数返回true,看下它的源码:
- static bool bfq_better_to_idle(struct bfq_queue *bfqq)
- {
- struct bfq_data *bfqd = bfqq->bfqd;
- bool idling_boosts_thr_with_no_issue, idling_needed_for_service_guar;
- ...............
- //异步bfqq或者idle调度算法的bfqq直接返回false
- if (bfqd->bfq_slice_idle == 0 || !bfq_bfqq_sync(bfqq) ||
- bfq_class_idle(bfqq))
- return false;
- //bfqd没有一个bfqq的权重提升了并且当前的bfqq绑定的进程向bfqq插入IO请求很快,则idling_boosts_thr_with_no_issue是true
- idling_boosts_thr_with_no_issue =
- idling_boosts_thr_without_issues(bfqd, bfqq);
- //当前的bfqq权重提升了并且正在磁盘驱动层传输的IO请求比较多等等则返回true
- idling_needed_for_service_guar =
- idling_needed_for_service_guarantees(bfqd, bfqq);
- return idling_boosts_thr_with_no_issue ||
- idling_needed_for_service_guar;
- }
可以发现主要是调用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()两个函数。
- //bfqd没有一个bfqq的权重提升了并且当前的bfqq绑定的进程有频繁向bfqq的队列插入IO请求的特性,该函数返回true
- static bool idling_boosts_thr_without_issues(struct bfq_data *bfqd,
- struct bfq_queue *bfqq)
- {
- //用的是SATA盘并且bfq总的已派发但还没完成的IO请求数很少,则rot_without_queueing为true
- bool rot_without_queueing =
- !blk_queue_nonrot(bfqd->queue) && !bfqd->hw_tag,
- bfqq_sequential_and_IO_bound,
- idling_boosts_thr;
- /* No point in idling for bfqq if it won't get requests any longer */
- if (unlikely(!bfqq_process_refs(bfqq)))
- return false;
- //就是说,bfqq绑定的进程需要大量连续快速传输IO请求
- bfqq_sequential_and_IO_bound = !BFQQ_SEEKY(bfqq) &&
- bfq_bfqq_IO_bound(bfqq) && bfq_bfqq_has_short_ttime(bfqq);
- /*idling_boosts_thr 为true,有两种情况。1:用的是SATA盘并且bfq总的已派发但还没完成的IO请求数很少 2:bfqq绑定的进程需要大量连续快速传输IO请求,并且用的SATA盘(或者IO请求在磁盘驱动传输的比较慢)*/
- idling_boosts_thr = rot_without_queueing ||
- //是SATA盘 或者 bfq总的已派发但还没完成的IO请求数比较多,说明IO在磁盘驱动传输的很慢
- ((!blk_queue_nonrot(bfqd->queue) || !bfqd->hw_tag) &&
- //bfqq绑定的进程需要大量连续快速传输IO请求
- bfqq_sequential_and_IO_bound);
- //idling_boosts_thr为true,并且没有bfqq的权重提升了则返回true
- return idling_boosts_thr && bfqd->wr_busy_queues == 0;
- }
首先是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()函数,
- static bool idling_needed_for_service_guarantees(struct bfq_data *bfqd,
- struct bfq_queue *bfqq)
- {
- /* No point in idling for bfqq if it won't get requests any longer */
- if (unlikely(!bfqq_process_refs(bfqq)))
- return false;
- //bfqq权重提升了并且 权重提升的bfqq数量比在st->active tree的bfqq的数量少(或者bfq在磁盘驱动传输的IO请求个数很多)
- return (bfqq->wr_coeff > 1 &&
- (bfqd->wr_busy_queues < bfq_tot_busy_queues(bfqd) || bfqd->rq_in_driver >= bfqq->dispatched + 4)
- ) ||
- bfq_asymmetric_scenario(bfqd, bfqq);//????这个不知道啥用
- }
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()函数。
- static void bfq_add_request(struct request *rq)
- {
- //通过rq->elv.priv[1]得到保存的bfqq
- struct bfq_queue *bfqq = RQ_BFQQ(rq);
- struct bfq_data *bfqd = bfqq->bfqd;
- struct request *next_rq, *prev;
- unsigned int old_wr_coeff = bfqq->wr_coeff;
- bool interactive = false;
- ...........
- //bfq_add_request()中把IO请求添加到bfqq->sort_list链表
- elv_rb_add(&bfqq->sort_list, rq);
- ...........
- //bfqq是新创建的或者bfqq在st->idle tree该if才成立
- if (!bfq_bfqq_busy(bfqq))//激活bfqq,把bfqq添加到st->active tree
- bfq_bfqq_handle_idle_busy_switch(bfqd, bfqq, old_wr_coeff,
- rq, &interactive);
- else {
- if (bfqd->low_latency && old_wr_coeff == 1 && !rq_is_sync(rq) &&
- time_is_before_jiffies(
- bfqq->last_wr_start_finish +
- bfqd->bfq_wr_min_inter_arr_async)) {//测试这里基本没成立过
- //更新bfqq->wr_coeff
- bfqq->wr_coeff = bfqd->bfq_wr_coeff;
- bfqq->wr_cur_max_time = bfq_wr_duration(bfqd);
- bfqd->wr_busy_queues++;
- bfqq->entity.prio_changed = 1;
- }
- //bfqq->next_rq发生了变化,执行bfq_updated_next_req()根据新的bfqq->next_rq消耗的配额和bfqq->max_budget更新bfqq权重
- if (prev != bfqq->next_rq)
- bfq_updated_next_req(bfqd, bfqq);
- }
- /*这个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才不会成立。*/
- if (bfqd->low_latency &&
- (old_wr_coeff == 1 || bfqq->wr_coeff == 1 || interactive))
- bfqq->last_wr_start_finish = jiffies;
- }
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()函数。
- static void bfq_bfqq_handle_idle_busy_switch(struct bfq_data *bfqd,
- struct bfq_queue *bfqq,
- int old_wr_coeff,
- struct request *rq,
- bool *interactive)
- {
- bool soft_rt, in_burst, wr_or_deserves_wr,
- bfqq_wants_to_preempt,
- //bfqq处于st->idle tree已经很长时间
- idle_for_long_time = bfq_bfqq_idle_for_long_time(bfqd, bfqq),
- //bfqq在传输完最后一个IO请求后的bfqd->bfq_slice_idle * 3时间内又来新的IO请求则arrived_in_time为true
- arrived_in_time = ktime_get_ns() <=
- bfqq->ttime.last_end_request +
- bfqd->bfq_slice_idle * 3;
- //bfqq有bfq_bfqq_in_large_burst标记则in_burst为true,in_burst表示bfqq绑定的进程有一段时间内大量派发IO请求的特性
- in_burst = bfq_bfqq_in_large_burst(bfqq);
- //soft_rt为true表示bfqq绑定的进程是实时性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;
- /*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倍*/
- *interactive = !in_burst && idle_for_long_time;
- wr_or_deserves_wr = bfqd->low_latency &&
- (bfqq->wr_coeff > 1 ||
- (bfq_bfqq_sync(bfqq) && bfqq->bic && (*interactive || soft_rt)));
- /*如果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请求*/
- bfqq_wants_to_preempt =
- bfq_bfqq_update_budg_for_activation(bfqd, bfqq,
- arrived_in_time);
- /*如果bfqq不是新创建的(就是说是处于st->ilde tree),同时bfqq空闲了idle_for_long_time很长时间,并且在bfqq最后一个IO请求传输完成的时间点bfqq->budget_timeout后,过了10s+ bfqq才来了新的IO请求而激活它,于是清理bfqq_in_large_burst标记。*/
- if (likely(!bfq_bfqq_just_created(bfqq)) &&//bfqq不是新创建的(就是说是处于st->ilde tree)
- idle_for_long_time &&//bfqq空闲很长时间
- //bfqq->budget_timeout + 10s < jiffies,就是说 bfqq->budget_timeout后已经过了10s+
- time_is_before_jiffies(bfqq->budget_timeout + msecs_to_jiffies(10000))) {
- //从bfqq->burst_list_node链表剔除
- hlist_del_init(&bfqq->burst_list_node);
- bfq_clear_bfqq_in_large_burst(bfqq);
- }
- bfq_clear_bfqq_just_created(bfqq);
- //如果bfqq被清理了bfq_bfqq_IO_bound标记
- if (!bfq_bfqq_IO_bound(bfqq)) {
- /*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标记*/
- if (arrived_in_time) {
- bfqq->requests_within_timer++;
- //bfqd->bfq_requests_within_timer默认120
- if (bfqq->requests_within_timer >= bfqd->bfq_requests_within_timer)
- bfq_mark_bfqq_IO_bound(bfqq);
- } else
- bfqq->requests_within_timer = 0;
- }
- if (bfqd->low_latency) {
- if (unlikely(time_is_after_jiffies(bfqq->split_time)))
- /* wraparound */
- bfqq->split_time =
- jiffies - bfqd->bfq_wr_min_idle_time - 1;
- //一般情况bfqq->split_time负无穷大,这个if大部分情况都成立
- if (time_is_before_jiffies(bfqq->split_time +
- bfqd->bfq_wr_min_idle_time)) {//这里成立
- //根据进程是IO属性(burst IO、交互式IO、实时性IO)调整bfqq->wr_coeff
- bfq_update_bfqq_wr_on_rq_arrival(bfqd, bfqq,
- old_wr_coeff,
- wr_or_deserves_wr,
- *interactive,
- in_burst,
- soft_rt);
- //如果bfqq->wr_coeff变化了,于是把bfqq->entity.prio_changed置1
- if (old_wr_coeff != bfqq->wr_coeff)
- bfqq->entity.prio_changed = 1;
- }
- }
- bfqq->last_idle_bklogged = jiffies;
- bfqq->service_from_backlogged = 0;
- bfq_clear_bfqq_softrt_update(bfqq);
- //把bfqq插入到st->active tree,根据bfqq->wr_coeff真正增大bfqq的entity权重,标记bfqq busy
- bfq_add_bfqq_busy(bfqd, bfqq);
- /*如果bfqq达到抢占bfqd->in_service_queue的条件,则令bfqd->in_service_queue因BFQQE_PREEMPTED而过期失效。但是并不能保证立即被调度bfqq使用,bfqq只是前边执行bfq_add_bfqq_busy()加入了st->active tree而已!*/
- if (bfqd->in_service_queue &&
- ((bfqq_wants_to_preempt &&//bfqq_wants_to_preempt抢占条件是1
- bfqq->wr_coeff >= bfqd->in_service_queue->wr_coeff) ||
- bfq_bfqq_higher_class_or_weight(bfqq, bfqd->in_service_queue)) &&
- next_queue_may_preempt(bfqd))
- //因抢占导致的bfqq失效
- bfq_bfqq_expire(bfqd, bfqd->in_service_queue,false, BFQQE_PREEMPTED);
- }
- //bfqq派发的IO请求数全传输完成,并且bfqq处于st->idle tree已经很长时间则返回true
- static bool bfq_bfqq_idle_for_long_time(struct bfq_data *bfqd,
- struct bfq_queue *bfqq)
- {
- /*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很长时间了。*/
- return bfqq->dispatched == 0 &&
- time_is_before_jiffies(//bfqq->budget_timeout+ bfqd->bfq_wr_min_idle_time < jiffies返回true
- bfqq->budget_timeout +
- bfqd->bfq_wr_min_idle_time);
- }
首先再说明一下,执行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()函数源码
- static void bfq_update_bfqq_wr_on_rq_arrival(struct bfq_data *bfqd,
- struct bfq_queue *bfqq,
- unsigned int old_wr_coeff,//bfqq老的的wr_coeff值,该函数里会更新它
- bool wr_or_deserves_wr,//wr_or_deserves_wr为true表示bfqq的bfqq->wr_coeff大于1,或者bfqq是交互式或者实时同步IO
- bool interactive,//interactive为true表示进程是交互式IO
- bool in_burst,//in_burst为true表示进程是普通的大量传输IO
- bool soft_rt)//soft_rt为true表示进程是实时性IO
- {
- //old_wr_coeff == 1 : bfqq老的wr_coeff是1,没有权重提升
- //bfqq正在提升权重,或者bfqq是同步IO并且想要提升权重(因为bfqq是交互式IO或者实时性IO),则wr_or_deserves_wr是1
- if (old_wr_coeff == 1 && wr_or_deserves_wr) {
- /* start a weight-raising period */
- //走这个分支说明bfqq是交互式IO
- if (interactive) {//测试这里成立
- bfqq->service_from_wr = 0;
- //权重提升系数bfqq->wr_coeff更新为30,之后__bfq_entity_update_weight_prio()中根据bfqq->wr_coeff增加bfqq的权重
- bfqq->wr_coeff = bfqd->bfq_wr_coeff;
- //更新bfqq的权重提升时间bfqq->wr_cur_max_time为bfq_wr_duration()
- bfqq->wr_cur_max_time = bfq_wr_duration(bfqd);
- } else {
- //走这个分支说明bfqq是实时IO
- //bfqq->wr_start_at_switch_to_srt赋值负无穷大
- bfqq->wr_start_at_switch_to_srt = bfq_smallest_from_now();
- //bfqq->wr_coeff更新 30*BFQ_SOFTRT_WEIGHT_FACTOR,显然实时IO的bfqq权重提升系数更大
- bfqq->wr_coeff = bfqd->bfq_wr_coeff *
- BFQ_SOFTRT_WEIGHT_FACTOR;
- //更新bfqq->wr_cur_max_time为实时性IO最大的权重提升时间bfqd->bfq_wr_rt_max_time
- bfqq->wr_cur_max_time =
- bfqd->bfq_wr_rt_max_time;
- }
- bfqq->entity.budget = min_t(unsigned long,
- bfqq->entity.budget,
- 2 * bfq_min_budget(bfqd));
- }
- //走这个分支说明bfqq老的权重提升系数bfqq->wr_coeff大于1
- else if (old_wr_coeff > 1)
- {
- //走这个分支说明bfqq是交互式IO,再次更新bfqq->wr_coeff和bfqq->wr_cur_max_time
- if (interactive) {
- bfqq->wr_coeff = bfqd->bfq_wr_coeff;
- bfqq->wr_cur_max_time = bfq_wr_duration(bfqd);
- } else if (in_burst)
- //走这个分支说明bfqq是普通的大量传输IO,则还原权重提升系数bfqq->wr_coeff为1
- bfqq->wr_coeff = 1;
- else if (soft_rt) {//走这个分支说明bfqq是实时性IO
- //如果bfqq->wr_cur_max_time不是实时性IO的最大的权重提升时间bfqd->bfq_wr_rt_max_time
- if (bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time)
- {
- //bfqq->wr_start_at_switch_to_srt更新为bfqq上次权重提升时间
- bfqq->wr_start_at_switch_to_srt = bfqq->last_wr_start_finish;
- //bfqq->wr_cur_max_time更新为实时性IO最大的权重提升时间
- bfqq->wr_cur_max_time = bfqd->bfq_wr_rt_max_time;
- //bfqq->wr_coeff更新 30*BFQ_SOFTRT_WEIGHT_FACTOR
- bfqq->wr_coeff = bfqd->bfq_wr_coeff * BFQ_SOFTRT_WEIGHT_FACTOR;
- }
- /*显然,如果进程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*/
- bfqq->last_wr_start_finish = jiffies;
- }
- }
- }
注释已经写的比较清楚,这里再做个整体总结:
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()函数,源码不复杂,如下:
- //主要是根据bfqq->wr_coeff计算bfqq新的权重
- struct bfq_service_tree * __bfq_entity_update_weight_prio(struct bfq_service_tree *old_st,//entity所属调度器st
- struct bfq_entity *entity,
- bool update_class_too)
- {
- ...................
- //老的调度器st->wsum减去entity->weight
- old_st->wsum -= entity->weight;
- if (entity->new_weight != entity->orig_weight)
- {
- if (entity->new_weight < BFQ_MIN_WEIGHT ||entity->new_weight > BFQ_MAX_WEIGHT) {
- //更新entity->new_weight
- if (entity->new_weight < BFQ_MIN_WEIGHT)
- entity->new_weight = BFQ_MIN_WEIGHT;
- else
- entity->new_weight = BFQ_MAX_WEIGHT;
- }
- //更新entity->orig_weight
- entity->orig_weight = entity->new_weight;
- //更新bfqq->ioprio
- if (bfqq)
- bfqq->ioprio = bfq_weight_to_ioprio(entity->orig_weight);
- .................
- new_st = bfq_entity_service_tree(entity);
- prev_weight = entity->weight;
- //根据bfqq->wr_coeff计算bfqq新的权重,很明显bfqq->wr_coeff越大计算出的权重越大
- new_weight = entity->orig_weight * (bfqq ? bfqq->wr_coeff : 1);
- ...............
- ///把新的权重更新到bfqq的entity->weight
- entity->weight = new_weight;
- ..........
- //调度器的st->wsum累加entity新的权重entity->weight
- new_st->wsum += entity->weight;
- }
- //测试new_st 和 old_st是同一个
- return new_st;
- }
2.3 结束提升进程bfqq的权重
进程bfqq的权重不是一直提升的,总有结束的时刻,什么时间会结束呢?在派发IO请求的过程bfq_dispatch_rq_from_bfqq->bfq_update_wr_data(),有概率结束进程bfqq的权重,下边看下bfq_update_wr_data()源码:
- //如果bfqq的权重提升时间用完了 或者 bfqq因权重提升消耗的配额达到了限制,则结束bfqq权重提升,bfqq->wr_coeff恢复为1等
- static void bfq_update_wr_data(struct bfq_data *bfqd, struct bfq_queue *bfqq)
- {
- struct bfq_entity *entity = &bfqq->entity;
- if (bfqq->wr_coeff > 1) { /* queue is being weight-raised */
- //bfqq拥有bfq_bfqq_in_large_burst标记的话,就不能再权重提升了,要结束权重提升
- if (bfq_bfqq_in_large_burst(bfqq))
- //bfqq结束权重提升,bfqq->wr_coeff 和 bfqq->last_wr_start_finish恢复到权重提升前的状态等等
- bfq_bfqq_end_wr(bfqq);
- /*bfqq->last_wr_start_finish是bfqq权重更新开始时间,bfqq->wr_cur_max_time是bfqq权重更新时间,该if成立说明bfqq的权重更新时间用完了,则可能就需要令bfqq->wr_coeff 和 bfqq->last_wr_start_finish恢复到权重提升前的状态等等*/
- else if (time_is_before_jiffies(bfqq->last_wr_start_finish +
- bfqq->wr_cur_max_time))
- {
- /*这个if是说,如果是交互式IO的bfqq的提升权重时间过了bfqq->wr_cur_max_time毫秒,或者bfqq由交互式IO特性切换到实时性IO特性而进一步提升了权重,又过了bfq_wr_duration(bfqd)毫秒,则执行bfq_bfqq_end_wr()令bfqq结束权重提升*/
- 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)))
- //bfqq结束权重提升,bfqq->wr_coeff 和 bfqq->last_wr_start_finish恢复到权重提升前的状态等等
- bfq_bfqq_end_wr(bfqq);
- else {
- //bfqq回到权重提升状态,增大bfqq->wr_coeff到30,更新bfqq->wr_cur_max_time为权重提升时间
- switch_back_to_interactive_wr(bfqq, bfqd);
- //bfqq->entity.prio_changed置1表示bfqq->wr_coeff更新了
- bfqq->entity.prio_changed = 1;
- }
- }
- /*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进程权重提升是有配额限制的*/
- if (bfqq->wr_coeff > 1 &&
- bfqq->wr_cur_max_time != bfqd->bfq_wr_rt_max_time &&
- bfqq->service_from_wr > max_service_from_wr) {
- //bfqq结束权重提升,bfqq->wr_coeff 和 bfqq->last_wr_start_finish恢复到权重提升前的状态等等
- bfq_bfqq_end_wr(bfqq);
- }
- }
- if ((entity->weight > entity->orig_weight) != (bfqq->wr_coeff > 1))
- //这里是重点,主要是根据最新的bfqq->wr_coeff计算bfqq新的权重
- __bfq_entity_update_weight_prio(bfq_entity_service_tree(entity),
- entity, false);
- }
注释写的比较清楚,有几个重点再说下:
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几个重要的参数,源码如下:
- static bool __bfq_insert_request(struct bfq_data *bfqd, struct request *rq)
- {
- //更新ttime->ttime_samples、ttime->ttime_total、ttime->ttime_mean
- bfq_update_io_thinktime(bfqd, bfqq);
- //更新bfqq的has_short_ttime状态
- bfq_update_has_short_ttime(bfqd, bfqq, RQ_BIC(rq));
- //这里边更新bfqq->seek_history
- bfq_update_io_seektime(bfqd, bfqq, rq);
- ..........
- //把IO请求添加到bfqq->sort_list链表,并选择合适的IO请求赋于bfqq->next_rq。接着可能激活bfqq,把bfqq添加到st->active tree
- bfq_add_request(rq);
- }
这里先看下bfq_update_io_thinktime()里对thinktime的更新:
- //__bfq_insert_request()->bfq_update_io_thinktime()
- static void bfq_update_io_thinktime(struct bfq_data *bfqd,
- struct bfq_queue *bfqq)
- {
- struct bfq_ttime *ttime = &bfqq->ttime;
- /*elapsed是bfqq上最近一次的IO请求传输完成到添加本次IO请求到bfqq的时间,这段时间bfqq是空闲状态,这就是thinktime吧。elapsed应该可以理解成bfqq每传输的两个IO请求之间的空闲时间,越大说明bfqq绑定的进程向bfqq插入IO请求越慢*/
- u64 elapsed = ktime_get_ns() - bfqq->ttime.last_end_request;
- //取最小时间
- elapsed = min_t(u64, elapsed, 2ULL * bfqd->bfq_slice_idle);
- //ttime->ttime_samples应该可以这样理解,每次执行到该函数令ttime->ttime_samples增加8
- ttime->ttime_samples = (7*bfqq->ttime.ttime_samples + 256) / 8;
- //ttime->ttime_total应该可以理解成每次增加8*elapsed,elapsed越大,ttime->ttime_total越大
- ttime->ttime_total = div_u64(7*ttime->ttime_total + 256*elapsed, 8);
- //ttime->ttime_mean显然是ttime->ttime_total除以ttime->ttime_samples
- ttime->ttime_mean = div64_ul(ttime->ttime_total + 128,
- ttime->ttime_samples);
- }
什么是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()函数,如下:
- //__bfq_insert_request()->bfq_update_has_short_ttime()
- static void bfq_update_has_short_ttime(struct bfq_data *bfqd,
- struct bfq_queue *bfqq,
- struct bfq_io_cq *bic)
- {
- //has_short_ttime默认是true
- bool has_short_ttime = true, state_changed;
- ....................
- //这个if可能会成立吗?正常情况bfqq->split_time负无穷大,该if正常应该不会成立
- if (time_is_after_eq_jiffies(bfqq->split_time +
- bfqd->bfq_wr_min_idle_time))
- return;
- /*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。*/
- if (atomic_read(&bic->icq.ioc->active_ref) == 0 ||
- //bfqq->ttime.ttime_samples大于80
- (bfq_sample_valid(bfqq->ttime.ttime_samples) &&
- //bfqq->ttime.ttime_mean大于8ms
- bfqq->ttime.ttime_mean > bfqd->bfq_slice_idle))
- has_short_ttime = false;
- ....................
- state_changed = has_short_ttime != bfq_bfqq_has_short_ttime(bfqq);
- if (has_short_ttime)
- bfq_mark_bfqq_has_short_ttime(bfqq);
- else
- bfq_clear_bfqq_has_short_ttime(bfqq);
- ....................
- }
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属性有关,相关源码如下:
- //__bfq_insert_request()->bfq_update_io_seektime()
- static void
- bfq_update_io_seektime(struct bfq_data *bfqd, struct bfq_queue *bfqq,
- struct request *rq)
- {
- /*bfqq->seek_history左移一位,每派发一个seek IO,bfqq->seek_history的bit0就置1,并且左移一位。bfqq派发的seek IO越多,bfqq->seek_history的是1的bit位越多*/
- bfqq->seek_history <<= 1;
- //根据bfqq前后派发的两个IO的扇区地址和本次派发IO的字节数,判断出本次派发的IO是seek IO的话,则把bfqq->seek_history的bit0置1
- bfqq->seek_history |= BFQ_RQ_SEEKY(bfqd, bfqq->last_request_pos, rq);
- if (bfqq->wr_coeff > 1 &&
- bfqq->wr_cur_max_time == bfqd->bfq_wr_rt_max_time &&
- BFQQ_TOTALLY_SEEKY(bfqq))
- bfq_bfqq_end_wr(bfqq);
- }
判断seek io以及相关的宏定义在下边:
- //判断是否是seek io
- #define BFQ_RQ_SEEKY(bfqd, last_pos, rq) (get_sdist(last_pos, rq) > BFQQ_SEEK_THR && \
- (!blk_queue_nonrot(bfqd->queue) ||blk_rq_sectors(rq) < BFQQ_SECT_THR_NONROT))
- //计算bfqq->seek_history有多少个bit位是1
- #define BFQQ_SEEKY(bfqq) (hweight32(bfqq->seek_history) > 19)
- #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。
- static void bfq_bfqq_handle_idle_busy_switch(struct bfq_data *bfqd,
- struct bfq_queue *bfqq,
- int old_wr_coeff,
- struct request *rq,
- bool *interactive)
- {
- //bfqq在传输完最后一个IO请求后的bfqd->bfq_slice_idle * 3时间内又来新的IO请求则arrived_in_time为true
- arrived_in_time = ktime_get_ns() <=
- bfqq->ttime.last_end_request +
- bfqd->bfq_slice_idle * 3;
- ..........................
- //bfqq有bfq_bfqq_in_large_burst标记则in_burst为true,in_burst表示bfqq绑定的进程有一段时间内大量派发IO请求的特性
- in_burst = bfq_bfqq_in_large_burst(bfqq);
- ..........................
- /*如果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标记了,该清理掉*/
- if (likely(!bfq_bfqq_just_created(bfqq)) &&//bfqq不是新创建的(就是说是处于st->ilde tree)
- idle_for_long_time &&//bfqq空闲很长时间
- time_is_before_jiffies(//bfqq->budget_timeout + 10s < jiffies,就是说 bfqq->budget_timeout后已经过了10s+
- bfqq->budget_timeout +
- msecs_to_jiffies(10000))) {
- //从bfqq->burst_list_node链表剔除
- hlist_del_init(&bfqq->burst_list_node);
- //清理掉 bfq_bfqq_in_large_burst 标记
- bfq_clear_bfqq_in_large_burst(bfqq);
- }
- .........................
- //如果bfqq被清理了bfq_bfqq_IO_bound标记
- if (!bfq_bfqq_IO_bound(bfqq)) {
- /*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*/
- if (arrived_in_time) {
- bfqq->requests_within_timer++;
- //bfqd->bfq_requests_within_timer默认120
- if (bfqq->requests_within_timer >=bfqd->bfq_requests_within_timer)
- bfq_mark_bfqq_IO_bound(bfqq);
- } else
- bfqq->requests_within_timer = 0;
- }
- }
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标记。如下:
- void bfq_bfqq_expire(struct bfq_data *bfqd,
- struct bfq_queue *bfqq,
- bool compensate,
- enum bfqq_expiration reason)
- {
- //如果bfqq超时原因是BFQQE_TOO_IDLE,并且bfqq只消化了很小一部分配额则令clear bfq_clear_bfqq_IO_bound。
- /*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请求就过期失效,就清理掉该属性。*/
- if (reason == BFQQE_TOO_IDLE &&
- entity->service <= 2 * entity->budget / 10)
- bfq_clear_bfqq_IO_bound(bfqq);
- }
什么情况下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源码的一些理解,可能有理解不对的地方。水平有限,本文如有错误请指出。