内核block层IO调度器—bfq算法之2源码详解"/>
内核block层IO调度器—bfq算法之2源码详解
在前一篇《内核block层IO调度器—bfq算法之1整体流程介绍》文章的基础上,本文主要讲解bfq算法内核源码,建议先看看前一篇文章。本文基于centos 8.3,内核版本4.18.0-240.el8,详细源码注释见 .18.0-240.el8。
相对于deadline算法,bfq算法真的复杂,源码相当繁杂。我们先从bfq算法注册给block层的接口函数开始讲解:
- static struct elevator_type iosched_bfq_mq = {
- .ops = {
- .limit_depth = bfq_limit_depth,
- .prepare_request = bfq_prepare_request,
- .requeue_request = bfq_finish_requeue_request,
- .finish_request = bfq_finish_requeue_request,
- .exit_icq = bfq_exit_icq,
- .insert_requests = bfq_insert_requests,//插入req
- .dispatch_request = bfq_dispatch_request,//派发req
- .next_request = elv_rb_latter_request,
- .former_request = elv_rb_former_request,
- .allow_merge = bfq_allow_bio_merge,
- .bio_merge = bfq_bio_merge,
- .request_merge = bfq_request_merge,
- .requests_merged = bfq_requests_merged,
- .request_merged = bfq_request_merged,
- .has_work = bfq_has_work,
- .depth_updated = bfq_depth_updated,
- .init_hctx = bfq_init_hctx,
- .init_sched = bfq_init_queue,
- .exit_sched = bfq_exit_queue,
- },
- ...................
- };
这么多接口函数,主要用到的bfq_insert_requests()和bfq_dispatch_request()。前者是把IO请求插入到bfq算法队列,后者是从bfq算法队列取出IO请求并派发给磁盘驱动,本文也主要介绍这两个函数。说明一点,bfq函数繁多,为了方便介绍,删减了一部分代码。
正式开讲前,还是再说明一点,bfq关键数据结构在内核源码和本文都是以简称出现,如下:
- 1:bfq_data简称bfqd
- 2:bfq_queue简称bfqq
- 3:bfq_entity简称entity
- 4:bfq_service_tree简称st
- 5:bfq_sched_data简称sd
- 6:IO请求request结构简称req
1: bfq_insert_request函数
bfq_insert_requests()里主要是执行bfq_insert_request(),这里先把bfq_insert_request()函数流程示意图贴下:
bfq_insert_request()函数源码如下:
- static void bfq_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq,
- bool at_head)
- {
- /*先查找rq->elv.priv[1]中是否有bfqq,有的话直接从取出返回。否则再尝试通过bic->bfqq[is_sync]得到bfqq,失败则针对异步或者同步IO分配新的bfqq并初始化。这里边有 rq->elv.priv[1] = bfqq*/
- bfqq = bfq_init_rq(rq);
- ...........
- //req插入bfqq->sort_list和bfqq->fifo链表,并选择设置下一次派发的req于bfqq->next_rq。可能激 活bfqq,把bfqq添加到st->active红黑树
- idle_timer_disabled = __bfq_insert_request(bfqd, rq);
- //这里是通过 rq->elv.priv[1] 再得到bfqq
- bfqq = RQ_BFQQ(rq);
- }
bfq_insert_request()里主要是执行bfq_init_rq()和__bfq_insert_request()两个函数。bfq_init_rq()主要是为当前进程分配一个bfqq,__bfq_insert_request()主要是把IO请求req插入到bfq的算法队列里,并把bfqq插入到st->active红黑树,算是把bfqq激活。二者源码下边一一介绍。
1.1:bfq_init_rq函数
bfq_init_rq()函数源码如下:
- static struct bfq_queue *bfq_init_rq(struct request *rq)
- {
- //新的req插入时,先查找rq->elv.priv[1]中是否有bfqq,有的话直接从取出返回,rq->elv.priv[1]本身保存的是req所属的bfqq
- if (rq->elv.priv[1])
- return rq->elv.priv[1];//找到bfqq直接return,否则下边分配新的bfqq
- //由rq->elv.icq得到bic
- bic = icq_to_bic(rq->elv.icq);
- ...........
- //先尝试通过bic->bfqq[is_sync]得到bfqq,失败则针对异步或者同步IO分配新的bfqq,然后并初始化
- bfqq = bfq_get_bfqq_handle_split(bfqd, bic, bio, false, is_sync,
- &new_queue);//新分配一个new_queue则new_queue设置true
- ..........
- rq->elv.priv[0] = bic;//rq->elv.priv[0]指向bic
- rq->elv.priv[1] = bfqq;//rq->elv.priv[1]指向bfqq
- ............
- return bfqq;
- }
先查找rq->elv.priv[1]中是否有bfqq,有的话直接从取出返回,失败则执行bfq_get_bfqq_handle_split()分配新的bfqq并初始化。
1.2: bfq_get_bfqq_handle_split函数
bfq_get_bfqq_handle_split()函数源码如下:
- static struct bfq_queue *bfq_get_bfqq_handle_split(struct bfq_data *bfqd,
- struct bfq_io_cq *bic,
- struct bio *bio,
- bool split, bool is_sync,
- bool *new_queue)
- {
- //先尝试由 bic->bfqq[]得到bfqq
- struct bfq_queue *bfqq = bic_to_bfqq(bic, is_sync);
- //由bic得到的bfqq存在直接返回bfqq
- if (likely(bfqq && bfqq != &bfqd->oom_bfqq))
- return bfqq;
- //到这里说明肯定要执行bfq_get_queue()分配一个新的bfqq,则new_queue设置true
- if (new_queue)
- *new_queue = true;
- if (bfqq)
- bfq_put_queue(bfqq);
- //针对异步或者同步IO分配bfqq,并初始化bfqq和entity
- bfqq = bfq_get_queue(bfqd, bio, is_sync, bic);
- //新分配的bfqq设置到bic->bfqq[is_sync]数组
- bic_set_bfqq(bic, bfqq, is_sync);//bic->bfqq[is_sync] = bfqq
- ..............
- return bfqq;
- }
这个函数先尝试通过bic->bfqq[is_sync]得到bfqq,失败则调用bfq_get_queue()分配一个新的bfqq。bfq_get_queue()函数下边讲解。
1.3 :bfq_get_queue函数
bfq_get_queue()函数如下:
- //针对异步或者同步IO分配bfqq,并初始化bfqq和entity
- static struct bfq_queue *bfq_get_queue(struct bfq_data *bfqd,
- struct bio *bio, bool is_sync,
- struct bfq_io_cq *bic)
- {
- ........
- if (!is_sync) {
- //异步IO时,从这里得到一个新的异步bfqq
- async_bfqq = bfq_async_queue_prio(bfqd, bfqg, ioprio_class,
- ioprio);
- bfqq = *async_bfqq;
- if (bfqq)
- goto out;
- }
- //同步IO,在这里分配一个新的同步 bfqq
- bfqq = kmem_cache_alloc_node(bfq_pool,
- GFP_NOWAIT | __GFP_ZERO | __GFP_NOWARN,
- bfqd->queue->node);
- if (bfqq) {
- //bfqq初始化
- bfq_init_bfqq(bfqd, bfqq, bic, current->pid,
- is_sync);
- //bfqq的entity初始化
- bfq_init_entity(&bfqq->entity, bfqg);
- }
- ............
- return bfqq;
- }
bfq_get_queue()函数调用bfq_async_queue_prio()或kmem_cache_alloc_node()分配一个新的bfqq,然后调用bfq_init_bfqq()和bfq_init_entity()对bfqq和bfqq的entity初始化。下边讲解__bfq_insert_request()函数。
2:__bfq_insert_request函数
__bfq_insert_request()函数源码如下:
- static bool __bfq_insert_request(struct bfq_data *bfqd, struct request *rq)
- {
- //通过 rq->elv.priv[1] 再得到bfqq,为req分配新的bfqq后,会把bfqq暂存在 rq->elv.priv[1]
- struct bfq_queue *bfqq = RQ_BFQQ(rq),
- ...........
- //把req添加到bfqq->sort_list链表,并选择合适的req赋于bfqq->next_rq。可能激活bfqq,把bfqq添加到st->active红黑树
- bfq_add_request(rq);
- //计算req的fifo超时时间
- rq->fifo_time = ktime_get_ns() + bfqd->bfq_fifo_expire[rq_is_sync(rq)];
- //req插入bfqq->fifo链表,超时派发
- list_add_tail(&rq->queuelist, &bfqq->fifo);
- //没啥重点操作,里边可能会执行到bfq_bfqq_expire()
- bfq_rq_enqueued(bfqd, bfqq, rq);
- }
重点是执行bfq_add_request()函数,把IO请求req添加到bfqq->sort_list链表,并选择合适的IO请求req赋于bfqq->next_rq。另一个重点是,该函数里会激活bfqq,把bfqq添加到st->active红黑树,下文讲解。
2.1:bfq_add_request和bfq_bfqq_handle_idle_busy_switch函数
bfq_add_request()函数源码如下:
- //把req添加到bfqq->sort_list链表,并选择合适的req赋于bfqq->next_rq
- static void bfq_add_request(struct request *rq)
- {
- //通过rq->elv.priv[1]得到保存的bfqq
- struct bfq_queue *bfqq = RQ_BFQQ(rq);
- bfqq->queued[rq_is_sync(rq)]++;
- bfqd->queued++
- //bfq_add_request()中把req添加到bfqq->sort_list链表
- elv_rb_add(&bfqq->sort_list, rq);
- //第一次插入req时,bfqq->next_rq是NULL,
- prev = bfqq->next_rq;
- //从bfqq->next_rq和rq选个一个合适的req作为下次优先派发的req
- next_rq = bfq_choose_req(bfqd, bfqq->next_rq, rq, bfqd->last_position);
- //下次优先派发req
- bfqq->next_rq = next_rq;
- .............
- if (!bfq_bfqq_busy(bfqq))//重点操作是把bfqq插入到st->active红黑树
- bfq_bfqq_handle_idle_busy_switch(bfqd, bfqq, old_wr_coeff,rq, &interactive);
- }
这个函数开头主要是把IO请求添加到各种链表,选择下次最合适派发的IO请求赋值给bfqq->next_rq。重点是,如果bfqq的entity不在st->active红黑树(比如是新分配的bfqq,或者bfqq的entity处于st->idle红黑树),则执行bfq_bfqq_handle_idle_busy_switch()函数把bfqq添加到st->active红黑树。bfq_bfqq_handle_idle_busy_switch()函数源码如下:
- //重点操作是把bfqq的entity插入到st->active红黑树
- 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)
- {
- .............
- bfq_clear_bfqq_just_created(bfqq);
- .............
- bfq_clear_bfqq_softrt_update(bfqq);
- //把bfqq插入到st->active红黑树,标记bfqq busy
- bfq_add_bfqq_busy(bfqd, bfqq);
- }
bfq_bfqq_handle_idle_busy_switch()函数主要是执行bfq_add_bfqq_busy()函数,下文讲解。
2.2: bfq_add_bfqq_busy函数
bfq_add_bfqq_busy()函数源码如下:
- //把bfqq插入到st->active红黑树,标记bfqq busy
- void bfq_add_bfqq_busy(struct bfq_data *bfqd, struct bfq_queue *bfqq)
- {
- //把bfqq插入到st->active红黑树
- bfq_activate_bfqq(bfqd, bfqq);
- //标记bfqq busy
- bfq_mark_bfqq_busy(bfqq);
- //每向st->active红黑树添加一个bfqq,bfqq对应调度算法的bfqd->busy_queues[]成员加1
- bfqd->busy_queues[bfqq->ioprio_class - 1]++;
- ...........
- }
该函数是先执行bfq_activate_bfqq()把bfqq插入到st->active红黑树,然后执行bfq_mark_bfqq_busy()标记bfqq busy。显然只要bfqq添加到bfq调度器相关的st->active红黑树,则就会标记bfqq busy,前文if (!bfq_bfqq_busy(bfqq))是判断bfqq是否busy。下文重点介绍bfq_activate_bfqq()函数。
3 :bfq_activate_bfqq和bfq_activate_requeue_entity函数
先把bfq_activate_bfqq()函数流程图发下:
bfq_activate_bfqq()函数源码如下:
- void bfq_activate_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq)
- {
- struct bfq_entity *entity = &bfqq->entity;
- //把bfqq插入到st->active红黑树
- bfq_activate_requeue_entity(entity, bfq_bfqq_non_blocking_wait_rq(bfqq),false, false);
- bfq_clear_bfqq_non_blocking_wait_rq(bfqq);
- }
主要还是执行bfq_activate_requeue_entity()函数,源码如下:
- static void bfq_activate_requeue_entity(struct bfq_entity *entity,
- bool non_blocking_wait_rq,
- bool requeue, bool expiration)
- {
- struct bfq_sched_data *sd;
- for_each_entity(entity) {
- sd = entity->sched_data;
- /*如果entity是调度器正在使用的entiry或entity处于st->active红黑树,计算entity的虚拟运行时间并累加到entity->finish,把entity按照新的entity->finish插入st->active红黑树。否则计算新的entity->start,按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照entity->finish插入st->active红黑树*/
- __bfq_activate_requeue_entity(entity, sd, non_blocking_wait_rq);
- //从st查找合适的entity并更新到sd->next_in_service,作为下次派发IO请求优先使用的bfqq,没有找到则返回NULL
- if (!bfq_update_next_in_service(sd, entity, expiration) &&
- !requeue)
- break;
- }
- }
bfq_activate_requeue_entity()函数,源码注释说的比较清楚。这里再简单总结一下: __bfq_activate_requeue_entity()函数本质是计算entity的entity->start 和 entity->finish,entity->start是entity的起始虚拟运行时间,来自bfq调度器的虚拟运行时间st->vtime(这个下文讲解)。我的理解entity->start就是entity插入st->active红黑树时的bfq调度器虚拟运行时间。entity->finish是entity的结束虚拟运行时间,可以简单理解成entity->finish=entity->start + entity消耗的配额/权重,这点下文也有讲解。entiry是按是entity->finish由小到大在红黑树里从左向右排列。bfq_update_next_in_service()函数从st查找合适的entity并更新到sd->next_in_service。
3.1:__bfq_activate_requeue_entity函数
__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或bfqq所属调度类对应的bfq_service_tree结构
- struct bfq_service_tree *st = bfq_entity_service_tree(entity);
- //如果entity是bfq调度器正在使用的entiry或entity处于st->active红黑树
- if (sd->in_service_entity == entity || entity->tree == &st->active)
- /*计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树*/
- __bfq_requeue_entity(entity);
- else
- /*计算新的entity->start,按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,把entity按照entity->finish插入st->active红黑树*/
- __bfq_activate_entity(entity, non_blocking_wait_rq);
- }
__bfq_activate_requeue_entity()函数主要有两个分支:
1:如果entity是调度器正在使用的entiry或entity处于st->active红黑树,计算entity的虚拟运行时间并累加到entity->finish,把entity按照新的entity->finish插入st->active红黑树
2:否则计算新的entity->start,按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照entity->finish插入st->active红黑树
具体实现还是看下__bfq_requeue_entity 和 __bfq_activate_entity的源码实现。
3.2:__bfq_requeue_entity函数
先看下流程图
static void __bfq_requeue_entity(struct bfq_entity *entity)
- {
- struct bfq_sched_data *sd = entity->sched_data;
- struct bfq_service_tree *st = bfq_entity_service_tree(entity);
- //entity是bfq调度器正在使用的entity
- if (entity == sd->in_service_entity) {
- /*根据entity->service/entity->weight计算entity已经消耗的虚拟运行时间,entity->start加上这段虚拟运行时间就是entity->finish*/
- bfq_calc_finish(entity, entity->service);
- //用entity->finish更新entity->start,重置了
- entity->start = entity->finish;
- //如果entity在st->active红黑树,则从st->acitve tree剔除entity,下边再把entity加入st->active红黑树
- if (entity->tree)
- bfq_active_extract(st, entity);
- } else {
- /*从st->acitve tree剔除entity。这里不禁有疑问,上边也是执行bfq_active_extract(),有什么区别?上边更新了entity->finish,这里没有。下边把entity重新插入st->active红黑树时,就是按照最新entity->finish把entity插入st->active*/
- bfq_active_extract(st, entity);
- }
- /*按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树*/
- bfq_update_fin_time_enqueue(entity, st, false);
- }
__bfq_requeue_entity()函数主要作用是:if (entity == sd->in_service_entity)成立表示entity是bfq当前调度器正在使用的entity,则执行bfq_calc_finish()根据entity已经消耗的配额entity->service计算entity消耗的虚拟运行时间,entity->start加上这段虚拟运行时间得到entity->finish。如果if (entity == sd->in_service_entity)不成立,只是执行bfq_active_extract()把entity从st->active 红黑树剔除掉。最后,执行bfq_update_fin_time_enqueue(),还要再根据entity的配额entity->budget计算一次entity->finish,然后把entity按照最新的entity->finish插入st->active红黑树。下文依次介绍这些函数:
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);
- /*service是entity已经消耗的配额,除以weight得到entity已经消耗的虚拟运行时间.entity->finish等于entity->start加上entity已经消耗的虚拟运行时间*/
- entity->finish = entity->start + bfq_delta(service, entity->weight);
- }
根据service/entity->weight计算entity已经消耗的虚拟运行时间,entity->start加上这段虚拟运行时间就是entity->finish。bfq_active_extract()源码如下,就是从st->active红黑树剔除entity。
- static void bfq_active_extract(struct bfq_service_tree *st,
- struct bfq_entity *entity)
- {
- struct bfq_queue *bfqq = bfq_entity_to_bfqq(entity);
- struct rb_node *node;
- ...............
- //返回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);
- ...............
- }
- static void bfq_extract(struct rb_root *root, struct bfq_entity *entity)
- {
- entity->tree = NULL;
- rb_erase(&entity->rb_node, root);
- }
下边补个bfq_active_extract()函数流程图
下边看下bfq_update_fin_time_enqueue()函数
3.3 :bfq_update_fin_time_enqueue函数
先看下流程图
static void bfq_update_fin_time_enqueue(struct bfq_entity *entity,
- struct bfq_service_tree *st,
- bool backshifted)
- {
- struct bfq_queue *bfqq = bfq_entity_to_bfqq(entity);
- st = __bfq_entity_update_weight_prio(st, entity, true);
- /*这里简化成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->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树。注意,bfq_update_fin_time_enqueue()函数里执行bfq_calc_finish(entity, entity->budget),是用entity的配额entity->budget计算得到entity->finish。接着,执行bfq_active_insert()函数把entity插入st->active红黑树。bfq_active_insert()函数源码如下:
3.4:bfq_active_insert函数
先看下流程图
bfq_active_insert()函数源码如下:
- static void bfq_active_insert(struct bfq_service_tree *st,
- struct bfq_entity *entity)
- {
- struct bfq_queue *bfqq = bfq_entity_to_bfqq(entity);
- struct rb_node *node = &entity->rb_node;
- ………………………
- //把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);
- }
bfq_active_insert()源码注释写的比较清楚,不再赘述。bfq_insert()函数源码如下:
- static void bfq_insert(struct rb_root *root, struct bfq_entity *entity)
- {
- struct bfq_entity *entry;
- //st->idle或者st->active 树root节点
- struct rb_node **node = &root->rb_node;
- struct rb_node *parent = NULL;
- ............
- while (*node) {
- parent = *node;
- entry = rb_entry(parent, struct bfq_entity, rb_node);
- /*entity->finish小于红黑树节点的entry->finish则if成立,然后取出entry的左节点。显然,st->idle或者st->active红黑树中的的entity是按照entity->finish由小到大从左到右进行排序的*/
- if (bfq_gt(entry->finish, entity->finish))
- node = &parent->rb_left;
- else
- node = &parent->rb_right;
- }
- rb_link_node(&entity->rb_node, parent, node);
- rb_insert_color(&entity->rb_node, root);
- //entity->tree指向st->active或者st->idle红黑树根节点
- entity->tree = root;
- }
把entity按照entity->finish插入st->idle或者st->active红黑树都要执行bfq_insert()函数,entity->finish越小entity在红黑树越靠左。接着,__bfq_activate_requeue_entity()里另一个分支的__bfq_activate_entity()函数。
3.5:__bfq_activate_entity函数
先看下示意图
__bfq_activate_entity()函数源码如下:
- static void __bfq_activate_entity(struct bfq_entity *entity,
- bool non_blocking_wait_rq)
- {
- struct bfq_service_tree *st = bfq_entity_service_tree(entity);
- bool backshifted = false;
- unsigned long long min_vstart;
- //if和else分支测试可能成立,新分配的bfqq应该走else分支
- if (non_blocking_wait_rq && bfq_gt(st->vtime, entity->finish)) {
- backshifted = true;
- min_vstart = entity->finish;
- } else
- min_vstart = st->vtime;
- ............
- //entity现在处于st->idle红黑树
- if (entity->tree == &st->idle) {
- //entity从st->idle红黑树剔除掉,可能更新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;
- //st->wsum调度类st的active和idle tree上所有entity的权重之和,累加每一个entity的entity->weight
- st->wsum += entity->weight;
- bfq_get_entity(entity);
- entity->on_st_or_in_serv = true;
- }
- ................
- /*按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树*/
- bfq_update_fin_time_enqueue(entity, st, backshifted);
- }
如果if (entity->tree == &st->idle)成立,说明之前entity处于st->idle红黑树,则要把它从st->idle红黑树剔除掉,重新计算它的entity->start,entity->start取自st->vtime和entity->finish的最大者。如果if (entity->tree == &st->idle)不成立,则说明该entity应该是新创建的bfqq的entity,则entity->start= st->vtime,st->wsum += entity->weight是令st->wsum += entity->weight累加该entity的权重,st->wsum会累加所有加入st->active或st->idle红黑树的entity的权重。
最后,是执行bfq_update_fin_time_enqueue(),按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->active红黑树。这点前文已经说过。
4: bfq_dispatch_request函数
顾名思义bfq_dispatch_request()是派发IO请求的,它基本就只是执行了__bfq_dispatch_request()函数而已,下文先看下__bfq_dispatch_request()函数源码。
4.1:__bfq_dispatch_request函数
先看下流程图
static struct request *__bfq_dispatch_request(struct blk_mq_hw_ctx *hctx)
- {
- ............
- /*1:如果bfqd->in_service_queue指向的bfqq为NULL,或者这个bfqq上没有要派发的req则选择一个新的bfqq。2:bfqd->in_service_queue指向bfqq如果配额足够则返回它。3:如果这个bfqq的配额被消耗光了,则先令bfqq失效,接着也选择一个新的bfqq*/
- bfqq = bfq_select_queue(bfqd);
- /*计算并返回传输本次IO请求需要配额served:根据本次传的配额served,增加entity配额entity->service和调度器虚拟运行时间st->vtime。计算新的bfqd->peak_rate,并选择一个新的IO请求作为bfqq->next_rq,计算更新新的entity->budget,把IO请求从bfqq->sort_list链表剔除。如果bfqq的entity配额被消耗光了,还有概率执行bfq_bfqq_expire()令bfqq失效*/
- rq = bfq_dispatch_rq_from_bfqq(bfqd, bfqq);
- if (rq) {
- inc_in_driver_start_rq:
- bfqd->rq_in_driver++;
- start_rq:
- rq->rq_flags |= RQF_STARTED;
- }
- exit:
- return rq;
- }
__bfq_dispatch_request()函数主要有两个重点,一个是执行bfq_select_queue()选择一个bfqq,然后执行bfq_dispatch_rq_from_bfqq()从该bfqq上选择一个待派发的IO请求并返回。
4.2 bfq_select_queue函数
- static struct bfq_queue *bfq_select_queue(struct bfq_data *bfqd)
- {
- //当前没有bfqqgoto new_queue
- bfqq = bfqd->in_service_queue;
- if (!bfqq)
- goto new_queue;
- .........
- check_queue:
- next_rq = bfqq->next_rq;//选择优先派发的req
- if (next_rq) {
- /*bfq_serv_to_charge()是计算并返回传输本次req需要配额,bfq_bfqq_budget_left()是该bfqq还剩余的配额。前者大于后者,说明bfqq的配额不够了,则要是bfqq失效,选择新的bfqq*/
- if (bfq_serv_to_charge(next_rq, bfqq) >
- bfq_bfqq_budget_left(bfqq)) {
- ...............
- reason = BFQQE_BUDGET_EXHAUSTED;
- goto expire;
- } else {
- ...............
- goto keep_queue;
- }
- }
- ...............
- reason = BFQQE_NO_MORE_REQUESTS;//bfqq没有req要传输了
- expire:
- ............
- /*bfqq的entity配额被消耗光了或者bfqq没有可派发的req了等,则令bfqq的entity从st->active红黑树踢掉。接着计算entity的虚拟运行时间并更新到entity->finish,把entity重新加入st->idle或st->active红黑树。如果bfqq不会再被调度,可能把entity及bfqq释放掉。*/
- bfq_bfqq_expire(bfqd, bfqq, false, reason);
- new_queue:
- /*核心是取出sd->next_in_service赋于sd->in_service_entity作为马上要使用的entity,这就是新选择的bfqq的entity。最后从st查找合适的entity并更新到sd->next_in_service,没有找到则设置sd->next_in_service为NULL*/
- bfqq = bfq_set_in_service_queue(bfqd);
- if (bfqq) {
- //找到bfqq则goto check_queue分支
- bfq_log_bfqq(bfqd, bfqq, "select_queue: checking new queue");
- goto check_queue;
- }
- keep_queue:
- ..................
- return bfqq;
- }
在bfq_select_queue()函数中,主要分3种情况:
1:如果bfqd->in_service_queue指向的bfqq为NULL,则goto new_queue分支,执行bfqq = bfq_set_in_service_queue(bfqd)选择一个新的bfqq
2:next_rq = bfqq->next_rq是NULL,if (next_rq)不成立,说明这个bfqq上没有要派发的req了。执行reason = BFQQE_NO_MORE_REQUEST(bfqq上没有要派发的req)。接着执行bfq_bfqq_expire()令bfqq过期失效,执行bfqq = bfq_set_in_service_queue(bfqd)选择一个新的bfqq。
3: next_rq = bfqq->next_rq非NULL,if (next_rq)成立,if (bfq_serv_to_charge(next_rq, bfqq) >bfq_bfqq_budget_left(bfqq))不成立,这说明bfqq有足够的配额派发next_rq这个IO请求,则goto keep_queue直接返回bfqq即可。但如果if (bfq_serv_to_charge(next_rq, bfqq) >bfq_bfqq_budget_left(bfqq))成立,如果这个bfqq的配额被消耗光了,则reason = BFQQE_BUDGET_EXHAUSTED,接着执行bfq_bfqq_expire()令bfqq过期失效,执行bfqq = bfq_set_in_service_queue(bfqd)选择一个新的bfqq。
这个过程涉及的函数下文依次介绍。
4.3: bfq_set_in_service_queue函数
- static struct bfq_queue *bfq_set_in_service_queue(struct bfq_data *bfqd)
- {
- /*核心是取出sd->next_in_service赋于sd->in_service_entity作为马上要使用的entity,这就是新选择的bfq_queue的entity。最后从st查找合适的entity并更新到sd->next_in_service,没有找到则设置sd->next_in_service为NULL*/
- 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()函数选择一个新的bfqq作为当前要使用的bfqq,源码如下:
- struct bfq_queue *bfq_get_next_queue(struct bfq_data *bfqd)
- {
- struct bfq_entity *entity = NULL;
- struct bfq_sched_data *sd;
- struct bfq_queue *bfqq;
- //如果没有bfqq直接return NULL
- if (bfq_tot_busy_queues(bfqd) == 0)
- return NULL;
- sd = &bfqd->root_group->sched_data;
- 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;
- ...........
- //如果没有多余的entity作为sd->next_in_service,则要从st->acitve tree剔除entity
- if (bfq_no_longer_next_in_service(entity))
- bfq_active_extract(bfq_entity_service_tree(entity),
- entity);
- }
- bfqq = bfq_entity_to_bfqq(entity);
- for_each_entity(entity) {
- struct bfq_sched_data *sd = entity->sched_data;
- /*从st查找合适的entity并更新到sd->next_in_service,没有找到则设置sd->next_in_service为NULL*/
- if (!bfq_update_next_in_service(sd, NULL, false))
- break;
- }
- return bfqq;
- }
我们要先知道为什么会执行到bfq_get_next_queue()函数。看下流程:bfq_select_queue()->bfq_set_in_service_queue()->bfq_get_next_queue(),这是为了得到一个新的bfqq,作为bfq调度器当前正在使用的bfqq,即sd->in_service_entity。
bfq_get_next_queue()函数主要是把sd->next_in_service指向的entiry赋于sd->in_service_entity。sd->next_in_service是下一次要使用的bfqq的entity,现在执行bfq_get_next_queue()要得到一个新的bfqq,自然要先从sd->next_in_service得到entity,这个bfqq就是sd->in_service_entity。bfqq = bfq_entity_to_bfqq(entity)得到entity对应的bfqq。
接着,要执行bfq_update_next_in_service()选一个新的下一次使用的bfqq的entity,赋于sd->next_in_service。下次执行bfq_get_next_queue()要得到一个新的bfqq时,直接从sd->next_in_service取出即可。
__bfq_set_in_service_queue()函数主要是bfqd->in_service_queue = bfqq,不造啰嗦。
- static void __bfq_set_in_service_queue(struct bfq_data *bfqd,
- struct bfq_queue *bfqq)
- {
- if (bfqq) {
- bfq_clear_bfqq_fifo_expire(bfqq);
- ............
- }
- bfqd->in_service_queue = bfqq;
- }
bfq_update_next_in_service()函数源码下文讲解。
5 :bfq_update_next_in_service函数
- 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;
- ............
- if (new_entity && new_entity != sd->next_in_service) {
- change_without_lookup = true;//这里赋值change_without_lookup=true
- if (next_in_service) {
- unsigned int new_entity_class_idx =
- bfq_class_idx(new_entity);
- struct bfq_service_tree *st = sd->service_tree + new_entity_class_idx;
- change_without_lookup =
- //二者同一个调度策略
- (new_entity_class_idx ==bfq_class_idx(next_in_service)
- &&
- //new_entity->start 小于 st->vtime
- !bfq_gt(new_entity->start, st->vtime)
- &&
- //next_in_service->finish 大于 new_entity->finish
- bfq_gt(next_in_service->finish,new_entity->finish));
- }
- ............
- /*change_without_lookup为true把传入的new_entity赋值给next_in_service,下边把new_entity分配给sd->next_in_service*/
- if (change_without_lookup)
- next_in_service = new_entity;
- }
- ............
- /*如果change_without_lookup为false则执行bfq_lookup_next_entity()选择下一个使用的bfqq的entity。可能找不到,则返回NULL*/
- if (!change_without_lookup)
- next_in_service = bfq_lookup_next_entity(sd, expiration);
- if (next_in_service) {
- bool new_budget_triggers_change =
- bfq_update_parent_budget(next_in_service);
- parent_sched_may_change = !sd->next_in_service ||
- new_budget_triggers_change;
- }
- /*为sd->next_in_servic赋值下一次使用的bfqq的entity。如果前边没找到则sd->next_in_service被赋值NULL*/
- sd->next_in_service = next_in_service;
- if (!next_in_service)
- return parent_sched_may_change;
- return parent_sched_may_change;
- }
简单来说,就是为了找到下一次使用的entity并赋于sd->next_in_service。细节是从bfq调度器sd查找下一次使用的bfqq的entity并更新到sd->next_in_service,没有找到则设置sd->next_in_service为NULL,这些大部分在bfq_lookup_next_entity()函数完成,看下它的源码:
- static struct bfq_entity *bfq_lookup_next_entity(struct bfq_sched_data *sd,
- bool expiration)
- {
- struct bfq_service_tree *st = sd->service_tree;
- ...........
- //在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;
- }
bfq_lookup_next_entity()在sd调度器每个调度策略st里都搜索可用的 entity,而搜索一个st中可用的entiry是执行__bfq_lookup_next_entity()函数,看下它的源码:
- static struct bfq_entity *
- __bfq_lookup_next_entity(struct bfq_service_tree *st, bool in_service)
- {
- struct bfq_entity *entity;
- u64 new_vtime;
- //st->active红黑树没有entiry则返回NULL。next entity必须从st->active的红黑树查询一个合适的entity
- if (RB_EMPTY_ROOT(&st->active))
- return NULL;
- //返回st虚拟运行时间,返回root_entity->min_start或者st->vtime
- new_vtime = bfq_calc_vtime_jump(st);
- //st没有正在service 的entity则可能st->vtime = new_vtime
- if (!in_service)
- bfq_update_vtime(st, new_vtime);
- //从st->active 红黑树左边查找entity->start小于new_vtime并且entity->start最小的entity
- entity = bfq_first_active_entity(st, new_vtime);
- return entity;
- }
主要是执行bfq_first_active_entity()函数完成具体工作,源码如下:
- static struct bfq_entity *bfq_first_active_entity(struct bfq_service_tree *st,
- u64 vtime)
- {
- struct bfq_entity *entry, *first = NULL;
- struct rb_node *node = st->active.rb_node;
- while (node)
- {
- entry = rb_entry(node, struct bfq_entity, rb_node);
- left:
- // entry->start < vtime 则if成立,vtime 是st->vtime
- if (!bfq_gt(entry->start, vtime))
- first = entry;
- //如果entry有左节点,一直向左查找,有靠左的entry,entry->start和entry->min_start越小
- if (node->rb_left) {
- entry = rb_entry(node->rb_left,
- struct bfq_entity, rb_node);
- if (!bfq_gt(entry->min_start, vtime)) {
- node = node->rb_left;
- goto left;
- }
- }
- if (first)
- break;
- node = node->rb_right;
- }
- return first;
- }
bfq_first_active_entity()的查找规则是:从st->active红黑树根节点一直向左查找,找到最左边的并且entity->start小于st->vtime的entiry。为什么要这样查找后啊?因为这样可以找到entity->start最小的entity,这样的entiry更早插入st->active红黑树,当然这样的entity要更容易被选中使用,派发该entity对应的bfqq上的IO请求。ok,返回的entity就可能是下一次派发IO请求会用到的entiry,稍后到赋于sd->next_in_service。
ok,bfq_select_queue()函数到这里介绍完了,下边介绍bfq_bfqq_expire()函数。
6: bfq_bfqq_expire函数
- void bfq_bfqq_expire(struct bfq_data *bfqd,
- struct bfq_queue *bfqq,
- bool compensate,
- enum bfqq_expiration reason)
- {
- struct bfq_entity *entity = &bfqq->entity;
- ............
- /*计算更新bfqq->max_budget,如果bfqq上还有req(bfqq->next_rq)要派发,则计算根据next_rq传输的字节数计算bfqq->entity的配额,保证bfqq的配额还能传输完next_rq*/
- __bfq_bfqq_recalc_budget(bfqd, bfqq, reason);
- ............
- /*bfqq的entity配额被消耗光了或者bfqq没有可派发的IO请求了等等,bfqq的entity会从st->active红黑树踢掉。接着计算entity的虚拟运行时间并更新到entity->finish,把entity重新加入st->idle或st->active红黑树。如果bfqq不会再被调度,可能把entity及bfqq释放掉。*/
- if (__bfq_bfqq_expire(bfqd, bfqq, reason))
- return;
- ...........
- }
bfq_bfqq_expire()函数首先执行__bfq_bfqq_recalc_budget():根据bfqq的过期失效原因,计算bfqq的最大配额bfqq->max_budget。令bfqq具体过期失效在__bfq_bfqq_expire()完成。先看下__bfq_bfqq_recalc_budget ()函数源码:
6.1 __bfq_bfqq_recalc_budget 函数
- static void __bfq_bfqq_recalc_budget(struct bfq_data *bfqd,
- struct bfq_queue *bfqq,
- enum bfqq_expiration reason)
- {
- struct request *next_rq;
- int budget, min_budget;
- ............
- min_budget = bfq_min_budget(bfqd);
- ............
- if (bfqq->wr_coeff == 1)//测试时有30或1
- budget = bfqq->max_budget;
- else
- budget = 2 * min_budget;
- .............
- if (bfq_bfqq_sync(bfqq) && bfqq->wr_coeff == 1) {
- //根据bfqq失效原因reason,计算budget
- switch (reason) {
- case BFQQE_TOO_IDLE:
- ...............
- case BFQQE_BUDGET_TIMEOUT:
- budget = min(budget * 2, bfqd->bfq_max_budget);
- break;
- case BFQQE_BUDGET_EXHAUSTED:
- budget = min(budget * 4, bfqd->bfq_max_budget);
- break;
- case BFQQE_NO_MORE_REQUESTS:
- budget = max_t(int, bfqq->entity.service, min_budget);
- break;
- default:
- return;
- }
- } else if (!bfq_bfqq_sync(bfqq)) {
- budget = bfqd->bfq_max_budget;
- }
- //更新bfqq->max_budget
- bfqq->max_budget = budget;
- ................
- /*如果bfqq上还有req(即bfqq->next_rq)要派发,则计算根据next_rq传输的字节数计算bfqq->entity的配额,保证bfqq的配额还能传输完next_rq*/
- next_rq = bfqq->next_rq;
- if (next_rq)
- bfqq->entity.budget = max_t(unsigned long, bfqq->max_budget,
- bfq_serv_to_charge(next_rq, bfqq));
- }
该函数是计算更新bfqq->max_budget,如果bfqq上还有IO请求(即bfqq->next_rq)要派发,则计算根据next_rq传输的字节数计算bfqq->entity的配额,保证bfqq的配额还能传输完next_rq。
6.2__bfq_bfqq_expire函数
- 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)))
- {
- if (bfqq->dispatched == 0)
- bfqq->budget_timeout = jiffies;
- /*1:把entity从st->active或st->idle红黑树踢掉,ins_into_idle_tree为true则把entity再插入st->idle红黑树,否则直接释放掉entity及bfqq。2:计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照新的entity->finish插入st->idle或st->active红黑树*/
- bfq_del_bfqq_busy(bfqd, bfqq, true);
- } else {
- /*如果entity是调度器正在使用的entiry或entity处于st->active红黑树,计算entity的虚拟运行时间并累加到entity->finish,把entity按照新的entity->finish插入st->active红黑树。否则计算新的entity->start,按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照entity->finish插入st->active红黑树。最后从st查找合适的entity并更新到sd->next_in_service*/
- bfq_requeue_bfqq(bfqd, bfqq, true);
- .................
- }
- .....................
- //bfqd->in_service_queue = NULL 等
- return __bfq_bfqd_reset_in_service(bfqd);
- }
__bfq_bfqq_expire()函数主要有两个分支:如果bfqq上没有要派发的IO请求了(bfqq->sort_list空),则执行bfq_del_bfqq_busy()把bfqq的entity从st->active红黑树剔除掉,然后把entity加入到st->idle红黑树。bfq的调度器都是从st->active红黑树的寻找要使用的entity,然后返回entity的bfqq,再派发这个bfqq上的IO请求。另一个分支是执行bfq_requeue_bfqq(),计算entity的虚拟运行时间,更新entity->finish,然后把entity重新加入st->active红黑树。
bfq_del_bfqq_busy()函数源码如下:
- void bfq_del_bfqq_busy(struct bfq_data *bfqd, struct bfq_queue *bfqq,
- bool expiration)
- {
- //清理bfqq busy状态
- bfq_clear_bfqq_busy(bfqq);
- bfqd->busy_queues[bfqq->ioprio_class - 1]--;
- .................
- bfqg_stats_update_dequeue(bfqq_group(bfqq));
- /*1:把entity从st->active或st->idle红黑树踢掉,ins_into_idle_tree为true则把entity再插入st->idle红黑树,否则直接释放掉entity及bfqq。2:计算entity的虚拟运行时间并累加到entity->finish,最后可能把entity按照新的entity->finish插入st->idle红黑树*/
- bfq_deactivate_bfqq(bfqd, bfqq, true, expiration);
- ...........
- }
主要过程是:bfqq没有IO请求派发了或者bfqq失效了,清理bfqq的busy状态,把bfqq的entity从st->active或st->idle红黑树踢掉,有可能再把bfqq插入到st->idle红黑树。
bfq_deactivate_bfqq函数源码如下:
- 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;
- /*1:把entity从st->active或st->idle红黑树踢掉,ins_into_idle_tree为true则把entity再插入st->idle红黑树,否则直接释放掉entity及bfqq。2:计算entity的虚拟运行时间并累加到entity->finish,最后可能把entity按照新的entity->finish插入st->idle红黑树*/
- bfq_deactivate_entity(entity, ins_into_idle_tree, expiration);
- }
显然,重点还是执行bfq_deactivate_entity函数,这个我们在下节介绍。现在先看下bfq_requeue_bfqq函数源码:
6.3:bfq_requeue_bfqq函数
先看下流程图
bfq_requeue_bfqq()函数源码如下:
- void bfq_requeue_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq,
- bool expiration)
- {
- struct bfq_entity *entity = &bfqq->entity;
- /*如果entity是调度器正在使用的entiry或entity处于st->active红黑树,计算entity的虚拟运行时间并累加到entity->finish,把entity按照新的entity->finish插入st->active红黑树。否则计算新的entity->start,按照entity->budget计算entity的虚拟运行时间并累加到entity->finish,最后把entity按照entity->finish插入st->active红黑树。最后从st查找合适的entity并更新到sd->next_in_service*/
- bfq_activate_requeue_entity(entity, false,
- bfqq == bfqd->in_service_queue, expiration);
- }
bfq_activate_requeue_entity()函数源码再前文已经介绍过,可回头查看。下节我们看看bfq_deactivate_entity函数。
7:bfq_deactivate_entity函数
- static void bfq_deactivate_entity(struct bfq_entity *entity,
- bool ins_into_idle_tree,
- bool expiration)
- {
- struct bfq_sched_data *sd;
- struct bfq_entity *parent = NULL;
- for_each_entity_safe(entity, parent)
- {
- sd = entity->sched_data;
- /*把entity从st->active或st->idle红黑树踢掉,ins_into_idle_tree为true则把entity再插入st->idle红黑树,否则直接释放掉entity及bfqq。剔除成功返回true*/
- if (!__bfq_deactivate_entity(entity, ins_into_idle_tree)) {
- return;
- }
- ............
- /*entity是正在sd->next_in_service指向的调度器正在使用的entiry,则要选择一个新的next_in_service entity*/
- if (sd->next_in_service == entity)
- //从st查找合适的entity并更新到sd->next_in_service,没有找到则返回NULL
- bfq_update_next_in_service(sd, NULL, expiration);
- ............
- if (sd->next_in_service || sd->in_service_entity) {
- break;
- }
- ..............
- ins_into_idle_tree = true;
- }
- ..............
- }
里边主要执行了两个函数__bfq_deactivate_entity()和bfq_update_next_in_service()。前者是重点:把entity从st->active或st->idle红黑树踢掉,ins_into_idle_tree为true则把entity再插入st->idle红黑树,否则直接释放掉entity及bfqq。然后,计算entity的虚拟运行时间并累加到entity->finish,最后可能把entity按照新的entity->finish插入st->idle红黑树。
bfq_update_next_in_service()函数在前边已经讲解,主要是选择下一个使用的entity,赋于sd->next_in_service,不再赘述。下文重点看__bfq_deactivate_entity()函数源码。
7.1: __bfq_deactivate_entity函数
先看下流程图
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 = NULL;
- else
- entity->service = 0;
- ............
- //如果entity处于st->active红黑树,从st->acitve tree剔除entity
- if (entity->tree == &st->active)
- bfq_active_extract(st, entity);
- //如果entity处于st->idle红黑树,并且entity不是st正在使用的entity
- else if (!is_in_service && entity->tree == &st->idle)
- bfq_idle_extract(st, entity);//entity从st->idle红黑树剔除掉,可能更新st->first_idle或st->last_idle
- /*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))
- //entity不再被用到,释放bfqq,把entity剔除掉
- bfq_forget_entity(st, entity, is_in_service);
- else
- //把entity再插入st->idle红黑树,并可能更新st->first_idle或st->last_idle
- bfq_idle_insert(st, entity);
- return true;
- }
可以发现,首先执行bfq_calc_finish(entity, entity->service)以entity已经消耗的配额entity->service计算entity->finish。注意,这里不是以entity的配额entity->weight计算entity->finish,bfq_update_fin_time_enqueue()函数中执行的bfq_calc_finish(entity, entity->budget)才是以entity的配额entity->budget计算entity->finish。最后,执行bfq_active_extract()或bfq_idle_extract()把entity从st->active或st->idle红黑树踢掉,ins_into_idle_tree为true则把entity再插入st->idle红黑树,否则直接释放掉entity及bfqq剔除返回true。
bfq_active_extract()前文介绍过,下边把bfq_idle_extract和bfq_idle_insert函数源码贴上。
7.2 :bfq_idle_extract和bfq_idle_insert函数
- //entity从st->idle红黑树剔除掉,可能更新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->idle红黑树剔除,则选择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->idle红黑树剔除,则选择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->idle红黑树剔除掉
- bfq_extract(&st->idle, entity);
- if (bfqq)
- list_del(&bfqq->bfqq_list);
- }
bfq_idle_extract()函数源码注释写的比较清楚,需要重点说明的一点是,在把entity从st->idle红黑树剔除掉前,需要更新st->first_idle和st->last_idle。st->first_idle永远指向st->idle红黑树最左边的entity,st->last_idle永远指向st->idle红黑树最右边的entity。
- //把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);
- }
bfq_idle_insert()中是把entity插入st->idle树,并可能更新st->first_idle或st->last_idle。因为st->first_idle永远指向st->idle红黑树最左边的entity,st->last_idle永远指向st->idle红黑树最右边的entity。
8: 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;
- /*计算并返回本次派发的IO请求需要的配额,配额以IO请求要传输字节数为基准,同步和异步IO计算方法不一样。*/
- service_to_charge = bfq_serv_to_charge(rq, bfqq);
- /*根据本次传输IO请求的配额served,增加entity配额entity->service和调度器虚拟运行时间st->vtime*/
- bfq_bfqq_served(bfqq, service_to_charge);
- if (bfqq == bfqd->in_service_queue && bfqd->wait_dispatch) {
- bfqd->wait_dispatch = false;
- bfqd->waited_rq = rq;
- }
- /*计算新的bfqd->peak_rate,并选择一个新的req作为bfqq->next_rq,计算更新新的entity->budget,把req从bfqq->sort_list链表剔除*/
- bfq_dispatch_remove(bfqd->queue, rq);
- //bfqq不是正在使用的
- if (bfqq != bfqd->in_service_queue)
- goto return_rq;
- /*更新bfqq->bfqd->wr_busy_queues、bfqq->wr_coeff、bfqq->wr_cur_max_time、bfqq->last_wr_start_finish、bfqq->entity.prio_changed等参数*/
- bfq_update_wr_data(bfqd, bfqq);
- if (!(bfq_tot_busy_queues(bfqd) > 1 && bfq_class_idle(bfqq)))//这个if一般都成立
- goto return_rq;
- //这里一般执行不到
- bfq_bfqq_expire(bfqd, bfqq, false, BFQQE_BUDGET_EXHAUSTED);
- return_rq:
- return rq;
- }
首先执行bfq_serv_to_charge()函数计算并返回派发本次IO请求需要配额served。接着执行bfq_bfqq_served()函数根据本次派发的IO请求的配额served,累加到entity配额entity->service和调度器虚拟运行时间st->vtime。接着,执行bfq_dispatch_remove()计算新的bfqd->peak_rate,并选择下一次派发的IO请求赋于bfqq->next_rq,计算更新新的entity->budget,把IO请求从bfqq->sort_list链表剔等等。bfq_serv_to_charge()和bfq_bfqq_served()函数源码如下:
8.1: bfq_serv_to_charge和bfq_bfqq_served函数
bfq_serv_to_charge()函数源码如下:
- //计算并返回传输本次req需要配额,配额以req要传输字节数为基准,同步和异步IO计算方法不一样。
- static unsigned long bfq_serv_to_charge(struct request *rq,
- struct bfq_queue *bfqq)
- {
- if (bfq_bfqq_sync(bfqq) || bfqq->wr_coeff > 1 ||
- bfq_asymmetric_scenario(bfqq->bfqd, bfqq))
- return blk_rq_sectors(rq);//同步IO直接返回req要传输的字节数
- //异步IO是req要传输字节数乘以bfq_async_charge_factor
- return blk_rq_sectors(rq) * bfq_async_charge_factor;
- }
很明显,就是根据派发的IO请求的字节数计算的配额,但是异步IO的话要乘以bfq_async_charge_factor。为什么呢?我推测是让异步IO请求的配额消耗更快,这样更容易过期失效而选择新的bfqq的IO请求而派发。
bfq_bfqq_served()函数源码如下:
- //根据待派发req的配额served,增加entity配额entity->service和调度器虚拟运行时间st->vtime
- void bfq_bfqq_served(struct bfq_queue *bfqq, int served)//served是当前要派发req消耗的配额
- {
- struct bfq_entity *entity = &bfqq->entity;
- struct bfq_service_tree *st;
- if (!bfqq->service_from_backlogged)
- bfqq->first_IO_time = jiffies;
- if (bfqq->wr_coeff > 1)
- bfqq->service_from_wr += served;
- 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->idle红黑树的first_idle和last_idle,可能会把first_idle指向的entity从st->idle红黑树剔除掉,释放掉它的bfqq和entity*/
- bfq_forget_idle(st);
- }
- bfq_log_bfqq(bfqq->bfqd, bfqq, "bfqq_served %d secs", served);
- }
- static u64 bfq_delta(unsigned long service, unsigned long weight)
- {
- return div64_ul((u64)service << WFQ_SERVICE_SHIFT, weight);
- }
bfq_bfqq_served()函数比较重要,主要是根据本次派发IO请求的配额served,累加到entity->service。并且执行st->vtime += bfq_delta(served, st->wsum)计算bfq调度器的虚拟运行时间st->vtime。st->wsum是bfq调度器st中所有bfqq的entity(包括st->active和st->idle红黑树上的entity)的权重之和。bfq_delta(served, st->wsum)很巧妙,每派发调度器st上一个entity的一个IO请求,都要计算派发的IO请求的消耗的配额 (使用served*N/ st->wsum计算,N是一个常数),然后累计到调度器的虚拟运行时间st->vtime。简单说st->vtime= (bfqq1派发的IO请求消耗的配额之和+ bfqq2的派发的IO请求消耗的配额之和+……….+bfqqN的派发的IO请求消耗的配额之和) /(bfqq1的entity的权重+ bfqq1的entity的权重+………+bfqqN的entity的权重)。
8.2: bfq_dispatch_remove 和 bfq_remove_request函数
- static void bfq_dispatch_remove(struct request_queue *q, struct request *rq)
- {
- struct bfq_queue *bfqq = RQ_BFQQ(rq);
- //派发的req数加1
- bfqq->dispatched++;
- /*核心是计算得到bfqd->peak_rate,bfq_calc_max_budget()中根据bfqd->peak_rate计算bfqd->bfq_max_budget*/
- bfq_update_peak_rate(q->elevator->elevator_data, rq);
- /*选择一个新的req作为bfqq->next_rq,计算更新新的entity->budget,把req从bfqq->sort_list链表剔除*/
- bfq_remove_request(q, rq);
- }
bfq_dispatch_remove()函数中:执行bfq_update_peak_rate()计算新的bfqd->peak_rate,这个影响到bfqd->bfq_max_budget参数。接着,执行bfq_remove_request()选择一个新的IO请求req作为bfqq->next_rq,并计算更新新的entity->budget,把本次派发的IO请求从bfqq->sort_list链表剔除。
下边讲解bfq_remove_request()函数,先看下流程图
static void bfq_remove_request(struct request_queue *q,
- struct request *rq)
- {
- struct bfq_queue *bfqq = RQ_BFQQ(rq);
- struct bfq_data *bfqd = bfqq->bfqd;
- const int sync = rq_is_sync(rq);
- ............
- //如果要派发的req是bfqq->next_rq,则要选择一个新的bfqq->next_rq
- if (bfqq->next_rq == rq) {
- //从bfq调度器st红黑树或bfqq->sort_list或bfqq->fifo 选择下一个要派发的req
- bfqq->next_rq = bfq_find_next_rq(bfqd, bfqq, rq);
- //计算更新新的entity->budget,并执行bfq_requeue_bfqq()把bfqq重新插入队列
- bfq_updated_next_req(bfqd, bfqq);
- }
- if (rq->queuelist.prev != &rq->queuelist)
- list_del_init(&rq->queuelist);
- //派发一个req少一个,bfqq->queued[sync]和bfqd->queued减1
- bfqq->queued[sync]--;
- bfqd->queued--;
- //req从bfqq->sort_list链表剔除
- elv_rb_del(&bfqq->sort_list, rq);
- //req从rq->hash链表剔除
- elv_rqhash_del(q, rq);
- if (q->last_merge == rq)
- q->last_merge = NULL;
- ............
- //如果bfqq上没有派发的req
- if (RB_EMPTY_ROOT(&bfqq->sort_list))
- {
- bfqq->next_rq = NULL;
- //如果bfqq有busy状态并且bfqq不是bfqd->in_service_queue正在使用的bfqq
- if (bfq_bfqq_busy(bfqq) && bfqq != bfqd->in_service_queue)
- {
- /*bfqq没有req派发了,清理bfqq的busy状态,把bfqq(entity)从st->active红黑树踢掉,有可能再把bfqq插入到st->idle红黑树*/
- bfq_del_bfqq_busy(bfqd, bfqq, false);
- bfqq->entity.budget = bfqq->entity.service = 0;
- }
- ............
- } else {}
- ............
- }
bfq_remove_request()函数中,主要是选择一个新的IO请求req作为bfqq->next_rq,计算更新的entity->budget,把本次派发的IO请求req从bfqq->sort_list链表剔除。
本来还想对bfq源码做更详细的介绍,但是没想到bfq的源码真的复杂,本文篇幅已经过长,只能在稍后的篇章介绍。最后,水平有限,如有错误请指正。
更多推荐
内核block层IO调度器—bfq算法之2源码详解
发布评论