本文主要是介绍内核block层IO调度器—bfq算法之3源码要点总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
在《内核block层IO调度器—bfq算法之2源码详解》文章的基础上,本文主要总结一个bfq算法的源码要点,bfq算法真的复杂!本文基于centos 8.3,内核版本4.18.0-240.el8,详细源码注释见 https://github.com/dongzhiyan-stack/linux-4.18.0-240.el8。
要想了解bfq调度算法的工作原理,我觉得主要还得看派发IO请求的__bfq_dispatch_request()函数。该函数涉及怎么选择IO调度器使用的bfqq和entity、派发bfqq上的IO请求、bfqq因配额不够或者没有要派发的IO请求而过期失效、bfqq的entity过期失效而从st->active红黑树剔除而移动到st->active红黑树或者计算新的entity->finish而重新加入st->active红黑树、分配一个新的bfqq后把它的entity加入st->active红黑树、激活一个在st->active红黑树的entity而把它加入到st->active红黑树。下边先把涉及的流程框图贴下:
下文围绕这个图较为深入的讲解一下bfq调度算法的工作原理。首先还是把涉及的核心源码简明扼要贴下:
1:bfq 重要函数源码总结
先从派发IO请求的__bfq_dispatch_request()函数开始。
1.1派发IO请求入口函数
- static struct request *__bfq_dispatch_request(struct blk_mq_hw_ctx *hctx)
- {
- //选择派发IO请求的bfqq
- bfqq = bfq_select_queue(bfqd);
- //从bfqq选择一个派发的IO请求req
- rq = bfq_dispatch_rq_from_bfqq(bfqd, bfqq);
- }
入口函数__bfq_dispatch_request(),主要调用bfq_select_queue()和bfq_dispatch_rq_from_bfqq()两个函数。
- static struct bfq_queue *bfq_select_queue(struct bfq_data *bfqd)
- {
- bfqq = bfqd->in_service_queue;
- if (!bfqq)
- goto new_queue;
- ........
- check_queue:
- next_rq = bfqq->next_rq;
- if (next_rq) {
- if (bfq_serv_to_charge(next_rq, bfqq) >bfq_bfqq_budget_left(bfqq)) {
- reason = BFQQE_BUDGET_EXHAUSTED;//bfqq 的配额消耗光了
- goto expire;//bfqq 的配额消耗光而令bfqq到期失效
- }else{
- goto keep_queue;//继续使用bfqd->in_service_queue这个bfqq,保持
- }
- }
- ...........
- reason = BFQQE_NO_MORE_REQUESTS;//bfqq没有req要传输了
- expire:
- //bfqq过期失效
- bfq_bfqq_expire(bfqd, bfqq, false, reason);
- new_queue:
- //选择一个新的bfqq赋于bfqd->in_service_queue,作为bfq当前使用的bfqq
- bfqq = bfq_set_in_service_queue(bfqd);
- if (bfqq) {
- goto check_queue;//goto check_queue 分支,派发新选中的bfqq的IO请求
- }
- keep_queue:
- return bfqq;
- }
bfq_select_queue()中先判断bfqq=bfqd->in_service_queue是否有正在使用的bfqq,即判断bfqd->in_service_queue是否是NULL。如果bfqd->in_service_queue是NULL,表示没有正在使用的bfqq,则goto new_queue分支执行bfq_set_in_service_queue()选择一个bfqq赋于bfqd->in_service_queue,然后goto check_queue分支。如果bfqd->in_service_queue非NULL,则从bfqq->next_rq取出该bfqq要派发的IO请求。如果bfqq->next_rq是NULL,表示该bfqq没有要派发的IO请求了,则reason = BFQQE_NO_MORE_REQUESTS,然后执行bfq_bfqq_expire()令bfqq因没有派发的IO请求而过期失效。如果bfqq->next_rq非NULL,则判断该bfqq是否有足够的配额派发bfqq->next_rq这个IO请求,有足够的配额的话则goto keep_queue分支直接返回bfqd->in_service_queue这个bfqq。否则goto expire分支执行bfq_bfqq_expire()令bfqq因配额不够而过期失效。bfq_select_queue()函数有点复杂,在本文开头的示意图也有重点标出。下边把bfq_set_in_service_queue ()和bfq_bfqq_expire()函数源码简单列下:
- static struct bfq_queue *bfq_set_in_service_queue(struct bfq_data *bfqd)
- {
- //从sd->next_in_service取出entity赋于sd->in_service_entity,作为本次要使用的entity。然后查找一个新的entity赋于sd->next_in_service,作为下一次使用的entity
- struct bfq_queue *bfqq = bfq_get_next_queue(bfqd);
- //本质是 bfqd->in_service_queue = bfqq
- __bfq_set_in_service_queue(bfqd, bfqq);
- return bfqq;
- }
下边看下bfq_bfqq_expire()函数相关源码
- void bfq_bfqq_expire(struct bfq_data *bfqd,struct bfq_queue *bfqq,bool compensate,enum bfqq_expiration reason)
- {
- ..............
- //计算更新bfqq->max_budget,如果bfqq上还有req(bfqq->next_rq)要派发,则计算根据next_rq传输的字节数计算bfqq->entity的配额,保证bfqq的配额还能传输完next_rq
- __bfq_bfqq_recalc_budget(bfqd, bfqq, reason);
- ..............
- //令bfqq过期失效
- if (__bfq_bfqq_expire(bfqd, bfqq, reason))
- ..............
- }
- static bool __bfq_bfqq_expire(struct bfq_data *bfqd, struct bfq_queue *bfqq,
- enum bfqq_expiration reason)
- {
- if (RB_EMPTY_ROOT(&bfqq->sort_list) &&//bfqq上没有派发的req
- !(reason == BFQQE_PREEMPTED &&
- idling_needed_for_service_guarantees(bfqd, bfqq)))
- { //bfqq上没有IO请求要派发了而过期失效,把bfqq的entity从st->active红黑树剔除而移入st->active红黑树
- bfq_del_bfqq_busy(bfqd, bfqq, true);
- }else{
- //bfqq配额消耗光了而过期失效,把bfqq的entity重新加入到st->active红黑树。因为bfqq上还有IO请求要派发
- bfq_requeue_bfqq(bfqd, bfqq, true);
- }
- //bfqd->in_service_queue = NULL 清空
- return __bfq_bfqd_reset_in_service(bfqd);
- }
- void bfq_del_bfqq_busy(struct bfq_data *bfqd, struct bfq_queue *bfqq,
- bool expiration)
- {
- //清理bfqq busy状态
- bfq_clear_bfqq_busy(bfqq);
- //从st->active 红黑树剔除bfqq的entity
- bfq_deactivate_bfqq(bfqd, bfqq, true, expiration);
- }
- void bfq_deactivate_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq,
- bool ins_into_idle_tree, bool expiration)
- {
- struct bfq_entity *entity = &bfqq->entity;
- bfq_deactivate_entity(entity, ins_into_idle_tree, expiration);
- }
- //bfqd->in_service_queue指向的正在使用的bfqq,配额消耗光但bfqq上还有要派发IO请求,于是把bfqq重新插入到st->active红黑树,并且选择一个新的bfqq作为bfqd->in_service_queue。
- void bfq_requeue_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq,
- bool expiration)
- {
- struct bfq_entity *entity = &bfqq->entity;
- bfq_activate_requeue_entity(entity, false,bfqq == bfqd->in_service_queue, expiration);
- }
- //把entity加入到st->active红黑树
- static void bfq_activate_requeue_entity(struct bfq_entity *entity,
- bool non_blocking_wait_rq,
- bool requeue, bool expiration)
- {
- //把entity加入到st->active红黑树
- __bfq_activate_requeue_entity(entity, sd, non_blocking_wait_rq);
- ///查找新的 next_in_service entity赋于sd->next_in_service
- if (!bfq_update_next_in_service(sd, entity, expiration) &&!requeue)
- break;
- }
可以发现,bfqq因没有要派发的IO请求而过期失效, bfq_bfqq_expire()函数的调用流程是bfq_bfqq_expire()->__bfq_bfqq_expire()->bfq_del_bfqq_busy()->bfq_deactivate_bfqq()->bfq_deactivate_entity()。bfqq因没有足够的配额派发IO请求而过期失效, bfq_bfqq_expire()函数的调用流程是bfq_bfqq_expire()->__bfq_bfqq_expire()->bfq_requeue_bfqq()->bfq_activate_requeue_entity()。
1.2 bfq_deactivate_entity()从st->active红黑树剔除entity
- //把entity从st->active红黑树剔除
- static void bfq_deactivate_entity(struct bfq_entity *entity,
- bool ins_into_idle_tree,
- bool expiration)
- {
- //把entity从st->active 红黑树剔除,计算新的entity->finish把entity按照entity->>finish加入到st->active红黑树
- if (!__bfq_deactivate_entity(entity, ins_into_idle_tree))
- ...........
- //如果要从st->active红黑树剔除的entity是sd->next_in_service 指向的entity
- if (sd->next_in_service == entity)
- ///查找新的 next_in_service entity赋于sd->next_in_service
- bfq_update_next_in_service(sd, NULL, expiration);
- }
bfq_deactivate_entity()中主要调用__bfq_deactivate_entity()和bfq_update_next_in_service()函数。__bfq_deactivate_entity()函数把entity从st->active红黑树剔除,bfq_update_next_in_service()函数选择下一次运行的entity赋于sd->next_in_service。先看下__bfq_deactivate_entity()源码,bfq_update_next_in_service()放最后讲解。
- bool __bfq_deactivate_entity(struct bfq_entity *entity, bool ins_into_idle_tree)
- {
- struct bfq_sched_data *sd = entity->sched_data;
- struct bfq_service_tree *st;
- bool is_in_service;
- //找到entiry所属st
- st = bfq_entity_service_tree(entity);
- //entity是sd正在使用的entity则is_in_service为true
- is_in_service = entity == sd->in_service_entity;
- //service是entity已经消耗的配额,除以weight得到entity已经消耗的虚拟运行时间,再加上entity->start得到entity->finish
- bfq_calc_finish(entity, entity->service);
- if (is_in_service)//把sd->in_service_entity指向的entity从红黑树剔除时,要把sd->in_service_entity清NULL
- sd->in_service_entity = NULL;
- else
- entity->service = 0;
- if (entity->tree == &st->active)//如果entity处于st->active树,从st->acitve tree剔除entity
- bfq_active_extract(st, entity);
- else if (!is_in_service && entity->tree == &st->idle)//如果entity处于st->idle树,并且entity不是st正在使用的entity
- bfq_idle_extract(st, entity);
- //ins_into_idle_tree为false则不会把entity再插入到st->idle,而是释放掉entity。或者st->vtime>=entity->finish也是释放掉entity
- if (!ins_into_idle_tree || !bfq_gt(entity->finish, st->vtime))
- bfq_forget_entity(st, entity, is_in_service);//entity不再被用到,释放bfqq,把entity剔除掉
- else
- bfq_idle_insert(st, entity);//把entity再插入st->idle树,并可能更新st->first_idle或st->last_idle
- return true;
- }
__bfq_deactivate_entity()函数主要作用是:把entity从st->active或st->idle红黑树踢掉,ins_into_idle_tree为true则把entity再插入st->idle树,否则直接释放掉entity及bfqq。
1.3 bfq_activate_requeue_entity()把entity加入st->active红黑树
- //把entity加入到st->active红黑树
- static void bfq_activate_requeue_entity(struct bfq_entity *entity,
- bool non_blocking_wait_rq,
- bool requeue, bool expiration)
- {
- //把entity加入到st->active红黑树
- __bfq_activate_requeue_entity(entity, sd, non_blocking_wait_rq);
- ///查找新的 next_in_service entity赋于sd->next_in_service
- if (!bfq_update_next_in_service(sd, entity, expiration) &&!requeue)
- break;
- }
__bfq_activate_requeue_entity()函数主要调用__bfq_activate_requeue_entity()把entity加入st->active红黑树,然后也调用bfq_update_next_in_service()选择下一次运行的entity赋于sd->next_in_service。先看下__bfq_activate_requeue_entity()函数源码:
- static void __bfq_activate_requeue_entity(struct bfq_entity *entity,
- struct bfq_sched_data *sd,
- bool non_blocking_wait_rq)
- {
- //如果entity是调度器正在使用的entiry或entity处于st->active红黑树
- if (sd->in_service_entity == entity || entity->tree == &st->active)
- //根据entity->service计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树
- __bfq_requeue_entity(entity);
- else//否则到这里说明entity 是新创建的
- //计算entity->start,按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,把entity按照entity->finish插入st->active红黑树
- __bfq_activate_entity(entity, non_blocking_wait_rq);
- }
该函数两个分支:如果entity是调度器正在使用的entiry或entity处于st->active树,然后根据entity->service计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树。否则,entity及其bfqq是新创建的,计算entity->start,直接把entity插入st->active红黑树。这两个函数源码是:
- //bfqq的entity之前就在st->active红黑树,这里根据entity->service计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树
- static void __bfq_requeue_entity(struct bfq_entity *entity)
- {
- if (entity == sd->in_service_entity) {//entity是IO调度器正在使用的entity
- //根据entity->service/entity->weight计算entity已经消耗的虚拟运行时间,entity->start加上这段虚拟运行时间就是entity->finish
- bfq_calc_finish(entity, entity->service);
- //用entity->finish更新entity->start,重置了
- entity->start = entity->finish;
- if (entity->tree)//如果entity在st->active树,则从st->acitve tree剔除entity,下边再把entity加入st->active树
- bfq_active_extract(st, entity);
- }else{
- bfq_active_extract(st, entity);
- }
- //按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树
- bfq_update_fin_time_enqueue(entity, st, false);
- }
- //bfqq的entity是新创建的或者bfqq的entity之前在st->active红黑树,根据entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树
- static void __bfq_activate_entity(struct bfq_entity *entity,
- bool non_blocking_wait_rq)
- {
- if (non_blocking_wait_rq && bfq_gt(st->vtime, entity->finish)) {
- backshifted = true;
- min_vstart = entity->finish;
- } else
- min_vstart = st->vtime;
- //entity现在处于st->active红黑树
- if (entity->tree == &st->idle) {
- //entity从st->active红黑树剔除掉,可能更新st->first_idle或st->last_idle
- bfq_idle_extract(st, entity);
- entity->start = bfq_gt(min_vstart, entity->finish) ?min_vstart : entity->finish;
- } else {//到这里说明entity之前不在st->idle或st->active红黑树,entity是新分配的也走这里
- entity->start = min_vstart;//更新entity->start
- //st->wsum调度类st的active和idle tree上所有entity的权重之和,累加每一个entity的entity->weight
- st->wsum += entity->weight;
- }
- //按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树
- bfq_update_fin_time_enqueue(entity, st, backshifted);
- }
最后都是调用bfq_update_fin_time_enqueue()把entity插入st->active红黑树,看下它的源码:
- //按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树
- static void bfq_update_fin_time_enqueue(struct bfq_entity *entity,
- struct bfq_service_tree *st,
- bool backshifted)
- {
- //这里简化成entity->finish=entity->start+entity->budget/entity->weight,用entity的配额得到entity->finish,注意是entity的配额
- bfq_calc_finish(entity, entity->budget);
- //把entity按照新的entity->finish插入st->active红黑树
- bfq_active_insert(st, entity);
- }
- //把entity按照新的entity->finish插入st->active红黑树
- static void bfq_active_insert(struct bfq_service_tree *st,
- struct bfq_entity *entity)
- {
- //把entity按照entity->finish插入st->active红黑树,entity->finish越小entity在红黑树越靠左
- bfq_insert(&st->active, entity);
- if (node->rb_left)
- node = node->rb_left;
- else if (node->rb_right)
- node = node->rb_right;
- /*从entity的node所在节点,一直在红黑树向上得到它的parent节点,用entity->start更新这些红黑树节点的entity->min_start,同时也更新这些node在红黑树的左右子节点对应的entity的min_start。这个更新操作一直到根节点才结束*/
- bfq_update_active_tree(node);
- }
1.4 bfq_set_in_service_queue()和bfq_get_next_queue()
- static struct bfq_queue *bfq_set_in_service_queue(struct bfq_data *bfqd)
- {
- //从sd->next_in_service取出entity赋于sd->in_service_entity,作为本次要使用的entity。然后查找一个新的entity赋于sd->next_in_service,作为下一次使用的entity
- struct bfq_queue *bfqq = bfq_get_next_queue(bfqd);
- //本质是 bfqd->in_service_queue = bfqq
- __bfq_set_in_service_queue(bfqd, bfqq);
- return bfqq;
- }
bfq_set_in_service_queue()主要调用bfq_get_next_queue()函数:把sd->next_in_service赋值给sd->in_service_entity,sd->in_service_entity正是bfq调度器使用的entity,并且返回entity对应的bfqq。__bfq_set_in_service_queue()函数主要是把bfqq赋值给bfqd->in_service_queue,bfqd->in_service_queue正是bfq正在使用的bfqq。看下bfq_get_next_queue()函数源码:
- //从sd->next_in_service取出entity赋于sd->in_service_entity,作为本次要使用的entity。然后查找一个新的entity赋于sd->next_in_service,作为下一次使用的entity
- struct bfq_queue *bfq_get_next_queue(struct bfq_data *bfqd)
- {
- for (; sd ; sd = entity->my_sched_data) {
- ///取出sd->next_in_service作为当前要使用的entity,赋值给sd->in_service_entity
- entity = sd->next_in_service;
- sd->in_service_entity = entity;
- //sd->in_service_entity这个entity作为bfq当前正在使用的entity,就要把entity从st->actvie tree剔除
- if (bfq_no_longer_next_in_service(entity))
- bfq_active_extract(bfq_entity_service_tree(entity),entity);
- }
- for_each_entity(entity) {
- struct bfq_sched_data *sd = entity->sched_data;
- ///查找新的 next_in_service entity赋于sd->next_in_service
- if (!bfq_update_next_in_service(sd, NULL, false))
- break;
- }
- }
按照前文所说,该函数是从sd->next_in_service取出entity赋于sd->in_service_entity,作为本次要使用的entity。然后执行bfq_update_next_in_service()查找一个新的entity赋于sd->next_in_service,作为下一次使用的entity。bfq_update_next_in_service()前文已经提到了多次,下一小节讲解。
1.5 bfq_update_next_in_service()选择一个新的sd->next_in_service
看下bfq_update_next_in_service()函数重点代码,主要是调用bfq_lookup_next_entity()。
- static bool bfq_update_next_in_service(struct bfq_sched_data *sd,
- struct bfq_entity *new_entity,//new_entity有时是NULL,有时不是
- bool expiration)//expiration为true说明是bfqq到期了,为false说明是其他情况,比如在bfq_update_next_in_service()
- {
- struct bfq_entity *next_in_service = sd->next_in_service;
- bool parent_sched_may_change = false;
- bool change_without_lookup = false;
- ………………
- //如果change_without_lookup为false则执行bfq_lookup_next_entity()选择下一个使用的bfqq的entity。可能找不到,则返回NULL
- if (!change_without_lookup)
- next_in_service = bfq_lookup_next_entity(sd, expiration);
- ........................
- //为sd->next_in_servic赋值下一次使用的bfqq的entity。如果前边没找到则sd->next_in_service被赋值NULL
- sd->next_in_service = next_in_service;
- return parent_sched_may_change;
- }
- static struct bfq_entity *bfq_lookup_next_entity(struct bfq_sched_data *sd,
- bool expiration)
- {
- //在sd调度器每个调度策略st里都搜索符合条件的 entity,都找不到返回NULL
- for (; class_idx < BFQ_IOPRIO_CLASSES; class_idx++) {
- entity = __bfq_lookup_next_entity(st + class_idx,sd->in_service_entity &&!expiration);
- if (entity)
- break;
- }
- return entity;
- }
- static struct bfq_entity *__bfq_lookup_next_entity(struct bfq_service_tree *st, bool in_service)
- {
- //从st->active红黑树红黑树最左边查找entity->start最小并且entity->start小于vtime的entity
- entity = bfq_first_active_entity(st, new_vtime);
- return entity;
- }
bfq_first_active_entity()函数主要是在st->active红黑树最左边查找一个entity->start最小并且entity->start小于st->vtime的entity,这就是要最终找到的合适的entity,接着就会赋值给sd->next_in_service。bfq_update_next_in_service()的调用主要有3处:
1:bfq_activate_bfqq() / bfq_requeue_bfqq() ->bfq_activate_requeue_entity()把entity加入st->active红黑树后,执行bfq_update_next_in_service()选择一个新的entity赋于sd->next_in_service,作为下次使用的entity。
2:bfq_del_bfqq_busy ()->bfq_deactivate_bfqq ()->bfq_deactivate_entity()从st->active红黑树剔除entity后,执行bfq_update_next_in_service()选择一个新的entity赋于sd->next_in_service,作为下次使用的entity。
:
3:派发IO请求过程执行到__bfq_dispatch_request ()->bfq_select_queue->bfq_set_in_service_queue()->bfq_get_next_queue()->bfq_update_next_in_service()函数,把sd->next_in_service赋于sd->in_service_entity后,执行bfq_update_next_in_service()选择一个新的entity赋于sd->next_in_service,作为下次使用的entity。
1.6 bfq_dispatch_rq_from_bfqq()派发IO请求的后半部分
最后看下bfq_dispatch_rq_from_bfqq()函数。
- static struct request *bfq_dispatch_rq_from_bfqq(struct bfq_data *bfqd,
- struct bfq_queue *bfqq)
- {
- struct request *rq = bfqq->next_rq;
- unsigned long service_to_charge;
- //计算并返回本次传输req需要配额,配额以req要传输字节数为基准,同步和异步IO计算方法不一样。
- service_to_charge = bfq_serv_to_charge(rq, bfqq);
- //根据本次传输req的配额served,增加entity配额entity->service和调度器虚拟运行时间st->vtime
- bfq_bfqq_served(bfqq, service_to_charge);
- //计算新的bfqd->peak_rate,并选择一个新的req作为bfqq->next_rq,计算更新新的entity->budget,把req从bfqq->sort_list链表剔除
- bfq_dispatch_remove(bfqd->queue, rq);
- if (!(bfq_tot_busy_queues(bfqd) > 1 && bfq_class_idle(bfqq)))
- goto return_rq;
- return_rq:
- return rq;//bfqq->next_rq作为本次派发的IO请求
- }
bfq_dispatch_rq_from_bfqq()函数作用:从bfqq->next_rq选择为要派发IO请求,根据派发IO请求的数据量计算消耗的配额,累加到bfqq的entity->service和bfq调度器消耗的虚拟运行时间st->vtime。最后看下bfq_bfqq_served()函数:
- //根据待派发req的配额served,增加entity配额entity->service和调度器虚拟运行时间st->vtime
- void bfq_bfqq_served(struct bfq_queue *bfqq, int served)//served是当前要派发req消耗的配额
- {
- …………
- bfqq->service_from_backlogged += served;
- for_each_entity(entity) {
- //取出entity指向的调度器实体st
- st = bfq_entity_service_tree(entity);
- //entity->service累加待派发req的配额served
- entity->service += served;
- //st->vtime累加待派发req的配额served除以st->wsum算出的虚拟运行时间
- st->vtime += bfq_delta(served, st->wsum);
- /*处理st->active红黑树的first_idle和last_idle,可能会把first_idle指向的entity从st->active红黑树剔除掉,释放掉它的bfqq和entity*/
- bfq_forget_idle(st);
- }
- }
2:sd->next_in_service、sd->in_service_entity、bfqd->in_service_queue的使用与更新
第一节多次提到sd->next_in_service、sd->in_service_entity、bfqd->in_service_queue这3个变量,这一节主要介绍这3个变量的工作过程。sd->in_service_entity是bfq调度器当前正在使用的entity,bfqd->in_service_queue是bfq调度器正在使用的entity的bfqq,sd->next_in_service是bfq调度器下一次使用的entity。
派发IO请求执行到__bfq_dispatch_request->bfq_select_queue()函数,首先判断bfqd->in_service_queue是否为NULL,即 bfq是否有正在使用的bfqq。如果bfq没有正在使用的bfqq(bfqd->in_service_queue是NULL),则要执行bfq_set_in_service_queue()把sd->next_in_service这个下一次使用的entity赋值给sd->in_service_entity。sd->in_service_entity正是bfq调度器正在使用的entity,bfqd->in_service_queue是该entity对应的bfqq。如果bfq_select_queue()函数中,bfqd->in_service_queue非NULL,即bfq有正在使用的bfqq,则从bfqq的IO队列取出要派发的IO请求(即bfqq->next_rq指向的IO请求)。
最后说下sd->next_in_service,sd->next_in_service的更新是在bfq_update_next_in_service()函数,主要有三处。bfq_deactivate_entity()函数中把entity从st->active红黑树剔除后,执行bfq_update_next_in_service();bfq_activate_requeue_entity()函数中把entity加入st->active红黑树后,执行bfq_update_next_in_service();派发IO请求__bfq_dispatch_request()->bfq_select_queue()函数中,bfqd->in_service_queue是NULL即bfq没有正在使用的bfqq,则执行bfq_set_in_service_queue()->bfq_get_next_queue(),把sd->next_in_service赋值给sd->in_service_entity,然后执行bfq_update_next_in_service()选择一个新的entity赋值给sd->next_in_service。
bfq_update_next_in_service()第1节介绍过,函数主要是从st->active红黑树最左边查找一个entity->start最小并且entity->start小于vtime的entity,这就是要最终找到的合适的entity,后边就会赋值给sd->next_in_service。
3:entity->start、entity->finish、entity->min_start的更新
3.1 entity->start的更新时机
entity->start是entity的起始虚拟运行时间,entity->finish是entity结束的虚拟运行时间。entity->start在什么时间更新呢?
1:在一个新的进程派发IO请求,分配创建新的bfqq后把bfqq的entity加入st->active红黑树函数流程:bfq_add_request()->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_activate_entity()函数中,走了if (entity->tree == &st->idle)的else分支,用调度器的虚拟运行时间st->vtime赋值给entity->start。
2:在派发IO请求时,因为bfqq的配额消耗光而令bfqq过期失效过程__bfq_dispatch_request()->bfq_select_queue()->bfq_bfqq_expire()->__bfq_bfqq_expire()->bfq_requeue_bfqq()->bfq_activate_requeue_entity()->__bfq_activate_requeue_entity-> __bfq_requeue_entity()。在该函数中,执行bfq_calc_finish(entity, entity->service)用entity已经消耗的配额更新entity->finish,即entity->finish = entity->start + bfq_delta(service, entity-> service),然后entity->start = entity->finish用entity->finish更新entity->start。
3:entity之前因为没有派发的IO请求过期失效而从st->active红黑树移动到st->active红黑树,然后又要派发派entity的bfqq上的IO请求,而把entity激活,函数流程是bfq_add_request()->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_activate_entity()函数中,走了if (entity->tree == &st->idle)分支,用entity->finish和st->vtime更新entity->start。
3.2 entity->finish的更新时机
首先,entity加入st->active红黑树或者st->active红黑树时,都是按照entity->finish由小到大从左到右而排列。就是说,entity->finish越小,entity在st->active红黑树或者st->active红黑树越靠左。entity->finish的更新是在bfq_calc_finish()函数,如下:
- static void bfq_calc_finish(struct bfq_entity *entity, unsigned long service)
- { //service是entity->budget或entity->service
- struct bfq_queue *bfqq = bfq_entity_to_bfqq(entity);
- entity->finish = entity->start + bfq_delta(service, entity->weight);
- }
函数形参service是entity已经消耗的配额或者entity的权重,这个计算可以简化成entity->finish = entity->start + service*N/entity->weight(N是个常数)。显然,entity->finish可以等同于entity->start+entity消耗的配额。
什么时机会调用到bfq_calc_finish()函数呢?
1:在派发IO请求时,因为bfqq的配额消耗光而令bfqq过期失效过程__bfq_dispatch_request()->bfq_select_queue()->bfq_bfqq_expire()->__bfq_bfqq_expire()->bfq_requeue_bfqq()->bfq_activate_requeue_entity()->__bfq_activate_requeue_entity-> __bfq_requeue_entity()。在该函数中,执行bfq_calc_finish(entity, entity->service)。用entity已经消耗的配额,乘以N再除以entity权重weight得到entity已经消耗的虚拟运行时间(N是个常数),再加上entity->start得到entity->finish。
2:在派发IO请求时,因为bfqq上没有要派发的IO请求而令bfqq过期失效过程__bfq_dispatch_request()->bfq_select_queue()->bfq_bfqq_expire()->__bfq_bfqq_expire()->bfq_del_bfqq_busy()->bfq_deactivate_bfqq()->bfq_deactivate_entity()->__bfq_deactivate_entity()。在__bfq_deactivate_entity()函数中,执行bfq_calc_finish(entity, entity->service),用 entity已经消耗的配额,乘以N再除以entity权重weight得到entity已经消耗的虚拟运行时间(N是个常数),再加上entity->start得到entity->finish。
3:bfq_update_fin_time_enqueue()函数中,执行bfq_calc_finish(entity, entity->budget)。用entity的权重,乘以N再除以entity权重weight得到entity已经消耗的虚拟运行时间(N是个常数),再加上entity->start得到entity->finish。然后执行bfq_active_insert(st, entity)把entity按照最新的entity->finish加入st->active红黑树。
需要说明一点,__bfq_requeue_entity()和__bfq_requeue_entity()都是执行bfq_calc_finish(entity, entity->service),用 entity已经消耗的配额,乘以N再除以entity权重weight得到entity已经消耗的虚拟运行时间(N是个常数),再加上entity->start得到entity->finish。而bfq_update_fin_time_enqueue()函数中,是用entity的权重,乘以N再除以entity权重weight得到entity已经消耗的虚拟运行时间(N是个常数),再加上entity->start得到entity->finish。这是个很明显的区别。
还要说明一点,bfq_update_fin_time_enqueue()函数的调用有两个时机,一个是执行__bfq_dispatch_request()->bfq_select_queue()->bfq_bfqq_expire()->__bfq_bfqq_expire()->bfq_requeue_bfqq()->bfq_activate_requeue_entity()->__bfq_activate_requeue_entity-> __bfq_requeue_entity()->bfq_update_fin_time_enqueue(),把entity重新插入st->active红黑树最后,执行bfq_update_fin_time_enqueue()。
另一个时机是把一个新创建的bfqq的entity加入st->active红黑树或者把一个处于st->active红黑树的entity激活加入到st->active红黑树,函数流程是bfq_add_request()->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_activate_entity()函数最后执行bfq_update_fin_time_enqueue()把entity插入st->active红黑树。
3.3 entity->min_start的更新时机
entity->min_start的更新在bfq_update_active_tree()函数,有两个时机:在entity从st->active 剔除最后执行的bfq_active_extract()函数和entity加入st->active红黑树最后执行的bfq_active_insert()函数。看下源码
- //把entity按照新的entity->finish插入st->active红黑树
- static void bfq_active_insert(struct bfq_service_tree *st,
- struct bfq_entity *entity)
- {
- ………………..
- //把entity按照entity->finish插入st->active红黑树,entity->finish越小entity在红黑树越靠左
- bfq_insert(&st->active, entity);
- /*从entity的node所在节点,一直在红黑树向上得到它的parent节点,用entity->start更新这些红黑树节点的entity->min_start,同时也更新这些node在红黑树的左右子节点对应的entity的min_start。这个更新操作一直到根节点才结束*/
- bfq_update_active_tree(node);
- }
- //从st->acitve tree剔除entity
- static void bfq_active_extract(struct bfq_service_tree *st,
- struct bfq_entity *entity)
- {
- //返回entity所属红黑树下边的entiry对应节点
- node = bfq_find_deepest(&entity->rb_node);
- //把entiry从st active tree剔除
- bfq_extract(&st->active, entity);
- /*从entity的node所在节点,一直在红黑树向上得到它的parent节点,更新这些红黑树节点对应的bfq_entity的成员min_start,同时也更新这些node在红黑树的左右子节点对应的entity的min_start。这个更新操作一直到根节点才结束*/
- if (node)
- bfq_update_active_tree(node);
- }
bfq_update_active_tree()函数源码如下:
- /*从entity的node所在节点,一直在红黑树向上得到它的parent节点,更新这些红黑树节点对应的bfq_entity的成员min_start,同时也更新这些node在红黑树的左右子节点对应的entity的min_start。这个更新操作一直到根节点才结束*/
- static void bfq_update_active_tree(struct rb_node *node)
- {
- struct rb_node *parent;
- up:
- //先更新当前node对应的entity的min_start
- bfq_update_active_node(node);
- parent = rb_parent(node);
- if (!parent)//更新entity的min_start,直到entity红黑树根节点才return返回
- return;
- //如果node这个entity是父节点parent的左节点,则更新parent节点的右节点对应的entity的min_start
- if (node == parent->rb_left && parent->rb_right)
- bfq_update_active_node(parent->rb_right);
- else if (parent->rb_left)//否则说明node这个entity是父节点parent的右节点,则更新parent节点的左节点对应的entity的min_start
- bfq_update_active_node(parent->rb_left);//更新entity->min_start
- node = parent;
- goto up;
- }
- //先把entity->start赋值给entity->min_start,如果entity在红黑树下边的左右子节点的entity的min_start更小,再重新赋值给entity->min_start
- static void bfq_update_active_node(struct rb_node *node)
- {
- struct bfq_entity *entity = rb_entry(node, struct bfq_entity, rb_node);
- //用entity->start更新entity->min_start
- entity->min_start = entity->start;
- //如果当前entity在红黑树下边的左子节点的entity的min_start更小,则赋值给当前entity的min_start
- bfq_update_min(entity, node->rb_right);
- //如果当前entity在红黑树下边的右子节点的entity的min_start更小,则赋值给当前entity的min_start
- bfq_update_min(entity, node->rb_left);
- }
从entity的node所在节点,一直在红黑树向上得到它的parent节点,更新这些红黑树节点对应的bfq_entity的成员min_start,同时也更新这些node在红黑树的左右子节点对应的entity的min_start。这个更新操作一直到根节点才结束。
4:entity->service、st->vtime的更新和使用
entity->service是entity已经消耗的配额,st->vtime是bfq调度器的虚拟运行时间。在派发IO请求时,__bfq_dispatch_request()->bfq_dispatch_rq_from_bfqq(),先执行bfq_serv_to_charge()根据本次派发的IO请求bfqq->next_rq需要传输的字节数,计算需要消耗的配额served。然后执行到bfq_bfqq_served()函数,执行里边的entity->service += served把served累加到entity->service,同时执行st->vtime += bfq_delta(served, st->wsum),等同于st->vtime += served*N/ st->wsum(N是个常数),其实就是根据本次派发的IO请求消耗的配额累加到st->vtime。
显然,每派发一个IO请求,都要根据派发的IO请求消耗的配额,累加到entity->service,累计到st->vtime,entity->service和st->vtime随着派发IO请求个数增加而累加。
每派发调度器一个entity的一个IO请求,都要计算派发的IO请求的消耗的配额 (使用served*N/ st->wsum计算,N是一个常数),然后累计到调度器的虚拟运行时间st->vtime。简单说st->vtime= (bfqq1派发的IO请求消耗的配额之和+ bfqq2的派发的IO请求消耗的配额之和+……….+bfqqN的派发的IO请求消耗的配额之和) /(bfqq1的entity的权重+ bfqq1的entity的权重+………+bfqqN的entity的权重)。
5:为什么bfq有利交互式进程IO快速响应
首先,一个新的进程派发IO请求前,会分配一个新的bfqq,然后把这个新创建的bfqq的entity加入st->active红黑树,函数流程是bfq_add_request()->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_activate_entity()源码简化下:
- static void __bfq_activate_entity(struct bfq_entity *entity,
- bool non_blocking_wait_rq)
- {
- if (non_blocking_wait_rq && bfq_gt(st->vtime, entity->finish)) {
- backshifted = true;
- min_vstart = entity->finish;
- } else
- min_vstart = st->vtime;
- ……………………..
- entity->start = min_vstart;//更新entity->start
- //st->wsum调度类st的active和idle tree上所有entity的权重之和,累加每一个entity的entity->weight
- st->wsum += entity->weight;
- //按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树
- bfq_update_fin_time_enqueue(entity, st, backshifted);
- }
重点是用st->vtime 更新entity->start,然后执行bfq_update_fin_time_enqueue(),按照entity的初始权重entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树。这样可以保证entity的entity->start和entity->finish尽可能小,entity尽可能靠左插入到st->active红黑树。
然后在bfq_update_next_in_service()->bfq_lookup_next_entity()->__bfq_lookup_next_entity->bfq_first_active_entity()函数流程,在st->active红黑树选择一个最符合条件的entity即sd->next_in_service时。哪个entity最符合条件呢?是st->active红黑树最左边并且entity->start最小并且entity->start小于st->vtime的entity。一个entity的entity->finish越小在st->active红黑树 越靠左,entity->start越小越容易符合小于st->vtime的条件。因此新派发IO请求的进程创建的bfqq的entity插入st->active红黑树后,更容易在bfq_update_next_in_service()函数中选中被赋于sd->next_in_service。
bfq_update_next_in_service()的执行时机主要有3个,第1节有解过。
然后,等__bfq_dispatch_request()->bfq_select_queue()派发IO请求时,bfqd->in_service_queue这个正在的使用的bfqq过期失效,执行bfq_bfqq_expire()令bfqd->in_service_queue这个bfqq失效,然后执行bfq_set_in_service_queue()->bfq_get_next_queue(),把sd->next_in_service赋于sd->in_service_entity,sd->in_service_entity是bfq调度器当前使用的entity。然后返回sd->in_service_entity这个entity对应的bfqq。接着回到__bfq_set_in_service_queue()函数,执行bfqd->in_service_queue = bfqq,bfqd->in_service_queue是bfq当前使用的bfqq。好了,刚才那个新进程派发IO请求,创建的bfqq终于被bfq调度器使用到了,接着就可以派发这个进程的IO请求了。
6:st->first_idle 和 st->last_idle的更新
先把前文使用的贴图发下(为了演示简单,没有演示entity在红黑树中的树形排列形式,只是演示entity按照entity->finish由小到大从左向右排列)
一个新bfqq的entity是先加入的st->active 红黑树,后续如果该bfqq没有要派发的IO请求了,则把该bfqq的entity移动到st->idle红黑树。st->idle红黑树上entity是按照entity->finish由小到大从左向右排列。st->first_idle指向st->idle红黑树entity->finish最小的entity, st-> last_idle指向st->idle红黑树entity->finish最大的entity。下边把用到st->first_idle和st-> last_idle的函数源码列下:
把entity从st->active红黑树剔除的函数流程bfq_deactivate_entity()->__bfq_deactivate_entity()->bfq_idle_extract()
- //entity从st->active红黑树剔除掉,可能更新st->first_idle或st->last_idle
- static void bfq_idle_extract(struct bfq_service_tree *st,
- struct bfq_entity *entity)
- {
- struct bfq_queue *bfqq = bfq_entity_to_bfqq(entity);
- struct rb_node *next;
- /*entity是st->first_idle指向的entity,即st->idle红黑树最左边的entity。因为entiry要从st->active红黑树剔除,则选择entity右边的entity赋于st->first_idle*/
- if (entity == st->first_idle) {
- next = rb_next(&entity->rb_node);
- st->first_idle = bfq_entity_of(next);
- }
- /*entity是st->last_idle指向的entity,即st->idle红黑树最右边的entity。因为entiry要从st->active红黑树剔除,则选择entity左边的entity赋于st->last_idle*/
- if (entity == st->last_idle) {
- next = rb_prev(&entity->rb_node);
- st->last_idle = bfq_entity_of(next);
- }
- //entity从st->active红黑树剔除掉
- bfq_extract(&st->idle, entity);
- if (bfqq)
- list_del(&bfqq->bfqq_list);
- }
把entity从st->active红黑树剔除,然后加入st->idle红黑树的函数流程bfq_deactivate_entity()->__bfq_deactivate_entity()->bfq_idle_insert()。
- //把entity插入st->idle树,并可能更新st->first_idle或st->last_idle
- static void bfq_idle_insert(struct bfq_service_tree *st,
- struct bfq_entity *entity)
- {
- struct bfq_queue *bfqq = bfq_entity_to_bfqq(entity);
- struct bfq_entity *first_idle = st->first_idle;
- struct bfq_entity *last_idle = st->last_idle;
- //st->idle红黑树的最左边的entiry的finish最小,最右边的entity的finish最大。
- //st->first_idle记录entity->finish最小的entity,就是st->idle红黑树最靠左的entity
- if (!first_idle || bfq_gt(first_idle->finish, entity->finish))
- st->first_idle = entity;
- //st->last_idle记录entity->finish最大的entity,就是st->idle红黑树最靠右的entity
- if (!last_idle || bfq_gt(entity->finish, last_idle->finish))
- st->last_idle = entity;
- //把entity按照entity->finish插入st->idle红黑树,entity->finish越小entity在红黑树越靠左
- bfq_insert(&st->idle, entity);
- if (bfqq)//加入st->idle 的红黑树都加入到bfqd->idle_list链表
- list_add(&bfqq->bfqq_list, &bfqq->bfqd->idle_list);
- }
派发IO请求时执行__bfq_dispatch_request ()->bfq_dispatch_rq_from_bfqq ()->bfq_bfqq_served()->bfq_forget_idle(),释放掉st->first_idle指向的entity。
- //处理st->active红黑树的first_idle和last_idle,可能会把first_idle指向的entity从st->active红黑树剔除掉,释放掉它的bfqq和entity
- static void bfq_forget_idle(struct bfq_service_tree *st)
- {
- struct bfq_entity *first_idle = st->first_idle;
- struct bfq_entity *last_idle = st->last_idle;
- //st->active红黑树空并且last_idle->finish小于st->vtime则if成立,
- if (RB_EMPTY_ROOT(&st->active) && last_idle &&
- !bfq_gt(last_idle->finish, st->vtime)) {
- st->vtime = last_idle->finish;
- }
- //first_idle非空,并且st->vtime大于first_idle->finish则if成立
- if (first_idle && !bfq_gt(first_idle->finish, st->vtime))
- //first_idle指向的entity从st->active红黑树剔除掉,如果不再被用到,释放掉它的bfqq和entity
- bfq_put_idle_entity(st, first_idle);
- }
bfq_forget_idle()函数需要重点说下,当st->first_idle->finish小于st->vtime,该函数会执行bfq_put_idle_entity()释放掉st->first_idle指向的entity。st->vtime随着派发一个又一个IO请求而不断增加,st->vtime大于st->first_idle->finish是迟早的事。讲到这里,感觉bfq还是有很多知识点没有讲通,后续再慢慢更新吧……..水平有限,如有错误请指正。
最后说下测试经验:为了能复现多个进程的bfqq同时存在st->active红黑树,然后在st->active红黑树中查找st->start最小的entity,fio测试时的参数iodepth要很大,numjobs也要尽可能多,如下:
fio -filename=/mnt/ext4/fio_test -direct=1 -iodepth 80 -thread -rw=randrw -rwmixread=50 -ioengine=libaio -bs=64k -size=10G -numjobs=6 -runtime=180 -group_reporting -name=test。同时,也要有脏页回写进程在频繁刷IO。
这篇关于内核block层IO调度器—bfq算法之3源码要点总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!