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 调度点分析
调度点,就是系统检查“要不要切换任务”的时刻。很多初学者以为调度只在时间片耗尽时发生,其实不然。
典型的调度点包括:
- 时间片耗尽——当前任务时间片用完
- 任务创建/删除——新任务就绪或任务终止
- 任务挂起/恢复——调用suspend/resume
- 同步对象操作——释放信号量、发消息、解锁互斥量
- 中断退出——ISR返回时,可能唤醒高优先级任务
- 主动让出——调用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开启同优先级时间片。
嗯,调度算法这块,没有银弹。选型时要综合考虑实时性要求、CPU性能、RAM大小、任务数量。我个人习惯:能用优先级抢占就别用纯时间片,能用协作式的场景通常也不需要RTOS。