第三章:任务管理——任务创建、删除、挂起与恢复,任务优先级设计,任务状态转换
各位好,我是老张。在嵌入式系统里摸爬滚打了十几年,RTOS这块算是我的老本行。今天咱们聊聊任务管理,这是RTOS的核心中的核心。说白了,你写的代码最终都要变成一个个任务,交给系统去调度。任务管理搞不明白,后面写再多代码也是白搭。
3.1 任务创建与删除
任务创建,就是告诉RTOS:“嘿,我这有一段代码,你帮我跑起来。” 不同的RTOS,API名字不一样。FreeRTOS里叫 xTaskCreate,uC/OS里叫 OSTaskCreate。但核心参数都差不多:任务函数指针、任务名字、栈大小、参数、优先级、任务句柄。
我个人习惯,创建任务时栈大小一定要留够余量。我在一个血压计项目里吃过亏,当时为了省RAM,栈大小算得死死的。结果产品在低温环境下偶尔死机,查了三天,最后发现是栈溢出。从那以后,我每个任务至少多给20%的栈空间。
重要提醒:任务栈大小不是算出来的,是测出来的。用 uxTaskGetStackHighWaterMark() 这类API去监控实际使用量,再决定最终值。
任务删除相对简单。调用 vTaskDelete(NULL) 可以删除自己,传任务句柄可以删除其他任务。但要注意,删除任务时,它占用的堆内存会被回收,但栈空间不会自动释放——嗯,这里要小心,别搞出内存泄漏。
// 创建任务示例(FreeRTOS)
TaskHandle_t xTaskHandle = NULL;
void vTaskCode(void *pvParameters) {
while(1) {
// 血压测量逻辑
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void AppInit(void) {
xTaskCreate(
vTaskCode, // 任务函数
"BP_Measure", // 任务名字
256, // 栈大小(单位:字)
NULL, // 参数
3, // 优先级
&xTaskHandle // 任务句柄
);
}
3.2 任务挂起与恢复
挂起和恢复,是任务管理的“暂停键”和“继续键”。挂起一个任务,它就不再参与调度,直到被恢复。这在血压计里很常用——比如用户按下“开始测量”按钮,你挂起所有非关键任务,只保留测量和显示任务,保证实时性。
我记得有一次,客户反馈血压计在测量过程中偶尔会“卡住”。我排查后发现,是某个低优先级任务在测量期间被唤醒,抢占了CPU时间。解决方案很简单:测量开始时挂起那个任务,测量结束后恢复它。
小技巧:挂起任务时,最好用 vTaskSuspendAll() 挂起所有任务,而不是一个个挂。但要注意,这个API不能在中断服务函数里调用。
// 挂起与恢复示例
void StartMeasurement(void) {
// 挂起非关键任务
vTaskSuspend(xNonCriticalTaskHandle);
// 开始血压测量
StartBPMeasurement();
}
void EndMeasurement(void) {
// 恢复非关键任务
vTaskResume(xNonCriticalTaskHandle);
// 显示测量结果
DisplayResult();
}
3.3 任务优先级设计
优先级设计,是RTOS应用里最考验功力的地方。设计得好,系统稳如泰山;设计得不好,各种诡异问题层出不穷。
我一般遵循几个原则:
- 实时性要求高的任务,优先级高。比如血压测量中的AD采样任务,必须高优先级。
- 计算密集型任务,优先级低。比如数据处理、滤波算法,可以慢慢算。
- 避免优先级反转。这个坑我踩过。曾经有个项目,低优先级任务占着共享资源不放,高优先级任务一直等,系统性能直接崩了。后来用优先级继承才解决。
| 任务类型 | 优先级 | 说明 |
|---|---|---|
| AD采样 | 最高(5) | 实时性要求最高,不能丢数据 |
| 按键扫描 | 高(4) | 用户交互,响应要快 |
| 显示刷新 | 中(3) | 周期性刷新,可容忍少量延迟 |
| 数据处理 | 低(2) | 计算量大,优先级低 |
| 空闲任务 | 最低(0) | 系统空闲时运行 |
警告:不要把所有任务都设成同一个优先级。那样RTOS就退化成轮询了,失去了使用RTOS的意义。我曾经见过一个团队,所有任务优先级都是3,结果系统响应时间完全不可控。
3.4 任务状态转换
任务在RTOS里有几种状态:运行态、就绪态、阻塞态、挂起态。你想想看,一个任务从创建到销毁,就在这几个状态之间跳来跳去。
- 运行态:任务正在使用CPU。单核系统里,同一时刻只有一个任务在运行。
- 就绪态:任务准备好了,但CPU被别的任务占着。它在等调度器给它机会。
- 阻塞态:任务在等某个事件,比如延时到期、信号量可用、队列有数据。这时候它不消耗CPU。
- 挂起态:被
vTaskSuspend()挂起,除非被恢复,否则永远不参与调度。
为什么会这样设计?说白了,就是为了高效利用CPU。任务在等I/O的时候,CPU别闲着,赶紧去干别的活。我在一个多任务血压计项目里,用状态转换图给新同事讲解,他们一下子就明白了。
核心要点:任务状态转换是RTOS调度的基础。理解了这个,你就能预测系统在不同场景下的行为。比如,高优先级任务进入阻塞态,调度器会立刻切换到下一个就绪态的高优先级任务。
嗯,任务管理这块内容不少,但都是基本功。我建议你在实际项目里多练练,特别是优先级设计和状态转换,光看书是学不会的。下一章咱们聊聊任务间通信,那又是另一片天地了。