3. 优先级与调度策略:抢占式调度、时间片轮转、协作式调度、优先级反转问题
调度策略,说白了就是RTOS决定「下一个该谁跑」的规则。很多初学者觉得调度嘛,不就是谁优先级高谁先上?其实没那么简单。我做了这么多年嵌入式,见过太多因为调度策略选错导致系统崩溃的案例。今天咱们就把这几种调度策略掰开揉碎,好好聊聊。
3.1 抢占式调度(Preemptive Scheduling)
这是RTOS里最常用的调度方式。它的核心逻辑就一句话:高优先级任务随时可以打断低优先级任务。
举个例子:你正在写一个低优先级的日志任务,突然来了个紧急的中断,要处理电机控制。抢占式调度下,电机控制任务会立刻抢走CPU,等它处理完了,日志任务再接着跑。
核心特征:
- 任务优先级决定执行权
- 高优先级任务就绪时,立即抢占CPU
- 被抢占的任务上下文自动保存
- 响应时间可预测,适合实时系统
我在项目中遇到过一个问题:一个传感器采集任务优先级设得太高,结果它频繁抢占低优先级的显示任务,导致屏幕刷新卡顿。后来我把采集任务改成中断+信号量的方式,才解决了这个问题。
注意:抢占式调度虽然响应快,但容易引发优先级反转和死锁。后面我们会详细讲优先级反转。
3.2 时间片轮转(Round-Robin Scheduling)
同优先级任务之间怎么调度?这时候就需要时间片轮转了。每个任务轮流跑一个固定时间片,时间到了就切换给下一个。
你想想看,如果两个任务优先级一样,又没有时间片轮转,那谁先跑完谁就占着CPU,另一个永远没机会。这显然不合理。
| 参数 | 说明 | 典型值 |
|---|---|---|
| 时间片长度 | 每个任务一次运行的最大时间 | 1-10ms |
| 切换开销 | 上下文保存+恢复的时间 | 几微秒到几十微秒 |
| 公平性 | 同优先级任务均分CPU | 取决于时间片设置 |
我个人习惯把时间片设成系统Tick的整数倍。比如系统Tick是1ms,时间片就设5个Tick。这样调度器在Tick中断里就能完成切换,不用额外定时器。
经验之谈:时间片太短会导致频繁切换,浪费CPU在上下文切换上;太长又会让任务响应变慢。我一般从5ms开始调,根据实际负载微调。
3.3 协作式调度(Cooperative Scheduling)
协作式调度跟抢占式完全相反——任务自己决定什么时候让出CPU。说白了,就是任务之间互相「谦让」。
这种调度方式在早期的嵌入式系统里很常见,现在一些超低功耗场景还在用。它的好处是:没有抢占,就没有优先级反转,也不需要复杂的同步机制。
但缺点也很明显:如果一个任务死循环或者忘了让出CPU,整个系统就卡死了。我曾经在一个物联网终端项目里用过协作式调度,结果一个传感器驱动库里有bug,偶尔会卡在while循环里,导致整个设备死机。从那以后,我对协作式调度就特别谨慎。
适用场景:
- 任务数量少且可控
- 每个任务执行时间短且确定
- 对功耗要求极高(省去调度器开销)
- 系统复杂度低,不需要抢占
3.4 优先级反转问题(Priority Inversion)
这是抢占式调度里最头疼的问题。什么叫优先级反转?简单说就是:低优先级任务因为持有资源,导致高优先级任务被阻塞。
我画个图帮你理解:
你看,任务A优先级最高,但它等不到资源,反而被中优先级的任务B给「拖死」了。这就是优先级反转的典型场景。
3.5 解决方案:优先级继承与优先级天花板
怎么解决优先级反转?业界主要有两种方案:
3.5.1 优先级继承(Priority Inheritance)
当高优先级任务被低优先级任务持有的锁阻塞时,低优先级任务临时继承高优先级任务的优先级。这样中优先级的任务就抢不过它了,锁就能尽快释放。
实现要点:
- 只在任务被阻塞时才提升优先级
- 释放锁后恢复原始优先级
- 支持嵌套锁(但实现复杂)
- 可能引发多次优先级调整
3.5.2 优先级天花板(Priority Ceiling)
给每个互斥锁设定一个「天花板优先级」——所有可能获取这个锁的任务中的最高优先级。任务一旦获取锁,优先级立刻提升到天花板级别。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 优先级继承 | 只在必要时提升,开销小 | 可能多次调整,实现复杂 |
| 优先级天花板 | 实现简单,一次提升到位 | 可能过度提升,浪费CPU |
我个人更倾向于优先级继承。虽然实现复杂一点,但更精准,不会浪费CPU。不过如果你的系统比较简单,任务数量少,优先级天花板也是个不错的选择。
避坑指南:我曾经在一个项目里用了优先级天花板,结果因为天花板设得太高,导致一个低优先级任务长时间占用CPU,影响了其他实时任务。后来改成优先级继承,问题就解决了。所以选方案时一定要结合实际场景。
3.6 调度策略选择指南
说了这么多,到底该怎么选?我整理了一个对照表:
| 场景 | 推荐调度策略 | 理由 |
|---|---|---|
| 硬实时任务(如电机控制) | 抢占式 | 响应时间确定,优先级保证 |
| 同优先级批量任务 | 时间片轮转 | 公平分配CPU,防止饿死 |
| 超低功耗设备 | 协作式 | 省去调度器开销,降低功耗 |
| 混合实时/非实时任务 | 抢占式+时间片 | 实时任务抢占,非实时任务轮转 |
嗯,调度策略这块内容不少,但核心就这几个点。记住:没有万能的调度策略,只有最适合你场景的方案。多在实际项目里试错,慢慢就有感觉了。