4. 任务调度算法:时间片轮转、优先级抢占、协作式与调度点分析

调度算法,说白了就是RTOS的“大脑”。它决定哪个任务该跑,哪个任务该等。我做了这么多年嵌入式,见过太多系统崩溃,最后查下来都是调度策略没选对。这一章,咱们就把四种核心调度算法掰开揉碎讲清楚。

4.1 时间片轮转调度(Round-Robin)

时间片轮转,是最公平的调度方式。每个任务轮流执行固定长度的时间片,时间到了就切换下一个。就像几个人分蛋糕,每人吃一口就传给下一个人。

核心特征:

  • 所有任务优先级相同
  • 每个任务分配固定时间片(通常1-100ms)
  • 时间片耗尽触发任务切换
  • 适用于对实时性要求不高的场景

我在一个智能家居项目里用过纯时间片轮转。当时有6个传感器采集任务,每个跑20ms时间片。结果发现温度传感器偶尔会丢数据——因为轮到它的时候,缓冲区已经溢出了。后来我改成优先级抢占+时间片混合,才解决问题。

时间片大小的选择有讲究:

时间片长度 优点 缺点
1-5ms 响应快,切换平滑 上下文切换开销大
10-50ms 折中方案,常用 中等延迟
100ms以上 切换开销小 响应迟钝,像卡死

我的经验:时间片别低于系统Tick周期的3倍。比如Tick=1ms,时间片至少设3ms。否则任务还没干正事就被切走了,效率极低。

4.2 优先级抢占调度(Preemptive Priority)

这是RTOS里用得最多的调度方式。每个任务有独立优先级,高优先级任务随时可以打断低优先级任务。你想想看,紧急刹车信号来了,谁还管音乐播放器在不在播?

优先级抢占的核心规则:

  • 就绪态中,优先级最高的任务先运行
  • 高优先级任务就绪时,立即抢占当前任务
  • 同优先级任务之间,通常配合时间片轮转

我曾经在一个工业控制器项目里踩过坑。三个任务:A(优先级5)、B(优先级4)、C(优先级3)。A和B都等一个信号量,C在跑。结果A拿到信号量后一直占着CPU不放,B永远等不到。这就是典型的优先级反转。后来加了优先级继承协议才解决。

注意:优先级抢占不是万能的。优先级反转、死锁、饥饿问题都可能出现。设计时一定要做优先级规划,别把所有任务都塞到同一优先级。

优先级分配的一般原则:

优先级范围 适用任务类型 举例
最高(0-3) 硬实时中断型任务 刹车控制、心跳检测
中高(4-7) 周期性实时任务 传感器采集、PID控制
中等(8-11) 软实时任务 通信协议栈、UI刷新
低(12-15) 后台任务 日志记录、自检

4.3 协作式调度(Cooperative Scheduling)

协作式调度,也叫非抢占式调度。任务自己决定什么时候让出CPU。说白了就是“自觉”。每个任务跑完一段逻辑后,主动调用yield或delay,把控制权交回去。

协作式的特点:

  • 没有抢占,任务切换只在主动让出时发生
  • 不需要临界区保护共享数据(因为不会被打断)
  • 代码简单,资源开销小
  • 但一个任务死循环,整个系统就挂了

我记得早期做8位单片机项目,RAM只有256字节,跑不了完整RTOS。就用协作式调度,每个任务里手动插delay(10)。虽然简陋,但够用。现在做复杂系统,协作式基本只用在超低功耗设备上。

适用场景:

  • RAM/ROM极度受限的MCU
  • 任务数量少且执行时间可预测
  • 不需要硬实时响应
  • 电池供电设备(减少切换功耗)

4.4 调度点分析

调度点,就是系统检查“要不要切换任务”的时刻。很多初学者以为调度只在时间片耗尽时发生,其实不然。

典型的调度点包括:

  1. 时间片耗尽——当前任务时间片用完
  2. 任务创建/删除——新任务就绪或任务终止
  3. 任务挂起/恢复——调用suspend/resume
  4. 同步对象操作——释放信号量、发消息、解锁互斥量
  5. 中断退出——ISR返回时,可能唤醒高优先级任务
  6. 主动让出——调用delay、yield等

为什么会这样?因为RTOS内核在每个调度点都会调用调度器,重新计算当前最高优先级任务。如果发现更高优先级的任务就绪,立即切换。

避坑指南:我曾经在中断服务函数里释放信号量,以为退出中断后会自动调度。结果发现FreeRTOS需要手动在ISR末尾调用portYIELD_FROM_ISR。不调的话,高优先级任务要等到下一个Tick才被调度,白白浪费几十毫秒。

调度点的密度直接影响系统实时性:

调度点密度 实时性 CPU开销 典型场景
高(每个Tick多次) 强 高 工业控制、机器人
中(每个Tick一次) 中等 中等 消费电子、IoT
低(主动让出才调度) 弱 低 超低功耗设备

4.5 四种调度算法对比

我把四种算法放在一起对比,方便你选型时参考:

特性 时间片轮转 优先级抢占 协作式
任务优先级 相同 不同 通常相同
抢占能力 时间片到期 高优就绪即抢 不抢占
实时性 低 高 低
代码复杂度 低 高 极低
资源开销 中 高 低
典型RTOS FreeRTOS同优先级 μC/OS、RT-Thread Protothread

实际项目中,很少只用一种算法。大多数RTOS都是混合策略:优先级抢占为主,同优先级任务之间用时间片轮转。比如FreeRTOS,默认就是抢占式,但你可以通过配置configUSE_TIME_SLICING开启同优先级时间片。

RTOS调度算法分类与调度点 调度算法 时间片轮转 优先级抢占 协作式 调度点(触发切换的时机) 时间片耗尽 任务创建/删除 任务挂起/恢复 同步对象操作 中断退出 主动让出(yield/delay) 调度点密度决定实时性 调度点越多 → 响应越快 → CPU开销越大 实际项目:优先级抢占 + 同优先级时间片轮转

嗯,调度算法这块,没有银弹。选型时要综合考虑实时性要求、CPU性能、RAM大小、任务数量。我个人习惯:能用优先级抢占就别用纯时间片,能用协作式的场景通常也不需要RTOS。