Schedutil
注意
所有这些都假设频率和工作能力之间存在线性关系,我们知道这是有缺陷的,但这是最好的可行近似。
PELT(Per Entity Load Tracking)
使用PELT,我们跟踪各种调度实体的一些指标,从单个任务到任务组切片到CPU运行队列。作为基础,我们使用指数加权移动平均(EWMA),每个周期(1024微秒)都会衰减,使得y^32 = 0.5。也就是说,最近的32毫秒贡献了一半,而历史的其余部分贡献了另一半。
具体来说:
ewma_sum(u) := u_0 + u_1*y + u_2*y^2 + ... ewma(u) = ewma_sum(u) / ewma_sum(1)
由于这本质上是一个无限几何级数的进展,结果是可组合的,也就是说ewma(A) + ewma(B) = ewma(A+B)。这一特性很关键,因为它赋予了在任务移动时重新组合平均值的能力。
请注意,被阻塞的任务仍然会对聚合数据(任务组切片和CPU运行队列)产生影响,这反映了它们在恢复运行时的预期贡献。
利用这一点,我们跟踪了两个关键指标:'running'和'runnable'。'Running'反映了实体在CPU上运行的时间,而'runnable'反映了实体在运行队列上运行的时间。当只有一个任务时,这两个指标是相同的,但一旦CPU出现争用,'running'将减少以反映每个任务在CPU上运行的时间比例,而'runnable'将增加以反映争用的程度。
更多细节请参见:kernel/sched/pelt.c
频率/CPU不变性
因为在1GHz下占用CPU 50%与在2GHz下占用CPU 50%并不相同,而在LITTLE CPU上运行50%与在big CPU上运行50%也不相同,所以我们允许架构使用两个比率来调整时间增量,一个是动态电压和频率调整(DVFS)比率,另一个是微架构比率。
对于简单的DVFS架构(其中软件完全控制),我们可以简单地计算比率:
f_cur r_dvfs := ----- f_max
对于更动态的系统,其中硬件控制DVFS,我们使用硬件计数器(如Intel APERF/MPERF,ARMv8.4-AMU)来提供这个比率。对于Intel,我们使用:
APERF f_cur := ----- * P0 MPERF 4C-turbo; 如果可用且启用了turbo f_max := { 1C-turbo; 如果启用了turbo P0; 否则 f_cur r_dvfs := min( 1, ----- ) f_max
我们选择4C turbo而不是1C turbo,以使其稍微更可持续。
r_cpu被确定为当前CPU的最高性能级别与系统中任何其他CPU的最高性能级别的比率。
r_tot = r_dvfs * r_cpu
结果是上述的'running'和'runnable'指标变得与DVFS和CPU类型无关。也就是说,我们可以在CPU之间传输和比较它们。
更多细节请参见:
- kernel/sched/pelt.h:update_rq_clock_pelt()
- arch/x86/kernel/smpboot.c:"APERF/MPERF frequency ratio computation."
- Capacity Aware Scheduling:"1. CPU Capacity + 2. Task utilization"
UTIL_EST / UTIL_EST_FASTUP
因为周期性任务在休眠时其平均值会衰减,即使在运行时它们的预期利用率是相同的,它们在再次运行时会遭受(DVFS)加速。
为了缓解这一点(默认启用选项),UTIL_EST在出队时使用'running'值驱动了一个无限冲激响应(IIR)指数加权移动平均滤波器——当它最高时。另一个默认启用选项UTIL_EST_FASTUP修改了IIR滤波器,使其在增加时立即增加,只在减少时衰减。
还维护了一个运行队列的总和(可运行任务的):
util_est := Sum_t max( t_running, t_util_est_ewma )
更多细节请参见:kernel/sched/fair.c:util_est_dequeue()
UCLAMP
可以在每个CFS或RT任务上设置有效的u_min和u_max夹紧值;运行队列为所有运行任务保留了这些夹紧值的最大聚合。
更多细节请参见:include/uapi/linux/sched/types.h
Schedutil / DVFS
每当调度程序负载跟踪更新时(任务唤醒、任务迁移、时间推移),我们调用schedutil来更新硬件DVFS状态。
基础是CPU运行队列的'running'指标,根据上述,它是CPU的频率不变的利用率估计。从这个值,我们计算出一个期望的频率,如下:
max( running, util_est ); 如果UTIL_EST u_cfs := { running; 否则 clamp( u_cfs + u_rt , u_min, u_max ); 如果UCLAMP_TASK u_clamp := { u_cfs + u_rt; 否则 u := u_clamp + u_irq + u_dl; [大致见源代码获取更多细节] f_des := min( f_max, 1.25 u * f_max )
XXX IO-wait: 当更新是由于从IO完成唤醒任务时,我们会提高'u'的值。
然后使用这个频率来选择一个P状态/OPP,或者直接将其转换为硬件的CPPC风格请求。
XXX:截止时间任务(Sporadic Task Model)允许我们计算满足工作负载所需的最低f_min。
因为这些回调直接来自调度程序,所以DVFS硬件交互应该是'快速'和非阻塞的。Schedutil支持对DVFS请求进行速率限制,以应对硬件交互缓慢和昂贵的情况,这会降低效果。
更多信息请参见:kernel/sched/cpufreq_schedutil.c
注释
- 在低负载场景中,DVFS最相关,'running'数字将会与利用率密切相关。
- 在饱和场景中,任务移动会导致一些瞬时下降,假设我们有一个CPU饱和了4个任务,然后当我们将一个任务迁移到一个空闲CPU时,旧CPU的'running'值将为0.75,而新CPU将增加0.25。这是不可避免的,时间推移会纠正这一点。XXX我们是否仍然保证f_max由于没有空闲时间?
- 上述大部分内容都是关于避免DVFS下降,以及独立的DVFS域在负载转移时需要重新学习/加速的问题。