2. 任务状态机:就绪态、运行态、阻塞态、挂起态
做RTOS这么多年,我见过不少新手栽在任务状态管理上。说白了,任务调度就是让CPU在多个任务之间来回切换,而每个任务在任意时刻只能处于一种状态。理解这四种状态,是写好嵌入式程序的第一步。
2.1 四种核心状态
RTOS里的任务,就像我们日常生活中的状态切换。你想想看,一个人要么在干活(运行态),要么在等活干(就绪态),要么在等某个条件(阻塞态),要么被强制暂停(挂起态)。
| 状态 | 英文 | 含义 | 典型场景 |
|---|---|---|---|
| 就绪态 | Ready | 任务已准备好运行,等待CPU分配 | 任务创建后、延时结束、等待的资源可用 |
| 运行态 | Running | 任务正在占用CPU执行代码 | 当前被调度器选中的任务 |
| 阻塞态 | Blocked | 任务因等待某事件而暂停执行 | 等待信号量、消息队列、延时函数 |
| 挂起态 | Suspended | 任务被强制暂停,不参与调度 | 调试暂停、低功耗模式、任务被挂起API调用 |
核心要点:运行态在任何时刻只能有一个任务。其他三个状态可以有多个任务同时存在。
2.2 状态转换图
我习惯用一张图来理解状态转换。这张图我画过不下百遍,每次给团队新人培训都会拿出来讲。
┌─────────────┐
│ 创建任务 │
└──────┬──────┘
│
▼
┌─────────────┐
┌─────────┤ 就绪态 │◄──────────┐
│ └──────┬──────┘ │
│ │ │
│ 调度器选择 │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 运行态 │──────────┘
│ └──────┬──────┘ 时间片用完
│ │
│ 等待事件/资源
│ │
│ ▼
│ ┌─────────────┐
│ │ 阻塞态 │──────────┐
│ └─────────────┘ │
│ │ │
│ 事件到达/资源可用 │
│ │ │
└────────────────┘ │
│
┌───────┴───────┐
│ 挂起态 │
└───────────────┘
我的经验:实际项目中,80%的调度问题都出在「阻塞态→就绪态」这个转换上。我曾经在一个充电宝项目中,因为信号量释放时机不对,导致任务一直卡在阻塞态,整机死机。排查了整整两天才找到原因。
2.3 状态转换的触发条件
每种状态转换都有明确的触发条件。我整理了一张表,方便你对照理解。
| 转换路径 | 触发条件 | 常见API |
|---|---|---|
| 创建 → 就绪态 | 任务创建成功 | xTaskCreate() |
| 就绪态 → 运行态 | 调度器选中该任务 | vTaskStartScheduler() |
| 运行态 → 就绪态 | 时间片用完、被更高优先级任务抢占 | taskYIELD() |
| 运行态 → 阻塞态 | 调用阻塞API等待事件 | vTaskDelay()、xQueueReceive() |
| 阻塞态 → 就绪态 | 等待的事件到达或超时 | xSemaphoreGive()、超时返回 |
| 运行态 → 挂起态 | 调用挂起API | vTaskSuspend() |
| 挂起态 → 就绪态 | 调用恢复API | vTaskResume() |
注意:挂起态不能直接转换为阻塞态,必须先回到就绪态。我曾经见过有人试图在挂起态下等待信号量,结果任务永远醒不过来。
2.4 实际项目中的状态管理
在充电宝租借系统里,任务状态管理尤其重要。我给你举个例子:
// 充电宝租借系统的典型任务状态管理
void vBorrowTask(void *pvParameters) {
// 任务创建后处于就绪态
for(;;) {
// 等待用户扫码租借事件
// 这里任务从运行态进入阻塞态
xSemaphoreTake(xBorrowSemaphore, portMAX_DELAY);
// 事件到达,任务回到就绪态,然后被调度运行
// 执行租借逻辑
processBorrowRequest();
// 处理完成后,循环等待下一个事件
}
}
void vMonitorTask(void *pvParameters) {
for(;;) {
// 每100ms检查一次设备状态
vTaskDelay(pdMS_TO_TICKS(100));
// 检查电量、通信状态等
checkDeviceStatus();
}
}
你看,vBorrowTask大部分时间都处于阻塞态,等待用户操作。而vMonitorTask则周期性地在就绪态和阻塞态之间切换。这种设计让CPU资源得到充分利用。
2.5 常见陷阱与避坑指南
做项目这么多年,我总结了几条经验:
- 不要长时间占用CPU:运行态的任务如果执行时间过长,会阻塞其他任务。我建议每个任务的执行时间控制在1ms以内。
- 注意优先级反转:低优先级任务占用资源,高优先级任务等待,中间优先级任务抢跑。嗯,这个问题在充电宝项目中特别常见。
- 挂起态要谨慎使用:我曾经在调试时挂起了一个关键任务,结果忘了恢复,整机功能瘫痪。后来我养成了习惯,每次挂起都加一个定时器做超时保护。
- 阻塞态的超时设置:永远不要用
portMAX_DELAY除非你确定事件一定会到达。否则任务会永远卡死。
核心总结:任务状态机是RTOS调度的基础。理解这四种状态和它们的转换关系,你就能写出稳定、高效的嵌入式程序。记住,好的任务设计,80%的时间都在阻塞态,只有20%的时间在运行态。
下一章我会讲任务优先级和调度策略,到时候会结合充电宝租借系统的实际案例,给你展示如何设计一个完整的任务调度方案。