关于 PATCH-2 中 load_sum = 0,load_avg 可能不为零的测试与验证与平均负载计算的总结
根据之前分析,可以得到下面几个前提条件:
1)对于 cfs_rq 的 load_sum 来源于任务节点下下所有调度实体 se 的 load_sum * se->load.weight 之和,同时由调度实体 attch/deattch 附加或者移除 load_sum。同时在对 cfs_rq 进行 pelt 更新时,也是根据当前 cfs_rq->load.weight * contrib 累加得来。其中 contrib 对于 cfs_rq 是当前周期的时间片 delta。
cfs_rq load_sum += weight * contrib(contrib = delta)
2)对于 cfs_rq 的 load_avg 同上,由调度实体 attch/deattch 附加或者移除 load_avg。同时在对 cfs_rq 进行 pelt 更新时,load_avg = load_sum / divider。(cfs_rq 的 load_sum 等于 cfs_rq->load.weight * contrib)。
cfs_rq load_avg = load_sum / divider(load_sum = \Sum weight * delta)
从1)2)可知道当任务满载运行则负载 load_avg 越接近其权重值。
3)对于调度实体 se,如果 se 是 task,load_sum = 1 * contrib = delta,即任务的 load_sum 等于时间片的累积。
se load_sum += delta
4)对于调度实体 se,如果 se 是 group,load_sum = cfs_rq->weight * contrib,即任务组 se 的 load_sum 是时间片累计乘以对应 cfs_rq 权重。
se load_avg = load_sum * weight / divider
5)对于调度实体 se。如果 se 是 group,se 的权重等于:
se->load.weight = max(tg->share, tg->share * cfs_rq->avg.load_avg / tg->load_avg + cfs_rq->avg.load_avg)
接下来看广播负载期间 cfs_rq 和 se 的 load_sum 和 load_avg 计算:
static inline void
update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq)
{
long delta_avg, running_sum, runnable_sum = gcfs_rq->prop_runnable_sum; ----------------------(1)
unsigned long runnable_load_avg, load_avg;
u64 runnable_load_sum, load_sum = 0;
s64 delta_sum;
if (!runnable_sum)
return;
gcfs_rq->prop_runnable_sum = 0;
if (runnable_sum >= 0) { --------------------------------------------------------------------(2)
/*
* Add runnable; clip at LOAD_AVG_MAX. Reflects that until
* the CPU is saturated running == runnable.
*/
runnable_sum += se->avg.load_sum;
runnable_sum = min(runnable_sum, (long)LOAD_AVG_MAX);
} else { ------------------------------------------------------------------------------------(3)
/*
* Estimate the new unweighted runnable_sum of the gcfs_rq by
* assuming all tasks are equally runnable.
*/
if (scale_load_down(gcfs_rq->load.weight)) {
load_sum = div_s64(gcfs_rq->avg.load_sum,
scale_load_down(gcfs_rq->load.weight)); -----------------------------------------(4)
}
/* But make sure to not inflate se's runnable */
runnable_sum = min(se->avg.load_sum, load_sum);
}
/*
* runnable_sum can't be lower than running_sum
* As running sum is scale with CPU capacity wehreas the runnable sum
* is not we rescale running_sum 1st
*/
running_sum = se->avg.util_sum /
arch_scale_cpu_capacity(NULL, cpu_of(rq_of(cfs_rq)));
runnable_sum = max(runnable_sum, running_sum); --------------------------------------------(5)
load_sum = (s64)se_weight(se) * runnable_sum; ----------------------------------------------(6)
load_avg = div_s64(load_sum, LOAD_AVG_MAX);
delta_sum = load_sum - (s64)se_weight(se) * se->avg.load_sum; ------------------------------(7)
delta_avg = load_avg - se->avg.load_avg; ---------------------------------------------------(8)
se->avg.load_sum = runnable_sum; -----------------------------------------------------------(9)
se->avg.load_avg = load_avg; ---------------------------------------------------------------(10)
add_positive(&cfs_rq->avg.load_avg, delta_avg); --------------------------------------------(11)
add_positive(&cfs_rq->avg.load_sum, delta_sum); --------------------------------------------(12)
runnable_load_sum = (s64)se_runnable(se) * runnable_sum;
runnable_load_avg = div_s64(runnable_load_sum, LOAD_AVG_MAX);
delta_sum = runnable_load_sum - se_weight(se) * se->avg.runnable_load_sum;
delta_avg = runnable_load_avg - se->avg.runnable_load_avg;
se->avg.runnable_load_sum = runnable_sum;
se->avg.runnable_load_avg = runnable_load_avg;
if (se->on_rq) {
add_positive(&cfs_rq->avg.runnable_load_avg, delta_avg);
add_positive(&cfs_rq->avg.runnable_load_sum, delta_sum);
}
}
(1)其中 gcfs_rq->prop_runnable_sum 来自于下一层某个 se 移除或添加到 cfs_rq 时的 load_sum,也是在这一层需要广播到 cfs_rq 的 load_sum。
(2)如果是 attach,那么此时将当前级别的 se load_sum 和广播的 load_sum 相加得到一个附加后的负载总和,同时根据 pelt 算法可知,单个 se 的 load_sum 最大为 LOAD_AVG_MAX,我们不能超过该值。
(3)如果是 deattach,那么此时则是在当前级别移除相应的 load_sum,移除多少呢?首先判断来源处的 cfs_rq 是否还有 weight 存在。因为对于 cfs_rq 的权重来自于节点下所有 se 的累加,所以如果权重为零说明没有任务了那么此时只需要将其对应的 se 标记为 runnable_sum = 0,说明 se 没有任何负载总和。那么如果有权重,我们就直接取它当前 load_sum 为我们 se 的 load_sum 即可,保证了 cfs_rq 和 se load_sum 同步,通过上面 4)可知, cfs_rq 的 load_sum 是权重与时间片累积的相乘所以这里除以权重得到 se 真实的 load_sum。
(5)如果瞬时利用率存在我们要用刚才的 runnable_sum 和 running_sum 取最大值,原因为对于正在运行的任务顺势负载我们不能直接移除,其中 running_sum 计算方式有
util_sum = contrib * 1 << 10(其中 1 << 10 是默认设置, contrib 同 load_sum 一致来源于 delta)
所以这里直接使用 util_sum / 1 << 10 得到 running_sum ,使其负载总和范围在 0 到 LOAD_AVG_MAX 之间。
(6)用于 cfs_rq 的 load_sum 计算,所以这里需要将 se 的 load_sum 乘以权重得到 cfs_rq 的 load_sum。
(7)新的 runable_sum 减去原来 se 上的 load_sum *weight 则可以得到需要在对应 cfs_rq 上需要移除或者添加的 load_sum。
(8)同理得到 load_sum。
(9)(10)将新算出来的 load_sum 和 load_avg 设置到 se 上。
(11)(12)根据(7)(8)计算出来的差值从 cfs_rq 上添加或移除相应的值。
下面是一组正常运行测试的负载打印:
[ 718.082948] xxxxx: old ffff98d18177e600 cpu 15:load_sum 861433 load_avg 18 tg_load_avg_contrib 5 delta_sum 47253504 delta_avg 1006 load_avg 1024
[ 718.086925] xxxxx: new ffff98d18177e600 cpu 15:load_sum 48114937 load_avg 1024 tg_load_avg_contrib 5
[ 719.091621] xxxxx: old ffff98d18177e600 cpu 15:load_sum 618269 load_avg 13 tg_load_avg_contrib 0 delta_sum -617472 delta_avg -13 load_avg 0
[ 719.096940] xxxxx: new ffff98d18177e600 cpu 15:load_sum 797 load_avg 0 tg_load_avg_contrib 0
可以看到其中一组,在更新负载前 cfs_rq load_sum 等于 618269 ,load_avg 13,按照上述上述公式 618269 / 47742 =12 大致是等于 13 的。符合pelt 公式。
广播负载后该 cfs_rq load_sum 等于 797,load_avg 为 0在,其差值正是(7)(8)计算得来。
下面是一组触发 bug 的负载打印:
[ 10.764182] msleep.sh (185) used greatest stack depth: 12960 bytes left
[ 10.765054] xxxxx: old ffff9636823d6200 cpu 6:load_sum 275565 load_avg 5 tg_load_avg_contrib 5 delta_sum -275456 delta_avg -5 load_avg 0
[ 10.766152] xxxxx: old ffff9636823d5e00 cpu 5:load_sum 2146304 load_avg 45 tg_load_avg_contrib 44 delta_sum -2146304 delta_avg -45 load_avg 0
[ 10.772207] xxxxx: new ffff9636823d6200 cpu 6:load_sum 109 load_avg 0 tg_load_avg_contrib 5
[ 10.776383] xxxxx: new ffff9636823d5e00 cpu 5:load_sum 0 load_avg 0 tg_load_avg_contrib 44
===================================Wed Mar 22 16:28:26 UTC 2023============================================
cfs_rq[2]:/zy_test_l1_32/zy_test_l2_32
.tg_load_avg_contrib : 1012
.tg_load_avg : 1012
cfs_rq[2]:/zy_test_l1_32
.tg_load_avg_contrib : 32
.tg_load_avg : 76
^C*************************
do usetup, exit
*************************
可以看到被遗留的负载是 76 -32 = 44,上述打印中 cpu 5 上 load_sum 从 109 变为 0 则按照 patch 描述触发移除 cfs_rq list 导致其负载无法移除,tg_load_avg_contrib = 44 符合遗留负载量。
通过大量测试发现,没有 load_sum = 0 ,load_avg != 0 的情况,按照实际 update_tg_cfs_runnable 执行逻辑也可以看到最多只会出现 load_sum > 0 ,load_avg = 0 的情况。这种时候按照 load_avg = weight * load_sum / divider 计算的话,当 load_sum * weight < LOAD_AVG_MAX 也是符合 load_avg = 0 的情况,不会出现 load_sum = 0,load_avg != 0 的情况。按照 patch 的方式可以保证当 load_avg = 0 时,load_sum 也会一定等于 0,那么此时可以将 cfs_rq list 移除不参与负载更新。因为如果 load_sum > 0, load_avg = 0 时,在下一次复杂更新时不会做任何负载贡献,此时这个更新操作没有意义,通过同步 load_avg 和 load_sum 可以少去不必要的更新。