第4章:任务调度——抢占式调度与时间片轮转
各位好,我是老张。今天咱们聊聊RTOS里最核心的话题——任务调度。说白了,调度就是决定「哪个任务先跑、哪个任务后跑」的规则。你想想看,血压计里同时要处理按键、采集数据、驱动屏幕、还要定时测量,这么多任务挤在一起,总得有个「交警」来指挥交通吧?
这个交警,就是调度器。我做了十几年嵌入式,见过不少因为调度策略选错导致系统崩溃的案例。嗯,咱们今天就把这块掰开揉碎了讲清楚。
4.1 抢占式调度:谁急谁先跑
抢占式调度,是RTOS最常用的调度方式。它的核心思想很简单:高优先级的任务可以打断低优先级任务的执行。
举个例子。你在写血压计程序,一个任务负责按键扫描(优先级低),另一个任务负责处理测量完成的中断(优先级高)。当用户按下「开始测量」键时,按键任务正在跑,但测量完成中断一来,系统会立刻暂停按键任务,转去执行高优先级的测量处理任务。等处理完了,再回来接着按键扫描。
我在项目中遇到过一个问题:有个同事把按键任务的优先级设得比ADC采集还高,结果每次按键按下时,ADC数据采集就被打断,导致测量值跳变。后来改成抢占式调度,把ADC采集优先级提到最高,问题就解决了。
抢占式调度的关键点:
- 每个任务都有固定优先级(0~255,数值越小优先级越高)
- 高优先级任务就绪时,立即抢占CPU
- 同优先级任务之间,按时间片轮转
- 中断可以抢占任何任务(包括最高优先级任务)
你可能会问:「那低优先级任务岂不是永远没机会跑?」不会的。高优先级任务一旦进入阻塞状态(比如等待延时、等待信号量),系统就会立刻切换到下一个就绪的任务。这就是RTOS的「让权」机制。
4.2 时间片轮转:大家轮流来
时间片轮转,是针对同优先级任务的调度策略。说白了就是「一人跑一会儿,轮流来」。
每个任务分配一个固定时间片(比如5ms),时间到了就强制切换给下一个同优先级任务。这样能保证所有同优先级任务都能获得CPU时间,不会出现某个任务「饿死」的情况。
我记得在FreeRTOS里,时间片轮转是默认开启的。但有个坑:如果你把时间片设得太小(比如1ms),任务切换太频繁,系统开销会很大。我曾经在一个项目中把时间片设为1ms,结果CPU有30%的时间都在做任务切换,测量数据反而变慢了。
我的建议:
血压计这类医疗设备,时间片建议设在5~20ms之间。太短了浪费CPU,太长了影响响应速度。我一般用10ms,兼顾响应和效率。
4.3 调度算法分析:谁来决定下一个跑谁?
调度算法,就是调度器用来决定「下一个该跑谁」的规则。常见的算法有几种:
| 算法名称 | 原理 | 适用场景 | 缺点 |
|---|---|---|---|
| 优先级抢占式 | 始终运行最高优先级就绪任务 | 实时性要求高的系统 | 低优先级任务可能被饿死 |
| 时间片轮转 | 同优先级任务轮流执行固定时间 | 多个同等重要任务 | 时间片选择困难 |
| 最短剩余时间优先 | 优先执行剩余执行时间最短的任务 | 批处理系统 | 需要预估执行时间 |
| 多级反馈队列 | 多个优先级队列,动态调整 | 通用操作系统 | 实现复杂 |
在RTOS里,最常用的是「优先级抢占式 + 时间片轮转」的组合。说白了就是:先看优先级,高优先级的先跑;同优先级的,大家轮流跑。
我个人的习惯是:在血压计里,把测量相关的任务(ADC采集、数据处理)设为最高优先级,把UI显示、按键扫描设为中等优先级,把日志记录、自检等设为低优先级。这样能保证测量实时性,又不耽误其他功能。
4.4 空闲任务与钩子函数
空闲任务,是RTOS里一个特殊任务。当所有其他任务都处于阻塞或挂起状态时,系统就会运行空闲任务。它的优先级是最低的(通常是0)。
空闲任务主要干三件事:
- 统计CPU利用率:通过计算空闲任务运行的时间占比,可以算出CPU有多忙
- 执行钩子函数:用户可以注册一个函数,在空闲任务里执行
- 进入低功耗模式:如果系统支持,空闲任务可以调用WFI指令让CPU休眠
钩子函数(Hook Function),说白了就是一个回调函数。你可以在空闲任务里挂一个钩子,让它每次空闲时执行一些操作。比如:
// FreeRTOS中的空闲钩子函数示例
void vApplicationIdleHook(void)
{
// 进入低功耗模式
__WFI();
// 或者做一些后台维护工作
// 比如:检查电池电量、更新看门狗等
}
注意:钩子函数里不能调用会导致任务阻塞的API(比如vTaskDelay、xQueueReceive等)。因为空闲任务本身不能阻塞,否则系统就死锁了。我曾经有个同事在钩子函数里调用了延时,结果系统一空闲就卡死,排查了半天才发现问题。
在血压计里,我通常把空闲钩子用来做两件事:
- 进入低功耗模式:测量间隙让CPU休眠,省电
- 统计CPU负载:通过空闲时间占比判断系统是否过载
你想想看,如果CPU负载超过80%,说明任务太多了,得优化。如果低于20%,说明CPU太闲了,可以考虑降低主频省电。这些数据,都是通过空闲任务统计出来的。
4.5 实战经验:调度策略的选择
最后,我分享几个实际项目中的经验:
- 不要把所有任务都设成同一个优先级:那样就退化成裸机轮询了,RTOS的优势全没了
- 中断服务函数要短:中断里只做标记,具体处理交给任务。我见过有人把整个数据处理都放在中断里,结果其他任务全被饿死
- 优先级不要超过7级:优先级太多,调度开销会变大。一般3~5级就够用了
- 空闲钩子函数里不要做复杂操作:它会影响系统进入低功耗的时机
嗯,关于任务调度,今天就聊到这儿。下一章咱们讲任务间通信——信号量、消息队列、事件标志组,这些才是RTOS的精髓。到时候我会结合血压计的实际场景,讲讲怎么用它们来同步数据、传递消息。
记住一句话:调度策略选对了,系统就成功了一半。