4. 任务调度策略:抢占式调度、时间片轮转、合作式调度,以及如何根据功耗选择策略

调度策略,说白了就是决定“下一个该谁跑”的规则。很多工程师一上来就选抢占式,觉得它高级、实时性好。但在我做低功耗项目的经验里,选错调度策略,功耗可能直接翻倍。今天咱们就把三种主流调度策略掰开揉碎,看看它们各自适合什么场景,以及怎么根据功耗目标做选择。

4.1 三种调度策略的核心差异

先看一张对比表,快速建立整体认知:

特性 抢占式调度 时间片轮转 合作式调度
任务切换触发 外部事件/时钟中断 时钟节拍到期 任务主动让出CPU
实时性 高(可立即响应高优先级任务) 中(取决于时间片长度) 低(依赖任务自觉)
上下文切换开销 高(每次中断都切换) 高(固定频率切换) 低(只有任务主动让出时才切换)
功耗特征 频繁唤醒,功耗较高 周期性唤醒,功耗中等 可长时间休眠,功耗最低
代码复杂度 需要处理优先级反转、互斥 需要处理共享资源竞争 简单,无需锁机制

4.2 抢占式调度:实时性的代价

抢占式调度是RTOS的标配。高优先级任务就绪时,会立即打断正在运行的低优先级任务。我最早做工业控制器时,觉得这功能太香了——紧急报警信号来了,必须立刻响应。

但功耗方面,它有个致命问题:频繁的上下文切换。每次切换都要保存/恢复寄存器、堆栈指针,CPU核心要频繁进出休眠状态。我测过一个项目,单纯把抢占式调度换成合作式,在空闲率80%的场景下,功耗降低了40%。

关键指标:抢占式调度中,每次上下文切换的功耗 ≈ 3~5μA·ms(取决于MCU架构)。如果系统每秒切换1000次,光切换就吃掉3~5mA的电流。

什么时候必须用抢占式?

  • 有硬实时任务(响应时间 < 1ms)
  • 任务间有明确的优先级层级
  • 外部事件触发频率高且不可预测

4.3 时间片轮转:公平但浪费

时间片轮转,说白了就是“每人分一段CPU时间”。每个任务轮流跑固定的时间片,跑完就切下一个。我见过不少初学者在低功耗项目里用这个策略,结果电池没撑过一周。

为什么?因为时间片轮转强制系统以固定频率唤醒。即使所有任务都无事可做,调度器依然会按时切换。我曾经在一个传感器节点项目里,把时间片从10ms改成100ms,功耗直接降了60%。但代价是任务响应延迟变大了。

避坑指南:我曾经在一个电池供电的无线模块里用了时间片轮转,结果发现大部分时间片都在跑“空任务”——任务检查完队列发现没数据,直接yield。后来改成事件驱动+合作式调度,待机功耗从2mA降到了50μA。

时间片轮转的适用场景:

  • 多个同等优先级的交互式任务
  • 任务需要公平获得CPU时间
  • 系统对功耗不敏感(如市电供电)

4.4 合作式调度:低功耗的王者

合作式调度,也叫非抢占式调度。任务自己决定什么时候让出CPU。没有外部中断打断,没有时钟节拍强制切换。任务跑完了,主动调用 task_yield() 或等待某个事件。

我个人习惯在电池供电的IoT设备里首选合作式调度。原因很简单:CPU可以长时间处于深度休眠状态。任务处理完数据后,直接进入WFI(Wait For Interrupt)模式,只有外部事件(如传感器数据就绪、定时器到期)才能唤醒。

// 合作式调度典型任务结构
void sensor_task(void) {
    while(1) {
        // 1. 读取传感器数据
        read_sensor();
        // 2. 处理数据
        process_data();
        // 3. 发送结果
        send_result();
        // 4. 主动让出CPU,进入休眠
        task_sleep(100);  // 休眠100ms
    }
}

你想想看,如果这个任务用抢占式调度,即使它主动休眠了,调度器也会在下一个时钟节拍唤醒CPU检查是否有其他任务就绪。而合作式调度下,CPU真的就睡了,直到100ms后的定时器中断才醒。

经验之谈:合作式调度最适合“事件驱动”型系统。任务只在需要时运行,其他时间CPU深度休眠。我在一个温湿度采集器项目里,用合作式调度+32kHz RTC定时唤醒,两节AA电池撑了两年。

4.5 如何根据功耗选择策略?

这里我给出一个决策流程,是我在多个项目里总结出来的:

调度策略选择决策流程 开始选择调度策略 是否有硬实时任务(响应<1ms)? 是 使用抢占式调度 否 是否电池供电且对功耗敏感? 是 使用合作式调度 否 使用时间片轮转调度 提示:实际项目中常混合使用——高优先级任务用抢占式,低优先级任务用合作式

这个流程的核心逻辑是:先满足实时性,再优化功耗。如果系统没有硬实时要求,优先考虑合作式调度。如果必须用抢占式,可以通过以下手段降低功耗:

  • 降低时钟节拍频率:从1ms节拍改成10ms,减少不必要的上下文切换
  • 使用无滴答(Tickless)模式:只在有任务就绪时才产生时钟中断
  • 合并空闲任务:让空闲任务直接进入深度休眠,而不是跑空循环

4.6 混合调度策略:实战中的最优解

说实话,我在实际项目中很少只用一种策略。大多数嵌入式系统都有混合需求:一部分任务需要快速响应,另一部分可以慢慢跑。

举个例子,一个智能门锁系统:

  • 指纹识别任务:需要抢占式,用户按指纹必须立即响应
  • 蓝牙通信任务:可以用时间片轮转,保证数据收发公平
  • 电池电量监测任务:合作式,每5分钟唤醒一次,测完就睡

实战配置建议:在FreeRTOS中,可以通过设置不同的任务优先级来实现混合调度。高优先级任务(抢占式)优先级设为5~10,中等任务(时间片轮转)设为3~4,低功耗任务(合作式)设为1~2,并在空闲钩子中进入深度休眠。

4.7 避坑总结

最后,把我这些年踩过的坑总结一下:

我曾经犯过的错:

  • 在电池供电设备里用了默认的1ms时间片,结果待机功耗高达3mA——改成100ms时间片+合作式空闲任务后降到50μA
  • 在抢占式调度中忘了关中断就访问共享资源,导致优先级反转,系统死锁——后来加了互斥信号量才解决
  • 合作式调度中一个任务写了死循环,整个系统卡死——现在每个合作式任务我都会加看门狗喂狗操作

记住一句话:没有最好的调度策略,只有最适合当前场景的策略。选型时先列需求清单,再对照功耗和实时性要求做取舍。嗯,这就是我处理调度策略的基本思路。