4. 任务状态切换与阻塞

任务说明

本案例在FreeRTOS中创建两个任务,通过vTaskDelay()使任务主动释放CPU进入阻塞态,并借助uxTaskGetSystemState()读取各任务实时状态,观察任务在运行态→阻塞态→就绪态之间的切换过程。目标是通过实际代码理解任务状态迁移的触发条件与调度行为。

核心实现


#include "FreeRTOS.h"
#include "task.h"
#include "stdio.h"

/* 任务句柄 */
TaskHandle_t xTask1Handle = NULL;
TaskHandle_t xTask2Handle = NULL;

/* 任务1:运行后主动阻塞 */
void vTask1(void *pvParameters)
{
    for (;;)
    {
        /* 进入阻塞态,让出CPU */
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

/* 任务2:运行后主动阻塞 */
void vTask2(void *pvParameters)
{
    for (;;)
    {
        vTaskDelay(pdMS_TO_TICKS(300));
    }
}

/* 打印任务状态 */
void vPrintTaskStatus(void)
{
    TaskStatus_t xTaskStatusArray[2];
    UBaseType_t uxArraySize = 2;
    UBaseType_t uxIndex;

    /* 获取系统任务状态快照 */
    uxArraySize = uxTaskGetSystemState(xTaskStatusArray,
                                       uxArraySize,
                                       NULL);
    printf("--- Task States ---\r\n");
    for (uxIndex = 0; uxIndex < uxArraySize; uxIndex++)
    {
        const char *pcState;

        switch (xTaskStatusArray[uxIndex].eCurrentState)
        {
            case eRunning:   pcState = "Running";   break;
            case eReady:     pcState = "Ready";     break;
            case eBlocked:   pcState = "Blocked";   break;
            case eSuspended: pcState = "Suspended"; break;
            case eDeleted:   pcState = "Deleted";   break;
            default:         pcState = "Invalid";   break;
        }

        printf("Task: %-10s State: %s\r\n",
               xTaskStatusArray[uxIndex].pcTaskName,
               pcState);
    }
}

/* 监控任务:周期性打印状态 */
void vMonitorTask(void *pvParameters)
{
    for (;;)
    {
        vPrintTaskStatus();
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

int main(void)
{
    /* 创建两个工作任务 */
    xTaskCreate(vTask1, "Task1", 128, NULL, 1, &xTask1Handle);
    xTaskCreate(vTask2, "Task2", 128, NULL, 1, &xTask2Handle);

    /* 创建监控任务,优先级最高以便抢占查看状态 */
    xTaskCreate(vMonitorTask, "Monitor", 128, NULL, 2, NULL);

    /* 启动调度器 */
    vTaskStartScheduler();

    /* 正常不会执行到这里 */
    for (;;);
    return 0;
}

状态迁移分析

两个工作任务优先级相同(均为1),监控任务优先级最高(2)。调度器启动后:

  • 监控任务最先进入运行态,调用vPrintTaskStatus()打印当前状态快照;
  • 当监控任务调用vTaskDelay(100ms)进入阻塞态后,Task1和Task2按时间片轮转进入运行态;
  • Task1运行后立即调用vTaskDelay(200ms),从运行态切换到阻塞态;
  • Task2同样运行后调用vTaskDelay(300ms)进入阻塞态;
  • 当两个任务均阻塞时,空闲任务运行;
  • 延时到期后,任务从阻塞态回到就绪态,等待调度器分配CPU。

状态观测点

任务状态切换时序观测 t=0 t=600ms t=200ms t=400ms Monitor Running Blocked Running Blocked Running Blocked Running Blocked Task1 Ready Running Blocked Ready Running Blocked Ready Running Blocked Task2 Ready Ready Running Blocked Ready Running Blocked Ready Running Running Blocked Ready Monitor Blocked

编译与运行


# 在FreeRTOS Demo工程中添加上述源文件
# 确保FreeRTOSConfig.h中配置:
#   configUSE_TRACE_FACILITY    1   (使能uxTaskGetSystemState)
#   configUSE_STATS_FORMATTING_FUNCTIONS  1
#   configSUPPORT_DYNAMIC_ALLOCATION      1

# 编译(以GCC/CMake为例)
cmake -B build
cmake --build build

# 运行(模拟器或开发板)
./build/rtos_demo

验证方法

运行程序后,通过串口终端观察vPrintTaskStatus()周期性输出的状态信息。判定标准:

  • 输出中能同时看到Blocked和Ready状态的任务;
  • Task1和Task2不会同时处于Running状态;
  • Monitor任务每次打印时,至少有一个工作任务处于Blocked状态(因为工作任务的延时时间短于Monitor的打印周期,当Monitor从阻塞中唤醒时,工作任务可能已回到Ready状态);
  • 状态名称与代码中vTaskDelay()设置的延时逻辑一致:Task1阻塞约200ms,Task2阻塞约300ms。

若串口输出中能看到任务状态在Running→Blocked→Ready之间循环切换,且无系统崩溃或断言失败,则本案例验证通过。