FreeRTOS 任务挂起与恢复
1. 先理解"挂起"
把任务想成正在工作的程序:挂起 就像按下暂停键,任务暂时不再参与调度;恢复则让它重新具备运行资格。任务恢复后不一定立刻运行,还要看其他就绪任务的优先级。
| 状态/操作 | 通俗理解 | 如何继续 |
|---|---|---|
| 就绪 | 任务准备好,等待 CPU | 等调度器选择 |
| 阻塞 | 任务在等时间或某个条件 | 等待时间到或条件满足,内核自动解除阻塞 |
| 挂起 | 任务被明确暂停 | 需要其他代码调用恢复 API |
| 删除 | 任务已被移除 | 不能恢复;需要重新创建任务 |
阻塞和挂起最大的区别是:阻塞通常由等待行为产生,并可由等待条件自动解除;挂起是显式暂停,必须显式恢复。
2. 挂起任务:vTaskSuspend()
中文含义
挂起指定任务,使它从调度相关列表中移除并进入挂起状态。在恢复之前,该任务不会被调度运行。
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
参数 xTaskToSuspend 是目标任务句柄。传入 NULL 表示挂起当前任务:
vTaskSuspend(taskHandle); /* 挂起指定任务 */
vTaskSuspend(NULL); /* 挂起当前任务 */
当前任务挂起后,调度器会切换到其他可运行任务。之后必须有别的任务或中断恢复它,否则它会一直保持挂起状态。
3. 在任务中恢复:vTaskResume()
中文含义
恢复之前通过 vTaskSuspend() 挂起的任务,使其重新进入就绪状态。
void vTaskResume(TaskHandle_t xTaskToResume);
该函数应在任务上下文中调用,不能在中断服务函数中调用:
vTaskResume(taskHandle); /* 恢复指定的挂起任务 */
调用后,目标任务成为就绪任务。如果它的优先级高于当前任务,调度器可能立即切换到它;否则它会等待以后获得 CPU。
恢复函数只对当前处于挂起状态的任务起作用。若任务当时并没有挂起,调用不会替它保存一个"以后再恢复"的请求。
4. 在中断中恢复:xTaskResumeFromISR()
中文含义
在中断服务函数(ISR)中恢复一个挂起任务。
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume);
该函数只能用于中断上下文。它返回是否需要在中断退出时进行任务切换:
| 返回值 | 含义 |
|---|---|
pdTRUE |
恢复后需要请求任务切换;应在中断退出前使用端口提供的 ISR 切换宏处理 |
pdFALSE |
当前不需要因此进行任务切换 |
典型结构如下,具体切换宏以所用 FreeRTOS 移植层为准:
void EXTI_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 清除或处理外部中断标志 */
xHigherPriorityTaskWoken = xTaskResumeFromISR(taskHandle);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
一个重要限制:恢复不是事件通知
xTaskResumeFromISR() 不会把"恢复请求"排队保存。如果中断发生时任务尚未挂起,这次调用不会留到任务以后挂起时再生效。于是可能出现:先发生中断恢复,后执行任务挂起,任务最终一直挂起的时序问题。
如果需求是"无论任务是否正在等待,都要记住这个事件",通常应考虑任务通知、信号量或队列,而不是把挂起/恢复当作事件传递机制。
5. 挂起和恢复的典型流程
例如,一个控制任务暂时不需要运行:
控制任务正在运行
↓ vTaskSuspend(taskHandle)
控制任务进入挂起状态,不再参与调度
↓ 某条件满足,其他任务调用 vTaskResume(taskHandle)
控制任务回到就绪状态
↓ 调度器按优先级选择任务
控制任务获得 CPU 后从被挂起的位置继续执行
如果恢复动作来自中断,则把 vTaskResume() 换成 xTaskResumeFromISR(),并根据返回值在中断退出时请求切换。
6. 配置要求与使用注意
启用任务挂起/恢复 API,需要在 FreeRTOSConfig.h 中配置:
#define INCLUDE_vTaskSuspend 1
启用中断中恢复 API,还需要:
#define INCLUDE_xTaskResumeFromISR 1
要点:
-
vTaskSuspend()可以挂起自身;传入NULL即可。自挂起后,任务要等其他上下文恢复才能继续。 -
vTaskResume()用于任务上下文;xTaskResumeFromISR()用于 ISR,不能混用。 -
挂起不是嵌套计数操作。无论对同一任务调用多少次
vTaskSuspend(),一次有效恢复就可以解除挂起。 -
任务恢复只表示进入就绪状态,不保证立即运行;调度器仍按优先级和配置决定执行顺序。
-
挂起任务不会释放它的栈或 TCB,也不会像删除任务那样销毁任务。
-
如果要可靠地传递中断事件或累计多次事件,不要依赖恢复调用;改用任务通知、信号量或队列等同步机制。
7. 本章函数汇总
| 函数 | 中文含义 | 主要用法 |
|---|---|---|
vTaskSuspend() |
挂起任务 | vTaskSuspend(handle) 挂起指定任务;vTaskSuspend(NULL) 挂起当前任务 |
vTaskResume() |
在任务中恢复挂起任务 | vTaskResume(handle);只能在任务上下文调用,且目标任务应处于挂起状态 |
xTaskResumeFromISR() |
在中断中恢复挂起任务 | xTaskResumeFromISR(handle);根据返回值决定是否在 ISR 退出时请求切换 |
portYIELD_FROM_ISR()(移植宏) |
在 ISR 退出时请求任务切换 | 通常传入 xTaskResumeFromISR() 的返回状态;具体名称依端口而异 |
portEND_SWITCHING_ISR()(部分端口宏) |
根据条件在 ISR 退出时请求切换 | 某些端口使用;查看项目移植层,不要和其他端口宏混用 |