2.6.4.3 带宽控制 - 定时器

带宽控制器中有两个定时器,分别是 period_timerslack_timer, 前者用来周期性地更新带宽时间并对cfs_rq解挂,后者用来酌情回收已经分配给下属cfs_rq的时间,本节我们将详细探讨他们的实现原理。

定时器的初始化发生在整个带宽控制器的初始化阶段:

/* file: kernel/sched/fair.c */
void init_cfs_bandwidth(struct cfs_bandwidth *cfs_b) {
    raw_spin_lock_init(&cfs_b->lock);
    cfs_b->runtime = 0;
    /* 没有限额 */
    cfs_b->quota = RUNTIME_INF;
    /* 初始化周期为100ms */
    cfs_b->period = ns_to_ktime(default_cfs_period());

    /* 初始化 throttled 列表 */
    INIT_LIST_HEAD(&cfs_b->throttled_cfs_rq);
    /* 初始化 period timer */
    hrtimer_init(&cfs_b->period_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED);
    cfs_b->period_timer.function = sched_cfs_period_timer;

    /* 初始化slack timer */
    hrtimer_init(&cfs_b->slack_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    cfs_b->slack_timer.function = sched_cfs_slack_timer;
    cfs_b->slack_started = false;
}

该函数在初始化任务组时被调用,可以看到两个定时器的回调函数分别是 sched_cfs_period_timersched_cfs_slack_timer, 这里我们先看前者。

该函数相当于是一个控制层,保证带宽的period与quota的大小合适,并且确保实际工作顺利完成,函数 do_sched_cfs_period_timer 可以精简为如下逻辑:

重置好带宽的时间之后,最后调用函数 distribute_cfs_runtime 来解挂所有的被挂起的队列:

至此,整个 period_timer 就介绍完毕了,接下来我们看一下 slack_timer 是做什么的。

我们知道cfs_rq每次向任务组申请5ms的时间,对于CPU而言这个时间也不少了,但如果该cfs_rq中的任务刚好在申请完时间额度之后就进入了睡眠状态,并且还一睡不醒,那么让它在睡眠过程中一直持有这么多时间是不合理的,这完全可能导致任务组在其他CPU上的cfs_rq在本周期内分配不到时间而被挂起。时间是个宝贵的资源,我们必须保证它的利用率,如果这种情况下我们能将cfs_rq的时间返回一部分给任务组的话,其它有需要的cfs_rq就可以使用了。

返回时间的操作发生在调度器对任务进行dequeue操作时:

函数 start_cfs_slack_bandwidth 用来开启 slack 定时器,该定时器的回调函数是 sched_cfs_slack_timer, 该函数调用 do_sched_cfs_slack_timer 来完成解挂操作:

可以看出 slack 定时器其实是一个辅助角色,它在cfs_rq归还时间后被CFS开启,然后尽可能地提前解挂其它队列,以便提升带宽时间的利用率。

Last updated

Was this helpful?