实时组调度

0. 警告

随意调整这些设置可能会导致系统不稳定。这些旋钮仅供 root 用户使用,并且假设 root 用户知道自己在做什么。

最值得注意的是

  • sched_rt_period_us 中非常小的值,当周期小于可用的高分辨率定时器分辨率或处理预算刷新本身所需的时间时,可能会导致系统不稳定。

  • sched_rt_runtime_us 中非常小的值,当运行时太小时,系统难以取得进展(注意:迁移线程和 kstopmachine 都是实时进程)。

1. 概述

1.1 问题

实时调度完全是为了确定性,一个组必须能够依赖于带宽量(例如,CPU 时间)是恒定的。为了调度多个实时任务组,必须为每个组分配固定的可用 CPU 时间份额。如果没有最低保证,实时组显然可能会不足。模糊的上限是没用的,因为它不能被依赖。这只剩下唯一的固定部分。

1.2 解决方案

通过指定在给定周期内可以花费多少时间运行来划分 CPU 时间。我们为每个实时组分配这个“运行时间”,其他实时组将不允许使用它。

任何未分配给实时组的时间将用于运行正常优先级任务 (SCHED_OTHER)。任何未使用的已分配运行时间也将由 SCHED_OTHER 占用。

让我们考虑一个例子:一个固定帧率的实时渲染器必须每秒交付 25 帧,这产生了每帧 0.04 秒的周期。现在假设它还必须播放一些音乐并响应输入,因此大约有 80% 的 CPU 时间用于图形。那么,我们可以给这个组一个 0.8 * 0.04 秒 = 0.032 秒的运行时间。

这样,图形组将拥有 0.04 秒的周期和 0.032 秒的运行时间限制。现在,如果音频线程需要每 0.005 秒重新填充 DMA 缓冲区,但只需要大约 3% 的 CPU 时间来完成此操作,则可以使用 0.03 * 0.005 秒 = 0.00015 秒。因此,可以安排此组的周期为 0.005 秒,运行时间为 0.00015 秒。

剩余的 CPU 时间将用于用户输入和其他任务。由于实时任务已明确分配了执行其任务所需的 CPU 时间,因此可以消除图形或音频中的缓冲区欠载。

注意:上面的例子尚未完全实现。我们仍然缺少 EDF 调度器,以使非均匀周期可用。

2. 接口

2.1 系统范围设置

系统范围设置在 /proc 虚拟文件系统下配置

/proc/sys/kernel/sched_rt_period_us

相当于 100% CPU 带宽的调度周期。

/proc/sys/kernel/sched_rt_runtime_us

实时调度可以使用的全局时间限制。它始终小于或等于 period_us,因为它表示从 period_us 为实时任务分配的时间。即使未启用 CONFIG_RT_GROUP_SCHED,也会限制为实时进程保留的时间。启用 CONFIG_RT_GROUP_SCHED=y 时,它表示所有实时组可用的总带宽。

  • 时间以 us 为单位指定,因为接口是 s32。这给出了从 1us 到大约 35 分钟的操作范围。

  • sched_rt_period_us 的取值范围为 1 到 INT_MAX。

  • sched_rt_runtime_us 的取值范围为 -1 到 sched_rt_period_us。

  • 运行时为 -1 指定 runtime == period,即没有限制。

2.2 默认行为

sched_rt_period_us (1000000 或 1 秒) 和 sched_rt_runtime_us (950000 或 0.95 秒) 的默认值。这给 SCHED_OTHER (非 RT 任务) 留出 0.05 秒。选择这些默认值是为了让失控的实时任务不会锁定机器,而是留出一点时间来恢复它。将运行时设置为 -1,您将获得旧的行为。

默认情况下,所有带宽都分配给根组,新组从 /proc/sys/kernel/sched_rt_period_us 获取周期,运行时间为 0。如果要将带宽分配给另一个组,请减少根组的带宽并将部分或全部差值分配给另一个组。

实时组调度意味着您必须在组接受实时任务之前为组分配一部分总 CPU 带宽。因此,您将无法以除 root 之外的任何用户身份运行实时任务,即使该用户有权以实时优先级运行进程!

2.3 任务分组的基础

启用 CONFIG_RT_GROUP_SCHED 可让您显式地为任务组分配真实的 CPU 带宽。

这使用 cgroup 虚拟文件系统和 “<cgroup>/cpu.rt_runtime_us” 来控制为每个控制组保留的 CPU 时间。

有关使用控制组的更多信息,您还应该阅读控制组

会根据以下限制检查组设置,以保持配置可调度

Sum_{i} runtime_{i} / global_period <= global_runtime / global_period

现在,这可以简化为以下内容(但请参阅未来计划)

Sum_{i} runtime_{i} <= global_runtime

3. 未来计划

正在进行的工作是使每个组的调度周期 (“<cgroup>/cpu.rt_period_us”) 也可配置。

周期的约束是子组的周期必须小于或等于其父组。但实际上它还不是很实用,因为它在没有截止时间调度的情况下容易出现饥饿。

考虑两个同级组 A 和 B;两者都具有 50% 的带宽,但 A 的周期是 B 的两倍。

  • 组 A:period=100000us, runtime=50000us

    • 这在每 0.1 秒运行一次 0.05 秒

  • 组 B:period= 50000us, runtime=25000us

    • 这在每 0.1 秒运行两次 0.025 秒(或每 0.05 秒一次)。

这意味着当前 A 中的 while (1) 循环将运行 B 的整个周期,并且可以使 B 的任务(假设它们的优先级较低)在整个周期内处于饥饿状态。

下一个项目将是 SCHED_EDF (最早截止时间优先调度),将完整的截止时间调度引入 Linux 内核。对上述组进行截止时间调度并将周期结束视为截止时间将确保它们都获得分配的时间。

实现 SCHED_EDF 可能需要一段时间才能完成。优先级继承是最大的挑战,因为当前的 Linux PI 基础结构是为有限的静态优先级级别 0-99 而设计的。使用截止时间调度,您需要执行截止时间继承(因为优先级与截止时间增量(截止时间 - 现在)成反比)。

这意味着整个 PI 机制都必须重新设计 - 这是我们拥有的最复杂的代码之一。