调度器-实时篇:实现 Rate-Monotonic 与 Earliest-Deadline-First
"普通调度器追求公平,实时调度器追求确定性。
本文将深入实时调度的核心算法,
从 Rate-Monotonic 到 Earliest-Deadline-First,
为自制 OS 构建可靠的实时调度系统。"
引言:实时调度的本质需求
在普通操作系统中,调度器的目标是公平性和高吞吐量。
但在实时系统中,调度器的目标是确定性和截止时间保证。
考虑以下场景:
- 音频播放:必须在 10ms 内处理完音频数据,否则出现爆音
- 工业控制:必须在 1ms 内响应传感器信号,否则设备损坏
- 自动驾驶:必须在 100ms 内处理完摄像头数据,否则事故
实时调度的核心挑战不是'如何分配 CPU',而是'如何保证截止时间'。
本文将为自制 OS 实现一个完整的实时调度框架,
支持硬实时和软实时任务,并解决优先级反转等关键问题。
第一章:实时系统分类与特征
1.1 硬实时 vs 软实时
硬实时系统(Hard Real-Time)
- 定义:必须在截止时间前完成,否则系统失败
- 后果:设备损坏、安全事故、系统崩溃
- 示例:
- 航空航天控制系统
- 医疗设备(心脏起搏器)
- 工业自动化
软实时系统(Soft Real-Time)
- 定义:尽量在截止时间前完成,偶尔错过可接受
- 后果:性能下降、用户体验差,但系统仍可用
- 示例:
- 音频/视频播放
- 游戏引擎
- 电话会议
关键区别
| 特性 | 硬实时 | 软实时 | |——|——–|——–| | 截止时间 | 绝对必须满足 | 尽量满足 | | 错过后果 | 系统失败 | 性能下降 | | 调度算法 | 确定性算法 | 概率性算法 | | 资源预留 | 必须 | 可选 |
1.2 实时任务模型
周期性任务(Periodic Tasks)
- 特征:定期执行,有固定周期和执行时间
- 参数:
- 周期(Period, T):任务重复间隔
- 执行时间(Execution Time, C):每次执行所需 CPU 时间
- 截止时间(Deadline, D):必须完成的时间点
- 示例:传感器数据采集(每 10ms 采集一次,执行时间 1ms)
非周期性任务(Aperiodic Tasks)
- 特征:随机到达,无固定周期
- 参数:
- 到达时间(Arrival Time)
- 执行时间(Execution Time)
- 截止时间(Deadline)
- 示例:用户按键响应、网络数据包处理
调度可行性分析
对于周期性任务集,CPU 利用率必须满足特定条件:
- Rate-Monotonic:CPU 利用率 ≤ n(2^(1/n) - 1) (n 为任务数)
- Earliest-Deadline-First:CPU 利用率 ≤ 100%
1.3 实时调度的核心挑战
挑战一:截止时间保证
- 问题:如何确保任务在截止时间前完成?
- 解决方案:调度算法必须具有可调度性分析
挑战二:优先级反转
- 问题:低优先级任务持有高优先级任务需要的资源
- 解决方案:优先级继承、优先级天花板
挑战三:中断延迟
- 问题:中断处理时间影响实时任务响应
- 解决方案:中断线程化、优先级继承
挑战四:调度开销
- 问题:调度决策本身消耗 CPU 时间
- 解决方案:静态调度、预计算调度表
第二章:Rate-Monotonic Scheduling(RMS)
2.1 RMS 算法原理
核心思想
- 静态优先级:任务优先级在创建时确定,运行中不变
- 周期越短,优先级越高:Rate-Monotonic(速率单调)
- 可调度性条件:CPU 利用率 ≤ n(2^(1/n) - 1)
算法特点
| 特性 | 说明 | |——|——| | 调度类型 | 抢占式、静态优先级 | | 适用场景 | 周期性任务 | | 可调度性 | 有理论保证 | | 实现复杂度 | 低 |
可调度性分析
对于 n 个周期性任务,RMS 可调度的充要条件是:
U = Σ(Ci/Ti) ≤ n(2^(1/n) - 1)
其中:
- U:CPU 利用率
- Ci:任务 i 的执行时间
- Ti:任务 i 的周期
常见值:
- n=1:U ≤ 1.0 (100%)
- n=2:U ≤ 0.828 (82.8%)
- n=3:U ≤ 0.779 (77.9%)
- n→∞:U ≤ 0.693 (69.3%)
2.2 RMS 调度类实现
实时任务结构
// include/linux/sched.h
struct sched_rt_entity {
struct list_head run_list; /* 运行队列链表 */
unsigned long timeout; /* 超时时间 */
unsigned long time_slice; /* 时间片 */
int prio; /* 优先级 */
};
RMS 调度类
// kernel/sched/rt.c
static const struct sched_class rt_sched_class = {
.next = &fair_sched_class,
.enqueue_task = enqueue_task_rt,
.dequeue_task = dequeue_task_rt,
.pick_next_task = pick_next_task_rt,
.task_tick = task_tick_rt,
.task_new = task_new_rt,
.set_curr_task = set_curr_task_rt,
};
任务入队/出队
static void enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags) {
struct rt_rq *rt_rq = &rq->rt;
/* 更新任务时间戳 */
update_curr_rt(rq);
/* 加入优先级队列 */
enqueue_rt_entity(rt_rq, &p->rt, flags);
/* 抢占检查 */
if (!task_current(rq, p) && p->prio < rq->curr->prio) {
resched_curr(rq);
}
}
static void dequeue_task_rt(struct rq *rq, struct task_struct *p, int flags) {
struct rt_rq *rt_rq = &rq->rt;
/* 更新任务时间戳 */
update_curr_rt(rq);
/* 从优先级队列移除 */
dequeue_rt_entity(rt_rq, &p->rt, flags);
/* 重新调度检查 */
if (rt_rq->rt_nr_running &&
(!task_current(rq, p) || p->prio > rq->curr->prio)) {
resched_curr(rq);
}
}
优先级队列管理
// 实时任务使用优先级位图
#define MAX_RT_PRIO 100
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO);
struct list_head queue[MAX_RT_PRIO];
};
struct rt_rq {
struct rt_prio_array active;
unsigned long rt_nr_running;
};
任务选择
static struct task_struct *pick_next_task_rt(struct rq *rq) {
struct rt_rq *rt_rq = &rq->rt;
struct rt_prio_array *array = &rt_rq->active;
struct task_struct *next;
struct list_head *queue;
int idx;
/* 查找最高优先级 */
idx = sched_find_first_bit(array->bitmap);
if (idx >= MAX_RT_PRIO) {
return NULL;
}
/* 从队列头部取任务 */
queue = array->queue + idx;
next = list_entry(queue->next, struct task_struct, rt.run_list);
return next;
}
2.3 RMS 性能分析
测试场景
- 任务 1:周期 10ms,执行时间 2ms,优先级 1
- 任务 2:周期 20ms,执行时间 4ms,优先级 2
- 任务 3:周期 50ms,执行时间 10ms,优先级 3
- CPU 利用率:2/10 + 4/20 + 10/50 = 0.6 (60%)
结果分析
理论可调度性:n=3, U=0.6 ≤ 0.779 → 可调度
实际执行:所有任务均在截止时间前完成
适用场景
- 周期性任务:传感器采集、控制循环
- 硬实时系统:需要理论保证的场景
- 简单系统:任务数较少,CPU 利用率较低
第三章:Earliest-Deadline-First(EDF)
3.1 EDF 算法原理
核心思想
- 动态优先级:任务优先级根据截止时间动态调整
- 截止时间越早,优先级越高
- 可调度性条件:CPU 利用率 ≤ 100%
算法特点
| 特性 | 说明 | |——|——| | 调度类型 | 抢占式、动态优先级 | | 适用场景 | 周期性和非周期性任务 | | 可调度性 | 最优算法(CPU 利用率 100% 仍可调度) | | 实现复杂度 | 中 |
可调度性分析
EDF 是最优调度算法:
- 如果任何算法能调度任务集,EDF 也能调度
- CPU 利用率 ≤ 100% 时,EDF 总是可调度
3.2 EDF 调度类实现
截止时间任务结构
// include/linux/sched/deadline.h
struct sched_dl_entity {
u64 dl_runtime; /* 执行时间 */
u64 dl_period; /* 周期 */
u64 dl_deadline; /* 截止时间 */
u64 dl_bw; /* 带宽 */
s64 runtime; /* 剩余执行时间 */
struct rb_node rb_node; /* 红黑树节点 */
};
EDF 调度类
// kernel/sched/deadline.c
static const struct sched_class dl_sched_class = {
.next = &rt_sched_class,
.enqueue_task = enqueue_task_dl,
.dequeue_task = dequeue_task_dl,
.pick_next_task = pick_next_task_dl,
.task_tick = task_tick_dl,
.task_new = task_new_dl,
.set_curr_task = set_curr_task_dl,
};
截止时间队列管理
// 使用红黑树按截止时间排序
struct dl_rq {
struct rb_root_cached dl_se_tree;
u64 earliest_dl; /* 最早截止时间 */
unsigned long dl_nr_running;
};
static void enqueue_dl_entity(struct dl_rq *dl_rq, struct sched_dl_entity *dl_se) {
struct rb_node **link = &dl_rq->dl_se_tree.rb_root.rb_node;
struct rb_node *parent = NULL;
struct sched_dl_entity *entry;
/* 按截止时间插入红黑树 */
while (*link) {
parent = *link;
entry = rb_entry(parent, struct sched_dl_entity, rb_node);
if (dl_se->dl_deadline < entry->dl_deadline)
link = &parent->rb_left;
else
link = &parent->rb_right;
}
rb_link_node(&dl_se->rb_node, parent, link);
rb_insert_color(&dl_se->rb_node, &dl_rq->dl_se_tree.rb_root);
/* 更新最早截止时间 */
if (dl_se->dl_deadline < dl_rq->earliest_dl)
dl_rq->earliest_dl = dl_se->dl_deadline;
}
任务选择
static struct task_struct *pick_next_task_dl(struct rq *rq) {
struct dl_rq *dl_rq = &rq->dl;
/* 选择最早截止时间的任务 */
if (!dl_rq->dl_nr_running)
return NULL;
struct rb_node *leftmost = rb_first_cached(&dl_rq->dl_se_tree);
struct sched_dl_entity *dl_se = rb_entry(leftmost, struct sched_dl_entity, rb_node);
return dl_task_of(dl_se);
}
截止时间更新
static void update_dl_deadline(struct sched_dl_entity *dl_se) {
/* 更新截止时间为当前时间 + 周期 */
dl_se->dl_deadline = ktime_get_ns() + dl_se->dl_period;
/* 重新插入红黑树 */
rb_erase_cached(&dl_se->rb_node, &dl_rq->dl_se_tree);
enqueue_dl_entity(dl_rq, dl_se);
}
3.3 EDF 与 RMS 对比
| 特性 | RMS | EDF |
|---|---|---|
| 优先级 | 静态 | 动态 |
| 可调度性 | U ≤ 69.3% | U ≤ 100% |
| 实现复杂度 | 低 | 中 |
| 适用任务 | 周期性 | 周期性 + 非周期性 |
| 调度开销 | 低 | 中 |
选择建议
- RMS:任务简单、CPU 利用率低、需要理论保证
- EDF:任务复杂、CPU 利用率高、需要最优调度
第四章:优先级反转问题与解决方案
4.1 优先级反转问题
问题描述
高优先级任务 H 需要资源 R
中优先级任务 M 占用 CPU
低优先级任务 L 持有资源 R
执行序列:
1. L 获得资源 R
2. H 尝试获得 R,被阻塞
3. M 抢占 L(M 优先级 > L)
4. H 被 M 间接阻塞(优先级反转)
问题后果
- 高优先级任务被低优先级任务阻塞
- 截止时间无法保证
- 系统可能死锁
4.2 优先级继承(Priority Inheritance)
解决方案
- 当高优先级任务等待低优先级任务持有的资源时
- 临时提升低优先级任务的优先级到高优先级任务的优先级
- 释放资源后恢复原始优先级
实现
// kernel/locking/rtmutex.c
static void rt_mutex_adjust_prio(struct task_struct *task) {
struct task_struct *prio_task;
int highest_prio = task->normal_prio;
/* 查找最高优先级的等待者 */
if (!plist_empty(&task->pi_waiters))
prio_task = plist_first_entry(&task->pi_waiters,
struct task_struct, pi_waiters);
highest_prio = min(highest_prio, prio_task->prio);
/* 调整任务优先级 */
if (task->prio != highest_prio) {
task->prio = highest_prio;
resched_curr(task_rq(task));
}
}
4.3 优先级天花板(Priority Ceiling)
解决方案
- 每个资源有一个天花板优先级(最高可能等待该资源的任务优先级)
- 任务获得资源时,优先级提升到资源的天花板优先级
- 释放资源后恢复原始优先级
优势
- 避免死锁:天花板协议是死锁预防协议
- 确定性:优先级提升是可预测的
实现
static int rt_mutex_lock_pi(struct rt_mutex *lock) {
struct task_struct *task = current;
int ceiling = lock->ceiling_prio;
/* 提升优先级到天花板 */
if (task->prio > ceiling) {
task->prio = ceiling;
resched_curr(task_rq(task));
}
/* 尝试获得锁 */
return __rt_mutex_lock(lock);
}
4.4 解决方案对比
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| 优先级继承 | 实现简单,无死锁预防 | 可能多次继承 | 通用实时系统 |
| 优先级天花板 | 死锁预防,确定性 | 需要预先分析 | 硬实时系统 |
第五章:实时调度插件框架
5.1 实时调度策略注册
调度策略描述符
// include/linux/sched.h
struct sched_policy {
const char *name;
int policy_id;
const struct sched_class *sched_class;
int (*classify_task)(struct task_struct *p);
};
// 实时策略注册
SCHED_POLICY_REGISTER(rms, SCHED_RMS, &rt_sched_class, classify_rms_task);
SCHED_POLICY_REGISTER(edf, SCHED_EDF, &dl_sched_class, classify_edf_task);
策略分类函数
static int classify_rms_task(struct task_struct *p) {
/* 检查是否为周期性任务 */
if (p->rms_period > 0 && p->rms_runtime > 0) {
/* 计算静态优先级(周期越短,优先级越高) */
p->prio = 100 - (p->rms_period / 10);
return 1;
}
return 0;
}
static int classify_edf_task(struct task_struct *p) {
/* 检查是否为截止时间任务 */
if (p->dl_deadline > 0) {
return 1;
}
return 0;
}
5.2 实时任务创建
系统调用接口
// kernel/sys.c
long sys_sched_setattr(pid_t pid, struct sched_attr __user *uattr,
unsigned int flags) {
struct sched_attr attr;
struct task_struct *p;
/* 复制用户态参数 */
if (copy_from_user(&attr, uattr, sizeof(attr)))
return -EFAULT;
/* 找到目标任务 */
p = find_process_by_pid(pid);
if (!p) return -ESRCH;
/* 设置实时属性 */
p->dl_deadline = attr.dl_deadline;
p->dl_runtime = attr.dl_runtime;
p->dl_period = attr.dl_period;
p->rms_period = attr.rms_period;
p->rms_runtime = attr.rms_runtime;
/* 重新分配调度策略 */
assign_sched_policy(p);
return 0;
}
用户态 API
// include/sched.h
struct sched_attr {
__u32 size;
__u32 sched_policy;
__u64 sched_flags;
__s32 sched_nice;
__u32 sched_priority;
__u64 sched_deadline;
__u64 sched_runtime;
__u64 sched_period;
__u64 rms_period;
__u64 rms_runtime;
};
5.3 实时任务监控
调试接口
// kernel/sched/debug.c
static int sched_rt_show(struct seq_file *m, void *v) {
struct task_struct *p;
rcu_read_lock();
for_each_process(p) {
if (p->sched_class == &rt_sched_class) {
seq_printf(m, "%s: prio=%d, period=%llu, runtime=%llu\n",
p->comm, p->prio, p->rms_period, p->rms_runtime);
} else if (p->sched_class == &dl_sched_class) {
seq_printf(m, "%s: deadline=%llu, runtime=%llu, period=%llu\n",
p->comm, p->dl_deadline, p->dl_runtime, p->dl_period);
}
}
rcu_read_unlock();
return 0;
}
性能统计
// kernel/sched/stats.c
void update_rt_stats(struct task_struct *p) {
if (p->sched_class == &rt_sched_class) {
p->rt_stats.missed_deadlines +=
(ktime_get_ns() > p->next_rms_deadline) ? 1 : 0;
p->next_rms_deadline = ktime_get_ns() + p->rms_period;
} else if (p->sched_class == &dl_sched_class) {
p->dl_stats.missed_deadlines +=
(ktime_get_ns() > p->dl_deadline) ? 1 : 0;
}
}
第六章:性能测试与案例分析
6.1 RMS 性能测试
测试场景
- 任务 1:周期 10ms,执行时间 3ms
- 任务 2:周期 20ms,执行时间 6ms
- 任务 3:周期 50ms,执行时间 15ms
- CPU 利用率:3/10 + 6/20 + 15/50 = 0.9 (90%)
结果分析
理论可调度性:n=3, U=0.9 > 0.779 → 不可调度
实际执行:任务 3 经常错过截止时间
解决方案
- 减少任务 3 的执行时间:15ms → 10ms
- CPU 利用率:3/10 + 6/20 + 10/50 = 0.8 (80%)
- 结果:所有任务按时完成
6.2 EDF 性能测试
测试场景
- 相同任务集:CPU 利用率 90%
- 使用 EDF 调度
结果分析
理论可调度性:U=0.9 ≤ 1.0 → 可调度
实际执行:所有任务均在截止时间前完成
性能对比
| 指标 | RMS | EDF | |——|—–|—–| | 截止时间满足率 | 60% | 100% | | CPU 利用率 | 80% | 90% | | 调度开销 | 低 | 中 |
6.3 优先级反转测试
测试场景
- 任务 H:高优先级,需要资源 R
- 任务 M:中优先级,CPU 密集型
- 任务 L:低优先级,持有资源 R
结果分析
| 方案 | 任务 H 响应时间 | 是否满足截止时间 | |——|—————-|——————| | 无保护 | 50ms | 否 | | 优先级继承 | 5ms | 是 | | 优先级天花板 | 3ms | 是 |
第七章:高级特性与未来方向
7.1 混合调度策略
场景需求
- 硬实时任务:使用 RMS 或 EDF
- 软实时任务:使用 CFS
- 普通任务:使用普通调度
实现方案
// 调度器入口
static struct task_struct *pick_next_task(struct rq *rq) {
struct task_struct *p;
/* 1. 优先实时任务 */
p = pick_next_task_dl(rq);
if (p) return p;
p = pick_next_task_rt(rq);
if (p) return p;
/* 2. 回退到普通调度 */
return pick_next_task_fair(rq);
}
7.2 资源预留
带宽预留
- EDF 任务:预留 dl_runtime/dl_period 带宽
- RMS 任务:预留 rms_runtime/rms_period 带宽
实现
static int check_dl_overrun(struct dl_rq *dl_rq, struct sched_dl_entity *dl_se) {
u64 used_bw = dl_se->dl_runtime * NSEC_PER_SEC / dl_se->dl_period;
u64 total_bw = dl_rq->total_bw;
/* 检查带宽是否超限 */
if (total_bw + used_bw > BW_UNIT) {
return -1; /* 带宽超限 */
}
dl_rq->total_bw += used_bw;
return 0;
}
7.3 中断线程化
问题
- 中断处理程序:不能被抢占,影响实时任务
- 中断延迟:实时任务响应时间不确定
解决方案
- 中断线程化:将中断处理程序转换为高优先级线程
- 优先级继承:中断线程继承被中断任务的优先级
7.4 未来趋势
AI 驱动的实时调度
- 机器学习预测:基于历史行为预测任务执行时间
- 动态参数调整:自动调整截止时间和执行时间
- 自适应调度:根据系统负载动态切换 RMS/EDF
异构实时系统
- CPU/GPU 协同:实时任务分配到合适处理器
- 专用加速器:AI/ML 实时任务分配到 NPU
- 能耗优化:在实时性和功耗间动态平衡
结论:构建可靠的实时调度系统
实时调度是操作系统最严格的考验,
需要在确定性、可预测性、性能之间取得平衡。
通过本文的系统性设计,
我们构建了一个完整的实时调度框架,
支持:
- Rate-Monotonic:静态优先级,理论保证
- Earliest-Deadline-First:动态优先级,最优调度
- 优先级反转保护:确保高优先级任务及时执行
- 插件化架构:灵活扩展新的实时算法
此框架为后续实现 混合关键性系统、资源预留、中断线程化 奠定了坚实基础。
真正的实时系统,始于对截止时间的敬畏,
成于对调度算法的精确掌控。