2、任务调度策略:优先级抢占调度、时间片轮转、 Deadline调度、调度点与上下文切换开销分析

任务调度,说白了就是操作系统决定「谁先跑、跑多久」的规则。很多刚入行的朋友觉得调度就是调个优先级,其实远没那么简单。我这些年调过的系统里,至少有一半的实时性问题,根子都出在调度策略选型或配置上。

咱们先看一张总览图,把四种核心调度策略的关系理清楚。

嵌入式实时调度策略全景图 任务调度策略 优先级抢占调度 时间片轮转 Deadline 调度 调度点与上下文切换开销 核心特性 • 高优先级抢占低优先级 • 优先级反转问题 • 适合强实时场景 核心特性 • 每个任务固定时间片 • 公平调度,无饥饿 • 适合软实时/分时 核心特性 • 按截止时间排序 • EDF 最常用 • 可调度性分析复杂

2.1 优先级抢占调度

这是最常用的实时调度策略。每个任务有一个优先级,就绪队列里谁优先级高谁先跑。高优先级任务一旦就绪,立刻抢占正在运行的低优先级任务。

我习惯把这种策略比作「急诊插队」——ICU 来的病人必须优先处理。在 FreeRTOS 或 μC/OS-III 里,这就是默认行为。

关键点: 优先级必须静态分配或按协议动态调整。我个人建议:中断服务程序里的任务用最高优先级,周期性的控制任务用中优先级,后台日志等非实时任务用最低优先级。

但这里有个大坑——优先级反转。我曾经在一个电机控制项目里遇到过:低优先级任务持有了一个互斥锁,中优先级任务一直在跑,高优先级任务等锁等到超时,电机直接失控。解决方案是优先级继承协议,RTOS 一般都有这个选项,记得打开。

2.2 时间片轮转

时间片轮转,说白了就是「每人轮流用 CPU,一次用一小会儿」。每个任务分配一个固定长度的时间片,时间到了就切换到下一个就绪任务。

这种策略的好处是公平——不会出现某个任务永远得不到 CPU 的情况。但坏处也很明显:实时性无法保证。你想想看,一个紧急任务可能要等完一圈时间片才能轮到。

对比项 优先级抢占 时间片轮转
实时性 强(高优先级可立即响应) 弱(必须等时间片用完)
公平性 差(低优先级可能饿死) 好(每个任务都有机会)
适用场景 硬实时、工业控制 软实时、人机交互
典型 RTOS FreeRTOS、VxWorks Linux CFS、RT-Thread

实际项目中,我经常把两种策略混用。比如:高优先级任务用抢占模式,保证关键响应;同优先级的多个任务用时间片轮转,防止某个任务霸占 CPU。

2.3 Deadline 调度

Deadline 调度是按任务的截止时间来排队的——谁最快要到截止时间,谁先跑。最经典的算法是 EDF(Earliest Deadline First)。

我记得第一次用 EDF 是在一个视频编码器项目里。每帧必须在 33ms 内编码完成,否则画面会卡顿。用优先级抢占死活调不好,换成 EDF 后,CPU 利用率从 65% 提到了 92%,而且没有丢帧。

注意: EDF 在过载情况下会「多米诺骨牌式」地错过所有截止时间。我曾经吃过这个亏——系统负载超过 100% 时,所有任务一起完蛋。所以一定要做可调度性分析,留出 20%-30% 的余量。

EDF 的调度决策很简单:每次调度时,选 deadline 最近的任务。但实现上需要维护一个有序队列,插入和删除都是 O(log n) 的复杂度。在任务数少于 20 个的系统中,直接用数组遍历也够用。

2.4 调度点与上下文切换开销

调度点就是操作系统检查「要不要切换任务」的时刻。常见的调度点有:

  • 任务主动挂起:调用 delay、pend 等阻塞 API 时
  • 时间中断:系统 tick 中断触发,检查时间片是否用完
  • 任务创建/删除:任务状态变化时
  • 中断退出:中断返回前检查是否有更高优先级任务就绪

上下文切换开销,就是保存当前任务现场、恢复新任务现场所花的时间。这个开销是纯浪费的——CPU 没干任何有用的活。

实测数据(Cortex-M4 @ 168MHz):
FreeRTOS 一次上下文切换 ≈ 0.6μs ~ 1.2μs
μC/OS-III 一次上下文切换 ≈ 0.8μs ~ 1.5μs
Linux RT-Preempt 一次上下文切换 ≈ 3μs ~ 8μs

我曾经在一个 1000Hz 控制循环的系统里,把调度频率从每 1ms 一次降到每 5ms 一次,CPU 利用率直接降了 12%。你想想看,如果每秒切换 1000 次,每次浪费 1μs,那就是 1ms 的纯开销——占 CPU 的 0.1%,看起来不多,但在硬实时系统里,这 0.1% 可能就是压垮骆驼的最后一根稻草。

优化建议:
1. 减少不必要的调度点——能用信号量就别用任务切换
2. 合并短任务——把多个 100μs 的小任务合并成一个 500μs 的任务
3. 关中断临界区要短——我见过有人关中断 50μs,这期间所有中断都丢了
4. 使用 FPU 时注意上下文保存——FPU 寄存器有 32 个,保存恢复开销很大

嗯,调度策略这块就聊到这儿。记住一句话:没有最好的调度策略,只有最适合你场景的策略。选型时把任务的时间特性、紧急程度、CPU 负载都列出来,再决定用哪种。