3. 实时调度类(RT)基本原理:基于优先级的调度策略
实时调度类(RT)是 Linux 内核中为硬实时和软实时任务提供确定性调度的核心机制。其根本思想是通过优先级来确保关键任务在截止时间前获得 CPU 资源。本章深入剖析基于优先级的调度策略,详细对比 SCHED_FIFO 与 SCHED_RR 的差异,并阐明静态优先级与动态优先级的工作机制。
3.1 基于优先级的调度策略
在 Linux 实时调度中,每个实时任务都被赋予一个唯一的优先级数值(数值越小,优先级越高)。调度器在每次调度点(如任务阻塞、时间片耗尽、新任务唤醒)发生时,遍历所有处于 TASK_RUNNING 状态的实时任务,选择优先级最高的任务投入运行。
这种策略的数学本质是:∀ t ∈ T_ready, priority(t) = min(priority(t_i)),即调度器总是选择优先级数值最小的任务。
SCHED_FIFO 和 SCHED_RR),数值越小优先级越高。普通进程(SCHED_OTHER)的 nice 值映射到 100~139,始终低于任何实时任务。
3.2 SCHED_FIFO 与 SCHED_RR 的区别
Linux 内核提供了两种实时调度策略:SCHED_FIFO(先进先出)和 SCHED_RR(轮转)。两者都基于静态优先级,但在时间片管理上存在根本差异。
| 特性 | SCHED_FIFO | SCHED_RR |
|---|---|---|
| 时间片 | 无时间片限制,可无限运行直到主动让出或阻塞 | 有固定时间片(默认 100ms),耗尽后自动让出 |
| 同优先级调度 | 按队列顺序执行,后唤醒的任务排在队尾 | 时间片轮转,每个任务轮流获得 CPU |
| 抢占条件 | 仅被更高优先级任务或中断抢占 | 同优先级任务时间片到期后自动切换 |
| 适用场景 | 计算密集型、需独占 CPU 的关键任务 | 需要公平共享 CPU 的多个同优先级实时任务 |
| 饥饿风险 | 高优先级 FIFO 任务可能饿死低优先级任务 | 同优先级任务间不会饥饿,但低优先级仍可能被饿死 |
3.2.1 SCHED_FIFO 详细行为
当使用 SCHED_FIFO 时,调度器维护一个按优先级分组的就绪队列。每个优先级对应一个 FIFO 队列。任务被唤醒时,插入到其优先级队列的尾部。调度器总是从最高优先级的非空队列头部取出任务执行。
关键行为规则:
- 任务一旦获得 CPU,将一直运行直到:主动调用
sched_yield()、被更高优先级任务抢占、进入阻塞状态(如等待 I/O、睡眠)、被信号杀死。 - 如果任务被更高优先级任务抢占,它会被放回原优先级队列的头部,以便抢占结束后立即恢复执行。
- 多个同优先级 FIFO 任务之间不会自动切换,除非主动让出。
// 设置 SCHED_FIFO 策略的示例代码
#include <sched.h>
#include <stdio.h>
int main() {
struct sched_param param;
param.sched_priority = 80; // 实时优先级 80
// 设置当前进程为 SCHED_FIFO
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler failed");
return 1;
}
// 确认当前调度策略
int policy = sched_getscheduler(0);
printf("Current policy: %d (SCHED_FIFO=%d)\n", policy, SCHED_FIFO);
return 0;
}
3.2.2 SCHED_RR 详细行为
SCHED_RR 在 SCHED_FIFO 的基础上增加了时间片轮转机制。每个 RR 任务拥有一个固定的时间片(默认 100ms,可通过 sched_rr_get_interval() 查询)。
关键行为规则:
- 任务运行直到时间片耗尽、主动让出、被抢占或阻塞。
- 时间片耗尽后,任务被放回同优先级队列的尾部,调度器选择队列头部的下一个任务运行。
- 如果任务被更高优先级任务抢占,时间片保留,抢占结束后继续消耗剩余时间片。
- 同优先级任务之间实现公平的 CPU 时间分配。
// 查询 SCHED_RR 时间片大小的示例
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
int main() {
struct sched_param param;
param.sched_priority = 50;
sched_setscheduler(0, SCHED_RR, ¶m);
struct timespec tp;
if (sched_rr_get_interval(0, &tp) == 0) {
printf("SCHED_RR time slice: %ld sec %ld ns\n",
tp.tv_sec, tp.tv_nsec);
}
return 0;
}
3.3 静态优先级与动态优先级机制
Linux 实时调度类采用静态优先级机制,即任务的优先级在创建时设定,除非显式调用 sched_setparam() 或 sched_setscheduler(),否则不会改变。这与普通进程(SCHED_OTHER)使用的动态优先级(基于 nice 值和运行历史调整)形成鲜明对比。
| 特性 | 静态优先级(RT 任务) | 动态优先级(普通任务) |
|---|---|---|
| 修改方式 | 仅通过系统调用显式修改 | 内核根据运行行为自动调整 |
| 优先级范围 | 0~99(实时优先级) | 100~139(对应 nice -20~19) |
| 老化机制 | 无老化,优先级永不衰减 | 有老化,长时间运行的任务优先级会降低 |
| 确定性 | 高,行为完全可预测 | 低,受负载和历史影响 |
| 适用场景 | 硬实时、确定性要求高的任务 | 交互式、批处理等非实时任务 |
3.3.1 静态优先级的工作原理
静态优先级存储在任务描述符 task_struct 的 rt_priority 字段中。调度器在每次调度时直接读取该字段,不进行任何计算或调整。这种设计的优势在于:
- 确定性:任务的响应时间只取决于其优先级和系统中更高优先级任务的行为,不受运行历史影响。
- 低开销:调度决策仅需比较整数,无需计算动态权重。
- 可分析性:系统设计者可以通过优先级分配精确预测最坏情况响应时间(WCRT)。
kernel/sched/rt.c 中,pick_next_task_rt() 函数通过 sched_find_first_bit() 位图操作快速找到最高优先级的非空队列,时间复杂度为 O(1)。
3.3.2 动态优先级在 RT 中的角色
虽然 RT 任务本身使用静态优先级,但 Linux 内核在以下场景中引入了动态优先级机制来辅助实时调度:
- 优先级继承(Priority Inheritance):当低优先级 RT 任务持有高优先级任务需要的锁时,低优先级任务临时继承高优先级,防止优先级反转。
- 优先级提升(Priority Boost):在特定事件(如唤醒等待队列)中,内核临时提升任务的优先级以避免活锁。
- RT 节流(RT Throttling):当 RT 任务占用 CPU 超过
/proc/sys/kernel/sched_rt_runtime_us限制时,内核强制降低其优先级以保障普通任务的运行。
// 查看和修改 RT 任务优先级的系统调用
#include <sched.h>
#include <stdio.h>
void print_rt_priority(pid_t pid) {
struct sched_param param;
int policy;
// 获取当前调度参数
if (sched_getparam(pid, ¶m) == -1) {
perror("sched_getparam");
return;
}
policy = sched_getscheduler(pid);
printf("PID %d: policy=%d, static priority=%d\n",
pid, policy, param.sched_priority);
}
// 修改优先级(需要 root 权限)
void set_rt_priority(pid_t pid, int new_prio) {
struct sched_param param;
param.sched_priority = new_prio;
if (sched_setparam(pid, ¶m) == -1) {
perror("sched_setparam");
}
}
3.4 优先级反转与继承协议
在基于静态优先级的实时系统中,优先级反转是一个经典问题:低优先级任务持有高优先级任务需要的共享资源,导致高优先级任务被阻塞,而中等优先级任务(不依赖该资源)却可以抢占低优先级任务运行,造成高优先级任务无限期等待。
Linux 内核通过 优先级继承协议(Priority Inheritance Protocol, PIP) 来解决此问题:
- 当高优先级任务被低优先级任务持有的锁阻塞时,低优先级任务临时继承高优先级任务的优先级。
- 继承的优先级确保低优先级任务不会被中等优先级任务抢占,从而快速释放锁。
- 锁释放后,低优先级任务恢复其原始优先级。
kernel/locking/rtmutex.c 中,通过 rt_mutex 机制支持。这是 PREEMPT_RT 内核的关键组成部分。
// 优先级继承的伪代码逻辑
// 当任务 T1(优先级 80)尝试获取 T2(优先级 30)持有的锁时:
if (lock->owner != NULL && lock->owner->prio < current->prio) {
// 临时提升锁持有者的优先级
lock->owner->prio = current->prio; // 继承优先级
// 锁持有者现在以优先级 80 运行,不会被中等优先级任务抢占
}
// 当锁释放时:
lock->owner->prio = lock->owner->original_prio; // 恢复原始优先级
通过优先级继承,系统保证了实时任务的响应时间可预测性,这是静态优先级调度能够用于硬实时系统的关键保障机制。