CFS对vruntime的迭代更新

cfs调度器对vruntime的迭代更新是调度器实现公平调度策略的核心机制,下面我们来分析vruntime的迭代更新机制。

1. 调度实体描述

vruntime是调度实体的成员,所以我们先明确调度实体的相关描述:
CFS对vruntime的迭代更新
(1)load用以追踪进程组和进程的权重信息,进而影响vruntime的增加速度。
(2)虚拟运行时间,用于cfs决策选择哪一个进程作为即将要运行的进程。
(3)当前调度实体隶属于的队列。
(4)进程组的就绪队列,非空表示进程组,NULL表示进程。

2. 调度实体vrntime的设置

2.1 vruntime的初始化
CFS对vruntime的迭代更新
(1)继承父进程的虚拟运行时间。
(2)子进程的虚拟运行时间取cfs_rq->min_vruntime+sched_vslice(cfs_rq, se)和父进程当前的虚拟运行时间两者中较大者。
(3)如果要求子进程先调度,但这个时候子进程虚拟运行时间比较大,那么交换当前运行进程和新建子进程的虚拟运行时间并设置当前运行进程的可抢占标志。
(4)新建子进程虚拟运行时间中减掉当前就绪队列cfs_rq的最小虚拟运行时间。这样做是因为后面enqueue的时候会再加上唤醒的cfs_rq的最小虚拟运行时间,这样新建的进程的初始vruntime总是比cfs_rq->vruntime大约大sched_vslice(cfs_rq, se),这样新进程总是可以比较容易地抢占别的进程。
CFS对vruntime的迭代更新
place_entity设置调度实体se的虚拟运行时间,此时调用place_entity时的initial参数设置为1, 以便用sched_vslice_add计算初始的虚拟运行时间vruntime, 内核以这种方式确定了进程在延迟周期中所占的时间份额, 并转换成虚拟运行时间. 这个是调度器最初向进程欠下的债务。
如果休眠进程的vruntime保持不变, 而其他运行进程的 vruntime一直在推进, 那么等到休眠进程终于唤醒的时候, 它的vruntime比别人小很多, 会使它获得长时间抢占CPU的优势, 其他进程就要饿死了. 这显然是另一种形式的不公平,因此CFS是这样做的:在休眠进程被唤醒时重新设置vruntime值,以min_vruntime值为基础,给予一定的补偿,但不能补偿太多。这个重新设置其虚拟运行时间的工作就是通过place_entity来完成的, 另外新进程创建完成后, 也是通过place_entity完成其虚拟运行时间vruntime的设置的。

2.2 vruntime的更新
在update_curr中我们会调用:
curr->vruntime += calc_delta_fair(delta_exec, curr)
来更新当前调度实体的虚拟运行时间vruntime:
CFS对vruntime的迭代更新
CFS对vruntime的迭代更新
vruntime的增量跟se->load有关系,根据__calc_delta计算逻辑化简下来:
curr->vruntime += (delat_exec * 1024)/ curr->load->weight
由此可知调度实体的权重值决定着调度实体的虚拟运行时间的增加速度。下面我们来分析调度实体动态权重值的变化。

3. 调度实体权重设置

前文讲到调度实体的动态权重值load->weight决定着调度实体虚拟运行时间vruntime的增加速度,下面分别分析task se和group se的load变化。可以看到,进程的权重值是通过其优先级查表算得的,是不变的,进程组的权重值是动态变化的。
3.1 task se的load设置
set_user_nice->set_load_weight->reweight_task
__setscheduler_params->set_load_weight->reweight_task
CFS对vruntime的迭代更新
(1)根据优先级prio获取权重值:
CFS对vruntime的迭代更新
(2)设置进程的权重值weight
reweight_entity–>update_load_set
static inline void update_load_set(struct load_weight *lw, unsigned long w)
{
lw->weight = w;
lw->inv_weight = 0;
}
3.2 group se的load设置
当我们dequeue task、enqueue task以及task tick的时候会通过update_cfs_group()函数更新group se的权重信息:
CFS对vruntime的迭代更新
(1)获取group se自己的cfs_rq。
(2)如果是task se,那么直接return。
(3)根据当前group的cfs_rq计算新的权重值。
(4)计算runnable_weight。
(5)更新group se的权重值为shares。
reweight_entity–>update_load_set
calc_group_shares()根据当前group cfs_rq负载情况计算新的权重。
CFS对vruntime的迭代更新
化简下来group se的权重计算公式是:
CFS对vruntime的迭代更新
tg->load_avg指所有的group cfs_rq负载贡献和。cfs_rq->tg_load_avg_contrib是指该group cfs_rq已经向tg->load_avg贡献的负载。因为tg是一个全局共享变量,多个CPU可能同时访问,为了避免严重的资源抢占。group cfs_rq负载贡献更新的值并不会立刻加到tg->load_avg上,而是等到负载贡献大于tg_load_avg_contrib一定差值后,再加到tg->load_avg上。例如,2个CPU的系统。CPU0上group cfs_rq初始值tg_load_avg_contrib为0,当group cfs_rq每次定时器更新负载的时候并不会访问tg变量,而是等到group cfs_rq的负载grp->avg.load_avg大于tg_load_avg_contrib很多的时候,这个差值达到一个数值(假设是2000),才会更新tg->load_avg为2000。然后,tg_load_avg_contrib的值赋值2000。又经过很多个周期后,grp->avg.load_avg和tg_load_avg_contrib的差值又等于2000,那么再一次更新tg->load_avg的值为4000。这样就避免了频繁访问tg变量。
这里我们暂时只需要了解到group se的动态负载跟组权重是成正比关系的。这样组权重越大,group se的虚拟运行时间增加越缓慢。