3. 调度算法详解:优先级抢占式调度、时间片轮转调度、协作式调度、混合调度策略

各位做POS机开发的同行,调度算法这个话题,说实话是实时操作系统的灵魂。我这些年调试过的POS机死机、卡顿、响应慢的问题,十有八九都能追溯到调度策略选型不当。今天咱们就把这四种调度算法掰开揉碎了讲清楚。

3.1 优先级抢占式调度

这个算法,说白了就是「谁急谁先跑」。每个任务都有一个优先级,高优先级的任务可以随时打断低优先级任务的执行。POS机里最典型的场景就是刷卡支付——当用户挥卡时,支付任务必须立刻响应,哪怕此时屏幕正在刷新动画。

核心机制:

  • 每个任务分配一个优先级数值(通常0-255,数值越小优先级越高)
  • 就绪队列中优先级最高的任务获得CPU
  • 高优先级任务就绪时,立即抢占当前运行的低优先级任务
  • 系统调用或中断返回时触发重新调度

我在做某款手持POS机时遇到过一个问题:按键扫描任务优先级设得太高,结果每次按键都会打断LCD刷新的关键时序,导致屏幕闪烁。后来我把按键任务优先级降了两级,问题就解决了。这里有个经验——优先级不是越高越好,够用就行

避坑指南:我曾经在一个项目中遇到「优先级反转」的经典问题。一个低优先级的打印任务持有了互斥锁,中优先级的UI刷新任务不断抢占,导致高优先级的支付任务拿不到锁,整个系统卡死。解决方案是使用优先级继承协议,或者干脆把临界区做短。

3.2 时间片轮转调度

时间片轮转,我习惯叫它「轮流坐庄」。每个任务分配一个固定的时间片(比如10ms),时间到了就强制切换到下一个任务。这种算法在POS机上主要用于那些「没有急事但也不能饿着」的任务,比如日志记录、电量监测。

参数 典型值 说明
时间片长度 5-20ms 太短则切换开销大,太长则响应变差
就绪队列 环形队列 每个任务轮流获得CPU
上下文切换 保存/恢复寄存器 由定时器中断触发

你想想看,如果所有任务都用优先级抢占,那低优先级的任务可能永远得不到CPU——这就是所谓的「饥饿」问题。时间片轮转恰好解决了这个问题。我记得有一次调试一个POS机的蓝牙打印功能,打印任务总是被其他任务抢占,导致打印超时。改用时间片轮转后,每个任务都能分到CPU时间,问题迎刃而解。

我的建议:时间片长度要根据你的系统时钟节拍来定。如果系统时钟是100Hz(10ms一个tick),那时间片最好设成tick的整数倍。我一般设成2-3个tick,也就是20-30ms。太短的话,切换开销占比太高,不值得。

3.3 协作式调度

协作式调度,说白了就是「自觉」。任务主动让出CPU,而不是被强制打断。这种调度方式在早期的嵌入式系统中很常见,现在主要用于一些对实时性要求不高的场景。

为什么还要提它?因为POS机里有些任务天生就适合协作式调度。比如菜单界面的绘制——你画到一半被抢占了,画面就会撕裂。协作式调度可以保证一个任务完整执行完再切换。

协作式调度的典型实现:

void task_entry(void)
{
    while(1)
    {
        // 执行任务逻辑
        do_something();
        
        // 主动让出CPU
        task_yield();
    }
}

嗯,这里要注意。协作式调度最大的问题是——如果某个任务不主动让出CPU,其他任务就永远没机会。我在一个老款POS机上见过这种情况:一个死循环的动画任务没有加yield,结果整个系统点不动了。所以,用协作式调度,一定要确保每个任务都有明确的yield点

3.4 混合调度策略

实际项目中,我们很少只用一种调度算法。POS机这种既要响应刷卡(高实时性),又要流畅显示动画(低延迟),还要稳定打印(不丢数据)的系统,必须用混合策略。

我个人习惯的做法是这样的:

  • 高优先级任务(支付、读卡、中断处理):用优先级抢占式调度,确保毫秒级响应
  • 中优先级任务(UI刷新、按键扫描):用时间片轮转,保证每个任务都能运行
  • 低优先级任务(日志记录、电量监测):用协作式调度,空闲时再跑

你可能会问:这三种调度怎么共存?其实很多RTOS都支持。比如FreeRTOS的调度器就是优先级抢占式+时间片轮转的混合体——同优先级任务之间轮转,不同优先级之间抢占。我在此基础上再加一个协作式任务队列,专门放那些不着急的任务。

实战经验:我在设计某款智能POS机时,把支付任务优先级设为最高(0),UI刷新设为中等(5),日志任务设为最低(10)。同优先级的UI任务之间用时间片轮转。这样既保证了支付的实时性,又让UI看起来流畅,日志也不会饿死。运行了两年,没出过调度相关的问题。

3.5 如何选择调度算法

说了这么多,到底怎么选?我总结了一个简单的判断流程:

  1. 任务是否有严格的截止时间? 是 → 用优先级抢占式
  2. 多个任务优先级相同? 是 → 加时间片轮转
  3. 任务之间需要原子操作? 是 → 考虑协作式
  4. 系统负载变化大? 是 → 用混合策略

说白了,没有银弹。我见过有人把整个POS机所有任务都设成同一个优先级,然后用时间片轮转——结果刷卡响应慢得被客户投诉。也见过有人把所有任务都用优先级抢占——结果低优先级的日志任务永远跑不了,出了问题查都没法查。

最后提醒一句:调度算法的选择,一定要结合你的硬件平台。Cortex-M3/M4有硬件优先级嵌套,用抢占式调度效率很高。如果是低端的Cortex-M0,没有硬件优先级,那就要考虑上下文切换的开销。我曾经在M0上做过测试,一次完整的上下文切换要花掉将近100个CPU周期——如果你的时间片只有5ms,那切换开销占比就太大了。

好了,调度算法这块就讲到这里。下一章咱们聊聊任务同步与通信——这可是多任务系统的另一个大坑。