1. 抢占式调度概述
大家好,我是你们的讲师。今天咱们来聊聊RTOS里最核心的一个概念——抢占式调度。说实话,我做了十几年嵌入式开发,见过太多因为调度策略选错而翻车的项目。嗯,咱们先从最基础的说起。
什么是抢占式调度?
抢占式调度,说白了就是:高优先级的任务可以随时打断低优先级任务的执行。你想想看,就像你在写代码时,老板突然过来让你处理紧急bug——你得立刻停下手头的工作,先搞定bug再说。
在RTOS里,每个任务都有一个优先级。调度器会根据优先级决定谁先跑。抢占式调度意味着:
- 任务A正在运行,优先级是3
- 任务B就绪了,优先级是5(更高)
- 调度器立刻暂停任务A,切换到任务B
- 任务B跑完或阻塞后,再回来继续任务A
关键点:这种切换不是任务主动让出CPU,而是调度器强制执行的。这就是“抢占”二字的含义。
与协作式调度的对比
协作式调度就完全不一样了。我记得刚入行时,第一个项目用的就是协作式调度。那时候觉得挺简单,后来才发现坑不少。
| 特性 | 抢占式调度 | 协作式调度 |
|---|---|---|
| 任务切换时机 | 任意时刻(由调度器决定) | 任务主动让出CPU时 |
| 实时性 | 高,可预测 | 低,依赖任务自觉 |
| 代码复杂度 | 较高(需处理临界区) | 较低 |
| 资源开销 | 需要定时器中断支持 | 无需额外硬件 |
| 典型应用 | 工业控制、汽车电子 | 简单嵌入式系统 |
协作式调度有个致命问题:如果某个任务不主动让出CPU,整个系统就卡死了。我曾经在一个物联网项目里遇到过——某个传感器驱动里有个死循环,结果所有任务都跑不了。排查了整整两天才找到问题。
避坑指南:如果你用协作式调度,一定要确保每个任务里没有长时间阻塞的操作。我曾经见过有人把延时函数写在协作式任务里,结果整个系统响应延迟从1ms变成了500ms。
抢占式调度的核心优势
为什么现在绝大多数RTOS都用抢占式调度?说白了,就三个字:实时性。
具体来说:
- 响应时间可预测:高优先级任务能在确定时间内得到响应。比如你按下急停按钮,系统必须在10ms内做出反应。抢占式调度能保证这一点。
- CPU利用率高:低优先级任务可以利用空闲时间跑,高优先级任务随时可以打断。不像协作式,低优先级任务跑着,高优先级任务只能干等。
- 系统设计更灵活:你可以把紧急任务设高优先级,普通任务设低优先级。调度器自动帮你管理。
个人经验:我在做电机控制项目时,电流环必须每100μs执行一次。如果用协作式调度,其他任务稍微拖一下,电流环就超时了。换成抢占式后,电流环设最高优先级,其他任务随便跑,完全不影响。
你可能会问:抢占式调度有没有缺点?当然有。最大的问题是资源共享的复杂性。因为任务随时可能被中断,访问共享数据时必须加锁或关中断。否则就会出现数据不一致的问题。
举个例子:
// 两个任务共享一个全局变量
int shared_data = 0;
// 任务A:写入数据
void task_A(void) {
shared_data = 100; // 刚执行完这行,被任务B抢占了
// 此时shared_data = 100,但任务B不知道
}
// 任务B:读取数据
void task_B(void) {
if (shared_data == 100) {
// 执行某些操作
}
}
嗯,这里要注意。如果任务A刚写完shared_data就被任务B抢占了,任务B读到的值是100。但如果任务A只写了一半呢?比如shared_data是个32位变量,而CPU是16位的——那就可能读到一半新值一半旧值。这就是典型的竞态条件。
所以,抢占式调度虽然强大,但需要开发者对临界区保护有清晰的认识。我建议初学者先从简单的协作式开始理解调度原理,再逐步过渡到抢占式。
好了,这一章就讲到这里。下一章咱们深入看看抢占式调度的具体实现——任务控制块和就绪队列是怎么设计的。