(本文基于5.10.198版本讨论)
1. 概述
sched_avg结构体包含util_est结构体类型的字段util_est。sched_avg其他的字段因为在负载追踪算法(PELT)的计算中有重要的作用,相关的文章讨论的非常多。util_est字段的相关讨论相对较少。
关于util_est字段的作用,最常见的描述就是:
util_est计算进程休眠之前的平均占用率指标(util_avg),在进程重新唤醒的时候使用。
被长时间阻塞的进程,其被唤醒的时候,因为PELT算法在计算平均占用率(util_avg)指标的时候会进行衰减计算,导致阻塞前占用率非常高的进程,在衰减计算后变得非常小。这可能导致调度器认为这是个占用率很低的进程, 这样可能会使得与占用率密切相关的其他数值计算出现偏差。比如CPU的频率与占用率指标密切相关,偏小的占用率数值会让CPU没有及时的调节到正确的频率上。
那么,util_est的作用是这样的吗?
2. util_est结构体字段的计算
util_est结构体有两个字段。与之密切相关的还有两个宏定义。都定义在include/linux/sched.h头文件中。
util_est结构体的计算主要在进程enqueue和dequeue的时候。
在dequeue_task_fair中:
util_est_dequeue函数主要对CFS队列的util_est进行更新。
util_est_update函数主要对要出队进程的util_est结构体进行更新。
我们首先分析dequeue_task_fair函数。该函数首先确认是否开启了UTIL_EST特性:
没有开启则直接返回。
首先取出enqueued字段数值。
然后减去出队进程经过_task_util_est函数计算返回的数值:
最后将计算后的enqueued数值写回:
这里看一下_task_util_est函数的实现。
可以看到_task_util_est返回的就是util_est结构体的两个字段,enqueued和ewma比较后的较大值。
util_est_dequeue的计算完全在情理之中,任务出队的时候,CFS队列的util_est将出队任务的util_est值减去。
接下来分析一下util_est_update的实现。
util_est_update主要关注的是进程util_est数据的更新。对于enqueued字段,直接赋值为task_util的返回值,也就是se.avg.util_avg的值。关于util_est的功能,相关文章常说的将(尤其是休眠时间长的)进程在睡眠之前保存其原来的util_avg数值留待唤醒时使用,就是在这里保存的。
接下来是关于ewma字段的计算。
EWMA是Exponential Weighted Moving Average的缩写。其计算方式在util_est_update中有描述:
其实是公式的变形和化解过程。最有信息价值的是第一行和最后一行。第一行解释了ewma的计算思想,最后一行决定了代码如何实现。
首先看第一行,w是个权重值,其值是,相应的1 – w自然就是了。task_util取的是进程当前的util_avg数值,ewma(t-1)取的是之前的ewma数值。
经过权重计算的结果就是新的ewma数值。
最后一行,先看last_ewma_diff的计算,其实是enqueued和ewma的差值:
旧的(现在的)ewma先乘以权重,也就是ewma(t-1)/w,因为w是分数,所以向左位移:
再加上last_ewma_diff:
最后上面计算的和再乘以权重,因为w是分数,所以等同于乘以4。
这样就完成了两个字段数值部分的更新。因为enqueued字段的最高位标识该字段是否设置后有改动,置1为未改动,置0为改动。
这里刚刚设置完毕,将其置1。结束util_est_update的执行。
相比于util_est_dequeue,util_est_enqueue的操作只关注CFS队列的util_est字段的更新。
首先获取现在的enqueued数值:
然后加上入队进程的util_est数值:
最后更新enqueued字段:
3. util_est结构体的应用
util_est在调度器中得到充分应用的有cpu_util_next和cpu_util两个函数。
cpu_util_next函数的原型为:
其功能是预测如果进程p迁移到dst_cpu的话,cpu的平均占用率会是多少,预测值作为返回值返回。
下面我们看一下这个函数的实现,尤其是对util_est结构体数据的利用。
如果进程就在参数cpu上,迁移到另一颗cpu,那么直接用CFS队列的平均占用率(util_avg)指标自减去任务p的平均占用率(util_avg)。
如果如果进程既不在参数cpu上,也不在目标cpu上,且评估的就是目标cpu在任务迁移后的平均占用率,则直接用现在CFS队列的占用率数据加上新入队的任务的平均占用率即可。
下面的计算涉及到了util_est。
首先确认UTIL_EST特性的开启。
其次先读取现在CFS的enqueued数值,保存在变量util_est中。
如果要评估的就是目标cpu,则直接在util_est的基础上累加迁移进程p的util_est数据(_task_util_est前面读过了,这里不再分析),最后将util和util_est做对比,去较大者保存在util变量中。
当然,无论如何,估计的平均占用率是不能超过cpu的名义算力的,所以在名义算力和计算的util之间做比较,较小者作为cpu_util_next的计算结果返回。
cpu_util_next函数非常重要的一个应用是在进程唤醒的时候。在开启了EAS特性的内核中,调度器在评价进程放置在可选的不同CPU上后的能耗比,选择最佳者作为放置进程的目标。这个过程要依赖cpu_util_next函数进行计算。相关的计算逻辑在函数find_energy_effient_cpu和compute_energy中。
util_est另一处发挥作用的地方在函数cpu_util函数中。
cpu_util是个被多处调用的函数,能够获取CPU被用于CFS进程的算力。其实现如下,我们重点关注util_est在其中发挥的作用。
首先,取出CFS队列的占用率数据,保存在变量util中:
如果使能了UTIL_EST特性,则取enqueued数据,和util比较,较大者保存在变量util中。
最后,当然,数值不应该超过CPU的名义算力。
4. 总结
从上面的分析来看,第一节的问题是肯定的。util_est在进程唤醒选核的时候发挥着重要作用,让EAS特性能够通过计算预估值发挥作用。cpu占用率的计算也纳入util_est数据作为考量,使得CPU占用率相关的计算(比如CPU频率)更加准确。
标签:est,util,计算,进程,占用率,cpu,结构 From: https://www.cnblogs.com/liqb365/p/18429105