Schedutil¶
注意
所有这些都假设频率和工作能力之间存在线性关系,我们知道这是有缺陷的,但这是最佳可行的近似值。
PELT (按实体负载跟踪)¶
通过 PELT,我们跟踪各种调度器实体中的一些指标,从单个任务到任务组切片再到 CPU 运行队列。作为此基础,我们使用指数加权移动平均 (EWMA),每个周期 (1024us) 都会衰减,使得 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 运行队列),这反映了它们在恢复运行时预期的贡献。
使用此方法,我们跟踪 2 个关键指标:“running”(运行)和“runnable”(可运行)。“运行”反映实体在 CPU 上花费的时间,而“可运行”反映实体在运行队列上花费的时间。当只有一个任务时,这两个指标是相同的,但是一旦存在 CPU 争用,“运行”将减少以反映每个任务在 CPU 上花费的时间比例,而“可运行”将增加以反映争用量。
有关更多详细信息,请参阅: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; if available and turbo enabled
f_max := { 1C-turbo; if turbo enabled
P0; otherwise
f_cur
r_dvfs := min( 1, ----- )
f_max
我们选择 4C 睿频而不是 1C 睿频,以使其稍微更可持续。
r_cpu 被确定为当前 CPU 的最高性能级别与系统中任何其他 CPU 的最高性能级别之比。
r_tot = r_dvfs * r_cpu
结果是,上述“运行”和“可运行”指标变得与 DVFS 和 CPU 类型无关。IOW. 我们可以在 CPU 之间传输和比较它们。
有关更多详细信息,请参阅
kernel/sched/pelt.h:update_rq_clock_pelt()
arch/x86/kernel/smpboot.c:“APERF/MPERF 频率比率计算。”
容量感知调度:“1. CPU 容量 + 2. 任务利用率”
UTIL_EST¶
由于周期性任务在休眠时平均值会衰减,即使在运行时它们的预期利用率也相同,但它们在再次运行后会出现(DVFS)上升。
为了缓解这种情况(默认启用的选项),UTIL_EST 使用出队时的“运行”值驱动无限脉冲响应 (IIR) EWMA——当它是最高时。UTIL_EST 会进行滤波以立即增加,并且仅在减少时衰减。
还会维护一个更广泛的运行队列总和(可运行任务的总和)
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 运行队列的“运行”指标,根据上述内容,这是 CPU 的频率不变利用率估计。由此,我们计算出所需的频率,例如
max( running, util_est ); if UTIL_EST
u_cfs := { running; otherwise
clamp( u_cfs + u_rt , u_min, u_max ); if UCLAMP_TASK
u_clamp := { u_cfs + u_rt; otherwise
u := u_clamp + u_irq + u_dl; [approx. see source for more detail]
f_des := min( f_max, 1.25 u * f_max )
XXX IO-等待:当更新是由于从 IO 完成唤醒任务时,我们会提升上面的“u”。
然后,此频率用于选择 P 状态/OPP 或直接将其添加到 CPPC 样式的硬件请求中。
XXX:截止时间任务(零星任务模型)允许我们计算满足工作负载所需的硬 f_min。
由于这些回调直接来自调度器,因此 DVFS 硬件交互应该是“快速”和非阻塞的。当硬件交互缓慢且昂贵时,Schedutil 支持对 DVFS 请求进行速率限制,这会降低效率。
有关更多信息,请参阅:kernel/sched/cpufreq_schedutil.c
注释¶
在低负载场景中(DVFS 最相关),“运行”数值将密切反映利用率。
在饱和场景中,任务移动会导致一些瞬时下降,假设我们有一个被 4 个任务饱和的 CPU,那么当我们将一个任务迁移到空闲 CPU 时,旧 CPU 的“运行”值为 0.75,而新 CPU 将获得 0.25。这是不可避免的,并且时间推移会纠正此问题。XXX 我们是否仍然保证由于没有空闲时间而导致 f_max?
以上大部分内容都是为了避免 DVFS 下降,以及独立的 DVFS 域在负载转移时必须重新学习/上升。