4、任务调度策略:前后台系统 vs RTOS、任务优先级分配、时间片轮转
说到任务调度,这可能是嵌入式开发里最让我感慨的一个话题了。我刚入行那会儿,用的就是典型的前后台系统——一个死循环里塞一堆函数,再加个定时器中断。那时候觉得,嗯,够用了。直到有一次做电子标签项目,屏幕刷新和无线通信抢时间,搞得标签时不时卡顿一下,我才意识到:调度策略选不对,后面全是坑。
前后台系统:简单但脆弱
前后台系统,说白了就是「主循环 + 中断」。主循环是后台,中断是前台。代码结构大概长这样:
void main(void)
{
while(1)
{
Task_KeyScan(); // 按键扫描
Task_Display(); // 屏幕刷新
Task_CommCheck(); // 通信检测
Task_Update(); // 数据更新
}
}
void TIM2_IRQHandler(void)
{
// 定时中断,处理紧急任务
Flag_1ms = 1;
}
这种结构的好处是简单、直观、资源占用少。我在做低功耗电子标签时,初期就用的这套方案。但问题也很明显——所有任务串行执行,一个任务卡住了,后面的全得等着。
前后台系统适合什么场景?任务少、实时性要求不高、代码量小的项目。比如简单的温湿度标签、单色屏的电子价签。但一旦任务多了,或者某个任务有严格的响应时间要求,前后台系统就有点力不从心了。
RTOS:让任务各司其职
RTOS(实时操作系统)的核心思想,就是把任务拆成独立的线程,由调度器统一管理。每个任务有自己的栈空间、优先级、状态。调度器根据优先级和时间片,决定谁跑、谁等。
我个人的习惯是,只要项目里同时存在三个以上需要独立管理的任务,我就会考虑上RTOS。比如电子标签里,既要处理无线通信,又要刷新屏幕,还要响应按键、管理电池电量——这时候用RTOS,代码结构会清晰很多。
常见的RTOS有FreeRTOS、uC/OS、RT-Thread等。以FreeRTOS为例,创建任务很简单:
xTaskCreate(
Task_Display, // 任务函数
"Display", // 任务名
256, // 栈大小
NULL, // 参数
2, // 优先级
NULL // 任务句柄
);
xTaskCreate(
Task_Comm, // 通信任务
"Comm",
512,
NULL,
3, // 优先级更高
NULL
);
vTaskStartScheduler(); // 启动调度器
你看,每个任务独立运行,互不干扰。通信任务优先级高,有数据来了立刻处理;显示任务优先级低,空闲时再刷新。这就是RTOS的魅力。
任务优先级分配:别让高优先级饿死低优先级
优先级分配,说白了就是给任务排个序。谁最紧急,谁优先级最高。但这里有个陷阱——优先级反转。
我记得有一次做多标签同步显示的项目,三个任务:通信接收(高优先级)、数据处理(中优先级)、屏幕刷新(低优先级)。通信任务拿到了一个互斥锁,但被数据处理任务抢占了CPU。结果屏幕刷新任务想拿锁,拿不到,一直阻塞。通信任务也拿不到CPU,因为数据处理任务一直在跑。最后整个系统卡死了。
怎么解决?我建议用优先级继承或者使用无锁编程。FreeRTOS的互斥量本身就支持优先级继承,能有效避免这个问题。
另外,优先级分配有个经验法则:
| 优先级 | 任务类型 | 示例 |
|---|---|---|
| 最高 | 硬实时任务 | 中断处理、通信接收 |
| 中高 | 软实时任务 | 按键响应、数据解析 |
| 中低 | 周期性任务 | 屏幕刷新、状态检测 |
| 最低 | 后台任务 | 日志记录、空闲处理 |
你想想看,如果屏幕刷新和通信接收优先级一样高,会怎样?通信数据可能丢包。所以,优先级一定要拉开差距,但又不能差距太大,否则低优先级任务可能永远得不到CPU。
时间片轮转:公平但不绝对
时间片轮转,是RTOS里一种常用的调度方式。同优先级的任务,轮流占用CPU,每个任务跑一个固定时间片(比如10ms),时间到了就切换下一个。
这种方式的优点是公平。每个任务都有机会跑,不会出现某个任务长期霸占CPU的情况。但缺点也很明显——上下文切换有开销。每次切换都要保存寄存器、恢复栈指针,如果时间片太短,CPU时间全浪费在切换上了。
我建议时间片设置在5ms到20ms之间。太短了切换频繁,太长了实时性变差。具体多少,得看你的任务量和CPU主频。
在FreeRTOS中,时间片轮转是默认开启的。你只需要把多个任务设为同一优先级,调度器会自动轮转:
// 三个任务同一优先级
xTaskCreate(Task_A, "A", 256, NULL, 1, NULL);
xTaskCreate(Task_B, "B", 256, NULL, 1, NULL);
xTaskCreate(Task_C, "C", 256, NULL, 1, NULL);
// 调度器会轮流执行A、B、C,每个任务跑一个时间片
但要注意,时间片轮转不适用于硬实时任务。如果一个任务必须在10ms内响应,那就别把它和别的任务放同一优先级。给它单独的高优先级,用抢占式调度。
如何选择:前后台还是RTOS?
这个问题没有标准答案。我个人的判断标准是:
- 任务数 ≤ 3,且没有严格实时要求 → 前后台系统。简单、省资源、调试方便。
- 任务数 ≥ 4,或有多个中断源 → RTOS。管理复杂,但代码可维护性好。
- 有硬实时任务(如电机控制、无线通信) → RTOS + 高优先级任务。
- 超低功耗场景 → 前后台系统。RTOS的tickless模式虽然省电,但配置复杂。
说白了,没有银弹。每个项目都有自己的特点。我见过有人用前后台系统跑出了RTOS的效果,也见过有人用RTOS把简单项目搞复杂了。关键是你得理解每种方案的优缺点,然后根据实际情况做取舍。