4. 任务状态转换
任务目标
编写一个基于FreeRTOS的演示程序,创建两个任务,通过任务挂起(vTaskSuspend)、恢复(vTaskResume)和延时(vTaskDelay)操作,使用串口打印各任务的状态转换过程,从而直观理解任务在就绪(Ready)、运行(Running)、阻塞(Blocked)和挂起(Suspended)四种状态间的切换逻辑。
核心知识点
- 就绪态:任务具备运行条件,等待调度器分配CPU。
- 运行态:任务正在占用CPU执行。
- 阻塞态:任务因调用延时、等待信号量/队列等而暂时无法运行。
- 挂起态:任务被vTaskSuspend主动暂停,只能通过vTaskResume恢复。
硬件/环境要求
- 开发板:STM32F407(或其他Cortex-M4板卡)
- IDE:STM32CubeIDE 或 Keil MDK
- FreeRTOS版本:V10.x(CMSIS-OS v1封装或原生API均可)
- 串口:USART1,波特率115200,用于打印状态信息
完整实现代码
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/* 串口发送字符串(需根据实际硬件实现) */
extern void Debug_SendString(const char* str);
/* 任务句柄 */
TaskHandle_t xTask1Handle = NULL;
TaskHandle_t xTask2Handle = NULL;
/* 任务1:演示挂起与恢复 */
void vTask1(void *pvParameters)
{
const char *pcTaskName = "Task1";
TickType_t xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
Debug_SendString("[Task1] Running (Ready->Running)\r\n");
/* 延时500ms,进入阻塞态 */
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(500));
Debug_SendString("[Task1] Blocked (delay 500ms)\r\n");
/* 运行2次后挂起自己 */
static int count = 0;
count++;
if( count == 2 )
{
Debug_SendString("[Task1] Suspending self\r\n");
vTaskSuspend(NULL); /* 挂起自身,进入挂起态 */
Debug_SendString("[Task1] Resumed by Task2\r\n");
count = 0;
}
}
}
/* 任务2:控制任务1的挂起与恢复 */
void vTask2(void *pvParameters)
{
const char *pcTaskName = "Task2";
TickType_t xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
Debug_SendString("[Task2] Running\r\n");
/* 延时1秒 */
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
/* 每2秒挂起Task1一次,再延时1秒后恢复 */
Debug_SendString("[Task2] Suspending Task1\r\n");
vTaskSuspend(xTask1Handle);
vTaskDelay(pdMS_TO_TICKS(1000));
Debug_SendString("[Task2] Resuming Task1\r\n");
vTaskResume(xTask1Handle);
}
}
int main(void)
{
/* 硬件初始化(时钟、GPIO、串口等) */
Hardware_Init();
/* 创建两个任务 */
xTaskCreate(vTask1, "Task1", 128, NULL, 1, &xTask1Handle);
xTaskCreate(vTask2, "Task2", 128, NULL, 2, &xTask2Handle);
/* 启动调度器 */
vTaskStartScheduler();
/* 正常不会执行到这里 */
for( ;; );
}
代码说明
- vTaskSuspend(NULL):传入NULL表示挂起当前正在运行的任务自身。
- vTaskResume(xTask1Handle):恢复指定句柄的任务,使其从挂起态回到就绪态。
- vTaskDelayUntil:实现固定频率的延时,任务在延时期间处于阻塞态。
- 任务2优先级高于任务1,因此任务2运行时能抢占任务1。
运行与验证方法
- 编译并下载程序到开发板。
- 打开串口助手(115200-8-N-1),观察输出日志。
- 预期输出序列(关键节点):
[Task1] Running (Ready->Running)
[Task1] Blocked (delay 500ms)
[Task2] Running
[Task1] Running (Ready->Running)
[Task1] Blocked (delay 500ms)
[Task2] Suspending Task1
[Task1] Suspending self
[Task2] Resuming Task1
[Task1] Resumed by Task2
通过判定标准:
- 串口能观察到Task1在“Running”和“Blocked”间周期性切换。
- Task2执行“Suspending Task1”后,Task1不再输出任何信息,证明其已进入挂起态。
- Task2执行“Resuming Task1”后,Task1恢复输出,证明其从挂起态回到就绪态并被调度运行。
- 整个过程中系统无崩溃、无卡死,两个任务持续交替运行。
状态转换可视化
常见问题排查
- 任务1挂起后无法恢复:检查vTaskResume传入的句柄是否为xTask1Handle,且任务1确实处于挂起态而非阻塞态。
- 串口无输出:确认Debug_SendString已正确映射到实际串口发送函数,且串口初始化在任务创建前完成。
- 任务优先级导致Task1几乎不运行:若Task2使用vTaskDelayUntil且优先级较高,Task1仍能获得运行时间片,因为Task2会周期性阻塞。