上一节在讲任务组中 se
的时间计算时,有一个点我们没有深究:即 cfsrq 的总权重如何计算?在介绍 struct task_group
时,有个字段叫 shares
:
/* file: kernel/sched/sched.h */
struct task_group {
#ifdef CONFIG_FAIR_GROUP_SCHED
unsigned long shares;
#endif
};
正常来说,任务组的总体权重确定后,各个cfsrq按照各自的比例进行二次分配即可。如下图所示:
图中任务组的权重为1024,SE_R0
中三个se的权重总计为3072,而SE_R1
中两个se的权重也为3072,因此SE_R0
与SE_R1
平分任务组的权重,各占512. 由此可得计算公式:设tg表示整个任务组,grq表示任务组中分配到某个CPU上的cfsrq, ge表示指向该cfsrq的se, 则 se.weight = tg.shares * grq.weight/sum(grq.weight)
, 其中分母 sum(grq.weight)
表示任务组中所有 cfsrq 的权重之和。
计算权重的函数是 calc_group_shares()
:
/* file: kernsl/sched/fair.c */
static long calc_group_shares(struct cfs_rq *cfs_rq) {
long tg_weight, tg_shares, load, shares;
struct task_group *tg = cfs_rq->tg;
tg_shares = READ_ONCE(tg->shares);
load = max(scale_load_down(cfs_rq->load.weight), cfs_rq->avg.load_avg);
tg_weight = atomic_long_read(&tg->load_avg);
/* Ensure tg_weight >= load */
tg_weight -= cfs_rq->tg_load_avg_contrib;
tg_weight += load;
shares = (tg_shares * load);
if (tg_weight)
shares /= tg_weight;
return clamp_t(long, shares, MIN_SHARES, tg_shares);
}
函数中并没有使用前面我们总结出的公式来进行计算,因为同时访问所有CPU上的cfsrq是很昂贵的操作,需要对数据操作进行同步,因此这里使用的是任务组的平均负载(tg->loadavg)来替代权重。后续文章会详细讲解负载的计算方法,通常情况下平均负载与任务组的权重几乎相同,因此读者无需对该方法的具体实现感到困扰,内核的工程师们只是使用了一个更高效的方式来对前文中我们讨论的思路进行实现。
用户可以对任务组的权重进行设置,系统最终会调用如下方法来完成操作:
/* file: kernel/sched/fair.c */
int sched_group_set_shares(struct task_group *tg, unsigned long shares) {
int i;
/* 设置任务组的 shares */
tg->shares = shares;
/* 设置好任务组的总体 shares 之后,更新任务组在每个CPU上的se的权重信息 */
for_each_possible_cpu(i) {
struct rq *rq = cpu_rq(i);
struct sched_entity *se = tg->se[i];
/* 从当前se开始,沿着parent路径一路更新所有上层任务组的权重信息 */
for_each_sched_entity(se) {
update_load_avg(cfs_rq_of(se), se, UPDATE_TG);
update_cfs_group(se);
}
}
}
设置了任务组的权重之后,系统需要更新所有涉及到的任务组的权重信息,函数 update_cfs_group
会调用 calc_group_shares
计算 se 应该得到的配额,这里就不深入探讨了。