3、任务管理:任务创建与删除、任务优先级、任务状态与切换、空闲任务
各位同学,咱们今天聊聊任务管理。说实话,这是RTOS里最核心的部分,没有之一。你想想看,一个ECU里跑着几十个任务,谁先跑、谁后跑、谁该睡觉、谁该起床,全靠这套机制来调度。我在做发动机控制器项目时,就遇到过因为任务优先级没设计好,导致喷油和点火时序错乱,差点把台架给干废了。嗯,咱们今天就把这块彻底讲透。
3.1 任务的创建与删除
任务是什么?说白了,就是一个无限循环的C函数,加上一个独立的栈空间。我习惯把任务比作一个「独立的小人」——每个小人都有自己的私人物品(栈),有自己的工作内容(函数体),还有自己的编号(任务ID)。
创建任务时,我们通常用类似这样的API:
// 以FreeRTOS为例
TaskHandle_t xTaskHandle = NULL;
void vTaskFunction(void *pvParameters) {
// 任务体,通常是个死循环
for(;;) {
// 做点有意义的事
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 创建任务
xTaskCreate(
vTaskFunction, // 任务函数指针
"MyTask", // 任务名字(调试用)
256, // 栈深度(单位:字)
NULL, // 参数
3, // 优先级(数值越大优先级越高)
&xTaskHandle // 任务句柄
);
个人经验:我建议你在创建任务时,栈大小至少留出20%的余量。曾经有个同事,栈大小算得死死的,结果产品量产半年后,因为某个极端工况导致栈溢出,系统直接死机。排查了整整两周才找到原因——嗯,从那以后我都是栈大小x1.5起步。
删除任务相对简单,但要注意:千万别在中断服务函数里删除任务!我曾经犯过这个错,结果系统直接挂掉。正确的做法是发个信号量给某个任务,让它在任务上下文中执行删除操作。
// 删除任务
vTaskDelete(xTaskHandle);
警告:任务删除后,它占用的栈空间不会自动释放给其他任务使用。如果你频繁创建和删除任务,小心内存碎片!我建议用任务池(Task Pool)的方式来管理,而不是动态创建删除。
3.2 任务优先级
优先级这东西,说白了就是给任务排个座次。数值越大,优先级越高,越先得到CPU的使用权。但这里有个坑——优先级反转。
什么是优先级反转?我给你讲个真实案例。有一次我在做车身控制模块,有三个任务:高优先级的车窗控制、中优先级的空调控制、低优先级的仪表显示。它们共享一个I2C总线资源。正常情况下,高优先级任务应该很快完成。但有一天,低优先级任务先拿到了I2C总线锁,然后被中优先级任务抢占了CPU。高优先级任务想用I2C,发现锁被低优先级任务拿着,而低优先级任务又被中优先级任务堵着跑不了。结果高优先级任务活活被饿死!
解决这个问题的标准方案是优先级继承协议:
| 方案 | 原理 | 适用场景 |
|---|---|---|
| 优先级继承 | 低优先级任务持有锁时,临时提升到高优先级任务的优先级 | 资源竞争不激烈 |
| 优先级天花板 | 持有锁的任务,优先级直接提升到可能申请该锁的最高优先级 | 资源竞争激烈 |
| 关中断 | 访问共享资源时关中断,防止任务切换 | 临界区极短 |
核心要点:我个人习惯把任务优先级分成三档:紧急任务(优先级最高,响应时间<1ms)、普通任务(中等优先级,响应时间1-10ms)、后台任务(最低优先级,响应时间无所谓)。这样设计,优先级反转的影响范围可控。
3.3 任务状态与切换
任务有四种状态:运行态、就绪态、阻塞态、挂起态。你想想看,这就像人的四种状态:正在干活(运行)、排队等着干活(就绪)、在睡觉等闹钟(阻塞)、被关禁闭了(挂起)。
任务切换是怎么发生的?三种情况:
- 时间片耗尽:每个任务跑完一个时间片(通常是1-10ms),系统强制切换
- 主动让出:任务调用delay或等待信号量,主动放弃CPU
- 中断触发:中断服务函数里唤醒了更高优先级的任务
我记得有一次调试,发现某个任务总是卡住不跑。查了半天,原来是它一直在等一个永远不会来的信号量。这就是典型的「死锁」——两个任务互相等对方释放资源。解决方法是给等待加个超时:
// 带超时的信号量等待
if (xSemaphoreTake(xSemaphore, pdMS_TO_TICKS(100)) == pdTRUE) {
// 拿到信号量了,干活
} else {
// 超时了,做错误处理
// 我习惯在这里打印一条调试信息
}
避坑指南:我曾经在项目里遇到过一个诡异的问题——任务切换时栈指针被破坏。后来发现是中断服务函数里用了太多局部变量,把任务栈给踩了。解决方案:中断服务函数里尽量少用局部变量,或者把中断栈和任务栈分开。
3.4 空闲任务
空闲任务,说白了就是「没人干活时,系统自己在干嘛」。它是系统自动创建的,优先级最低,永远处于就绪态。当所有其他任务都阻塞或挂起时,空闲任务就跑起来了。
空闲任务通常做三件事:
- 统计CPU利用率:通过计算空闲任务运行的时间占比,反推出CPU的繁忙程度
- 清理任务资源:被删除的任务,它的栈空间和TCB由空闲任务来回收
- 进入低功耗模式:在电池供电的ECU里,空闲任务可以调用WFI指令让MCU休眠
你可能会问:空闲任务里能不能加自己的代码?当然可以!但要注意:
警告:不要在空闲任务里调用任何可能阻塞的API!比如vTaskDelay、xSemaphoreTake等。因为空闲任务一旦阻塞,系统就没有任务可跑了,直接死机。我见过有人把看门狗喂狗放在空闲任务里,结果系统负载高时空闲任务跑得少,看门狗超时复位——嗯,这是个经典错误。
最后说一句:空闲任务的优先级是0,你创建的任何用户任务优先级都要大于0。否则你的任务和空闲任务抢CPU,系统调度会乱套。
好了,任务管理这块就讲到这里。下一章咱们聊聊任务间通信——信号量、消息队列、事件标志组,这些都是实际项目中天天用的东西。