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(中优先级) 任务C(低优先级) 共享资源(互斥锁) ① 任务C获取锁 ② 任务A等待锁(被阻塞) ③ 任务B抢占CPU 问题分析: • 任务C持有锁,任务A等待锁被阻塞 • 任务B优先级高于C,抢占CPU,任务C无法释放锁 • 任务A(最高优先级)反而被任务B(中优先级)间接阻塞

你看,任务A优先级最高,但它等不到资源,反而被中优先级的任务B给「拖死」了。这就是优先级反转的典型场景。

3.5 解决方案:优先级继承与优先级天花板

怎么解决优先级反转?业界主要有两种方案:

3.5.1 优先级继承(Priority Inheritance)

当高优先级任务被低优先级任务持有的锁阻塞时,低优先级任务临时继承高优先级任务的优先级。这样中优先级的任务就抢不过它了,锁就能尽快释放。

实现要点:

  • 只在任务被阻塞时才提升优先级
  • 释放锁后恢复原始优先级
  • 支持嵌套锁(但实现复杂)
  • 可能引发多次优先级调整

3.5.2 优先级天花板(Priority Ceiling)

给每个互斥锁设定一个「天花板优先级」——所有可能获取这个锁的任务中的最高优先级。任务一旦获取锁,优先级立刻提升到天花板级别。

方案 优点 缺点
优先级继承 只在必要时提升,开销小 可能多次调整,实现复杂
优先级天花板 实现简单,一次提升到位 可能过度提升,浪费CPU

我个人更倾向于优先级继承。虽然实现复杂一点,但更精准,不会浪费CPU。不过如果你的系统比较简单,任务数量少,优先级天花板也是个不错的选择。

避坑指南:我曾经在一个项目里用了优先级天花板,结果因为天花板设得太高,导致一个低优先级任务长时间占用CPU,影响了其他实时任务。后来改成优先级继承,问题就解决了。所以选方案时一定要结合实际场景。

3.6 调度策略选择指南

说了这么多,到底该怎么选?我整理了一个对照表:

场景 推荐调度策略 理由
硬实时任务(如电机控制) 抢占式 响应时间确定,优先级保证
同优先级批量任务 时间片轮转 公平分配CPU,防止饿死
超低功耗设备 协作式 省去调度器开销,降低功耗
混合实时/非实时任务 抢占式+时间片 实时任务抢占,非实时任务轮转

嗯,调度策略这块内容不少,但核心就这几个点。记住:没有万能的调度策略,只有最适合你场景的方案。多在实际项目里试错,慢慢就有感觉了。