3. 任务优先级与抢占:谁先跑,谁后跑,谁插队
各位做单片机开发的同行,今天我们来聊聊任务调度里最核心、也最容易出幺蛾子的部分——优先级与抢占。说白了,就是决定哪个任务先执行、哪个任务后执行,以及高优先级的任务能不能把正在运行的低优先级任务一脚踢开。
我在项目里见过太多因为优先级没搞明白导致的诡异故障:系统偶尔卡死、关键任务响应延迟、甚至莫名其妙地复位。嗯,这些问题十有八九都跟优先级和抢占机制有关。
3.1 优先级定义:给任务排个座次
优先级,就是给每个任务分配一个数字,用来表示它的“紧急程度”。数字越小优先级越高,还是数字越大优先级越高?这取决于你用的RTOS。我个人习惯用“数值越小优先级越高”的体系,比如FreeRTOS就是这样的。
| RTOS | 优先级范围 | 数值与优先级关系 |
|---|---|---|
| FreeRTOS | 0 ~ configMAX_PRIORITIES-1 | 数值越大,优先级越高 |
| uC/OS-III | 0 ~ OS_CFG_PRIO_MAX-1 | 数值越小,优先级越高 |
| RT-Thread | 0 ~ RT_THREAD_PRIORITY_MAX-1 | 数值越小,优先级越高 |
我曾经接手过一个项目,前任把FreeRTOS的优先级配置搞反了,最高优先级的任务设成了0(最低),结果系统响应慢得像蜗牛。排查了两天才发现这个低级错误……从那以后,我每次新建工程都会在注释里写明优先级规则。
3.2 静态优先级 vs 动态优先级
优先级怎么定?有两种方式:静态和动态。
静态优先级
任务创建时就定死了优先级,运行期间不能改。简单、确定性强,适合大多数嵌入式场景。
// FreeRTOS 静态优先级示例
xTaskCreate(vTask1, "Task1", 128, NULL, 3, NULL); // 优先级3
xTaskCreate(vTask2, "Task2", 128, NULL, 5, NULL); // 优先级5(更高)
动态优先级
任务运行过程中可以调整优先级。灵活性高,但增加了系统复杂度和不确定性。
// FreeRTOS 动态修改优先级
vTaskPrioritySet(xTaskHandle, 4); // 把任务优先级改为4
你想想看,动态优先级就像在高速公路上突然给一辆车“加塞权”,其他车(任务)的运行时间全被打乱了。调试起来非常头疼。
3.3 抢占式调度 vs 非抢占式调度
这是调度策略的核心区别。我画了一张图,帮你快速理解两者的差异。
非抢占式调度(协作式)
任务自己决定什么时候让出CPU。要么任务执行完毕,要么主动调用延时或等待函数。高优先级任务即使就绪了,也得等当前任务“发善心”。
- 优点: 共享资源不需要互斥保护,不会出现优先级反转
- 缺点: 一个任务死循环,整个系统就挂了
- 适用场景: 任务简单、可预测性强的系统
抢占式调度
高优先级任务就绪时,立即打断当前运行的低优先级任务。这是现代RTOS的主流方式。
- 优点: 实时性好,高优先级任务响应快
- 缺点: 需要处理临界区保护、优先级反转等问题
- 适用场景: 对实时性要求高的系统
3.4 优先级反转问题:一个经典坑
优先级反转,说白了就是高优先级任务被低优先级任务“拖住”了,中间还夹着一个中优先级任务捣乱。这是抢占式调度里最经典的坑,没有之一。
问题场景
假设有三个任务:Task_H(高优先级)、Task_M(中优先级)、Task_L(低优先级)。它们共享一个互斥信号量保护的资源。
- Task_L 获得信号量,开始访问共享资源
- Task_H 就绪,抢占 Task_L,但发现信号量被占,进入阻塞等待
- Task_L 恢复运行,但此时 Task_M 就绪,抢占 Task_L
- Task_M 运行,Task_H 还在等 Task_L 释放信号量
- 结果:高优先级的 Task_H 被中优先级的 Task_M 间接阻塞了
解决方案
主要有三种方法:
| 方法 | 原理 | 优缺点 |
|---|---|---|
| 优先级继承 | 低优先级任务临时提升到高优先级任务的优先级 | 实现简单,但可能引入死锁 |
| 优先级天花板 | 访问共享资源的任务优先级提升到最高可能优先级 | 实现更简单,但可能过度提升优先级 |
| 禁止中断/临界区 | 访问共享资源时关中断 | 简单粗暴,但影响中断响应 |
// FreeRTOS 使用互斥信号量(自带优先级继承)
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
void Task_L(void *pvParameters) {
xSemaphoreTake(xMutex, portMAX_DELAY); // 获得互斥量
// 访问共享资源
xSemaphoreGive(xMutex); // 释放互斥量
}
3.5 优先级设计实战建议
说了这么多理论,最后给几条我在项目中总结的优先级设计原则:
- 关键任务优先级最高: 比如安全相关的任务、控制环路任务
- 中断服务任务次之: 需要快速响应外设事件的任务
- 周期性任务居中: 数据采集、状态更新等
- 后台任务最低: 日志记录、统计等非实时任务
- 避免优先级数量过多: 一般5-8个优先级就够用了,太多反而增加调度开销