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”。 “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;  if available and turbo enabled
f_max := { 1C-turbo;  if turbo enabled
           P0;        otherwise

                  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.”

  • 容量感知调度:”1. CPU 容量 + 2. 任务利用率”

UTIL_EST

由于定期任务在睡眠时其平均值会衰减,即使在运行时其预期利用率将相同,但在再次运行后,它们也会遭受(DVFS)斜坡上升。

为了缓解此问题(默认启用选项),UTIL_EST 使用无限脉冲响应 (IIR) EWMA,并在取消排队时使用 “running” 值(此时最高)。 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 运行队列的 “running” 指标,根据上述内容,它是 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-wait:当更新是由于来自 IO 完成的任务唤醒时,我们会提升上面的 ‘u’。

然后,此频率用于选择 P-state/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 域在负载变化时必须重新学习/加速。