3. ThreadX 任务状态机:六态转换与 API 触发条件
任务状态机,说白了就是 RTOS 调度器的「交通规则」。每个任务在生命周期里,都会在几个状态之间跳来跳去。我刚开始用 ThreadX 时,总觉得状态图是理论派画出来唬人的,直到有一次产品死锁,查了两天才发现是一个任务卡在了「挂起态」没出来……嗯,从那以后我再看状态机,就像看地图一样认真。
ThreadX 的任务状态比很多 RTOS 多两个——休眠态和完成态。一共六个状态,咱们一个一个捋清楚。
3.1 六态总览
先看这张图,你脑子里就有个框架了:
3.2 六个状态,逐个拆解
3.2.1 就绪态(Ready)
任务已经准备好运行,只等调度器「翻牌子」。所有就绪任务按优先级排在一个链表里——就绪链表。调度器每次从这里挑一个优先级最高的去执行。
- 进入条件:任务创建后自动进入;从挂起/休眠/终止/完成态被恢复时也会进入。
- 离开条件:调度器选中它,变成运行态。
- 相关 API:无直接 API,由调度器内部管理。
3.2.2 运行态(Running)
CPU 正在执行这个任务的代码。同一时刻,单核芯片上只有一个任务处于运行态。你想想看,这就是「皇帝」状态,但随时可能被「篡位」。
- 进入条件:调度器从就绪链表中选中当前最高优先级任务。
- 离开条件:时间片用完、被更高优先级任务抢占、主动挂起/休眠/终止/完成。
- 相关 API:无直接 API,由调度器触发。
3.2.3 挂起态(Suspended)
任务被「人为暂停」,不参与调度。挂起态是主动的、显式的暂停,不是等信号量那种阻塞。我习惯用它来做「任务级别的暂停/恢复」控制。
- 进入条件:调用
tx_thread_suspend()。 - 离开条件:调用
tx_thread_resume()。 - 相关 API:
UINT tx_thread_suspend(TX_THREAD *thread_ptr); UINT tx_thread_resume(TX_THREAD *thread_ptr);
3.2.4 休眠态(Sleeping)
任务主动「睡一会儿」,时间到了自动醒来。这是 ThreadX 实现低功耗的关键——当所有任务都休眠时,系统可以进入深度睡眠模式。
- 进入条件:调用
tx_thread_sleep()。 - 离开条件:休眠计时到期,自动回到就绪态。
- 相关 API:
UINT tx_thread_sleep(ULONG timer_ticks);
3.2.5 终止态(Terminated)
任务被「强制结束」,但它的控制块和栈还在。终止态不是删除——任务还可以被重置后重新运行。这有点像「暂停营业,但店铺没拆」。
- 进入条件:调用
tx_thread_terminate()。 - 离开条件:调用
tx_thread_reset()回到就绪态。 - 相关 API:
UINT tx_thread_terminate(TX_THREAD *thread_ptr); UINT tx_thread_reset(TX_THREAD *thread_ptr);
3.2.6 完成态(Completed)
任务的入口函数执行完毕,自然返回。这是 ThreadX 独有的状态——任务「寿终正寝」,但控制块保留,可以重置复用。
- 进入条件:任务函数执行 return 语句。
- 离开条件:调用
tx_thread_reset()回到就绪态。 - 相关 API:无直接进入 API,由任务返回自动触发;离开使用
tx_thread_reset()。
3.3 状态转换速查表
| 当前状态 | 触发 API / 事件 | 下一状态 | 备注 |
|---|---|---|---|
| 就绪态 | 调度器选中 | 运行态 | 自动发生,无需 API |
| 运行态 | 时间片用完 / 被抢占 | 就绪态 | 调度器内部触发 |
| 运行态 | tx_thread_suspend() | 挂起态 | 不能自己挂自己 |
| 挂起态 | tx_thread_resume() | 就绪态 | 恢复后重新参与调度 |
| 运行态 | tx_thread_sleep() | 休眠态 | 参数为 tick 数 |
| 休眠态 | 计时到期 | 就绪态 | 自动唤醒 |
| 运行态 | tx_thread_terminate() | 终止态 | 强制结束,资源保留 |
| 终止态 | tx_thread_reset() | 就绪态 | 重置后从头运行 |
| 运行态 | 函数 return | 完成态 | 自然结束 |
| 完成态 | tx_thread_reset() | 就绪态 | 复用任务控制块 |
3.4 几个容易混淆的点
挂起态 vs 休眠态:挂起是「别人叫醒你」,休眠是「闹钟叫醒你」。挂起需要另一个任务或 ISR 调用 resume,休眠是内核定时器自动唤醒。我一般在需要外部事件触发恢复时用挂起,在周期性任务中用休眠。
终止态 vs 完成态:终止是「被枪毙」,完成是「寿终正寝」。但两者都能通过 reset 复活。区别在于——终止态的任务可能在任何代码位置被掐断,完成态的任务已经执行完所有代码。如果你在终止态做 reset,任务会从入口函数重新开始;完成态也一样。
能不能自己挂起自己?不能。tx_thread_suspend() 不能挂起当前正在运行的任务。如果你想让当前任务暂停,用 tx_thread_sleep() 或者等待一个信号量/队列。我曾经看到新手在任务里写 tx_thread_suspend(tx_thread_identify()),结果直接触发断言——嗯,ThreadX 内部会检查这个。
tx_thread_info_get() 可以实时查看任务当前状态。我在调试时会在串口终端每秒打印一次所有任务的状态,一眼就能看出哪个任务「卡住」了。
3.5 状态机与低功耗的关系
ThreadX 的低功耗模式,说白了就是利用「休眠态」来做文章。当所有任务都处于休眠态或挂起态时,就绪链表为空,调度器无事可做。这时 ThreadX 会调用一个钩子函数——tx_low_power_enter(),让芯片进入 STOP 或 STANDBY 模式。
我在一个项目中,把不用的任务全部挂起,只留一个主任务做周期性采集。主任务采集完数据就休眠 100ms,这 100ms 里 CPU 几乎不耗电。整个系统的平均功耗从 30mA 降到了 0.5mA——你想想看,电池寿命从 3 天变成了 6 个月。
关于低功耗的具体配置,咱们后面有专门章节细讲。这里你只要记住:状态机是低功耗的基础,休眠态是省电的开关。