4. 任务状态转换监控

任务状态转换监控

在RTOS应用中,任务状态转换是理解系统行为、定位优先级反转或死锁问题的关键。本案例基于FreeRTOS,通过任务切换钩子函数(vApplicationTaskSwitchHook)与运行时状态统计,实现一个轻量级的状态转换监控器,记录每个任务的状态变化序列及停留时间。

核心实现

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* 状态监控数据结构 */
#define MAX_TASKS       8
#define MAX_TRANSITIONS 32

typedef struct {
    char taskName[configMAX_TASK_NAME_LEN];
    eTaskState lastState;
    TickType_t stateEnterTime;
    uint32_t stateCount[eRunning + 1];  /* 各状态累计次数 */
    TickType_t stateTotalTime[eRunning + 1]; /* 各状态累计时间 */
} TaskMonitor_t;

typedef struct {
    TickType_t time;
    char taskName[configMAX_TASK_NAME_LEN];
    eTaskState fromState;
    eTaskState toState;
} TransitionRecord_t;

static TaskMonitor_t taskMonitors[MAX_TASKS];
static TransitionRecord_t transitionLog[MAX_TRANSITIONS];
static volatile uint32_t transitionCount = 0;
static volatile uint32_t monitorTaskCount = 0;

/* 查找或注册任务监控条目 */
static TaskMonitor_t* findOrCreateMonitor(const char* name) {
    for (uint32_t i = 0; i < monitorTaskCount; i++) {
        if (strcmp(taskMonitors[i].taskName, name) == 0) {
            return &taskMonitors[i];
        }
    }
    if (monitorTaskCount < MAX_TASKS) {
        TaskMonitor_t* mon = &taskMonitors[monitorTaskCount++];
        strncpy(mon->taskName, name, configMAX_TASK_NAME_LEN - 1);
        mon->taskName[configMAX_TASK_NAME_LEN - 1] = '\0';
        mon->lastState = eReady;
        mon->stateEnterTime = xTaskGetTickCount();
        memset(mon->stateCount, 0, sizeof(mon->stateCount));
        memset(mon->stateTotalTime, 0, sizeof(mon->stateTotalTime));
        return mon;
    }
    return NULL;
}

/* 记录一次状态转换 */
static void recordTransition(const char* name, eTaskState from, eTaskState to) {
    if (transitionCount < MAX_TRANSITIONS) {
        TransitionRecord_t* rec = &transitionLog[transitionCount++];
        rec->time = xTaskGetTickCount();
        strncpy(rec->taskName, name, configMAX_TASK_NAME_LEN - 1);
        rec->taskName[configMAX_TASK_NAME_LEN - 1] = '\0';
        rec->fromState = from;
        rec->toState = to;
    }
}

/* FreeRTOS任务切换钩子:每次任务切换时调用 */
void vApplicationTaskSwitchHook(void) {
    TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
    const char* taskName = pcTaskGetName(currentTask);
    eTaskState currentState = eTaskGetState(currentTask);

    TaskMonitor_t* mon = findOrCreateMonitor(taskName);
    if (mon == NULL) return;

    /* 若状态发生变化,记录转换并更新统计 */
    if (mon->lastState != currentState) {
        TickType_t now = xTaskGetTickCount();
        mon->stateTotalTime[mon->lastState] += (now - mon->stateEnterTime);
        mon->stateCount[currentState]++;
        recordTransition(taskName, mon->lastState, currentState);
        mon->lastState = currentState;
        mon->stateEnterTime = now;
    }
}

/* 打印监控报告(由低优先级任务周期性调用) */
void vPrintMonitorReport(void) {
    TickType_t now = xTaskGetTickCount();
    char stateNames[][12] = {"Running", "Ready", "Blocked", "Suspended", "Deleted", "Invalid"};

    printf("\r\n===== Task State Monitor Report =====\r\n");
    printf("Total transitions recorded: %lu\r\n", (unsigned long)transitionCount);

    for (uint32_t i = 0; i < monitorTaskCount; i++) {
        TaskMonitor_t* mon = &taskMonitors[i];
        /* 结算最后一段停留时间 */
        mon->stateTotalTime[mon->lastState] += (now - mon->stateEnterTime);
        mon->stateEnterTime = now;

        printf("Task: %s\r\n", mon->taskName);
        for (int s = 0; s <= eRunning; s++) {
            if (mon->stateCount[s] > 0 || mon->stateTotalTime[s] > 0) {
                printf("  %-10s count=%lu time=%lu ticks\r\n",
                       stateNames[s],
                       (unsigned long)mon->stateCount[s],
                       (unsigned long)mon->stateTotalTime[s]);
            }
        }
    }

    /* 打印最近的状态转换序列 */
    printf("\r\nRecent transitions (last %lu):\r\n",
           (unsigned long)(transitionCount > 10 ? 10 : transitionCount));
    uint32_t start = transitionCount > 10 ? transitionCount - 10 : 0;
    for (uint32_t i = start; i < transitionCount; i++) {
        TransitionRecord_t* rec = &transitionLog[i];
        printf("  [%lu] %s: %s -> %s\r\n",
               (unsigned long)rec->time,
               rec->taskName,
               stateNames[rec->fromState],
               stateNames[rec->toState]);
    }
    printf("=====================================\r\n");
}

/* 示例任务:演示不同状态转换 */
static void vDemoTask1(void* param) {
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(200));  /* Running -> Blocked */
        taskENTER_CRITICAL();
        taskEXIT_CRITICAL();              /* 短暂Running */
        vTaskSuspend(NULL);               /* Running -> Suspended */
        vTaskDelay(pdMS_TO_TICKS(100));   /* 不会执行到这里 */
    }
}

static void vDemoTask2(void* param) {
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(500));   /* Running -> Blocked */
        /* 模拟就绪态等待 */
        taskYIELD();
    }
}

/* 监控报告任务 */
static void vMonitorTask(void* param) {
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(2000));
        vPrintMonitorReport();
    }
}

void vStartStateMonitorDemo(void) {
    xTaskCreate(vDemoTask1, "Demo1", 128, NULL, 2, NULL);
    xTaskCreate(vDemoTask2, "Demo2", 128, NULL, 2, NULL);
    xTaskCreate(vMonitorTask, "Monitor", 256, NULL, 1, NULL);
}

运行与验证

  1. 启用钩子:在 FreeRTOSConfig.h 中设置 #define configUSE_IDLE_HOOK 0 和 #define configUSE_TICK_HOOK 0,并确保 configCHECK_FOR_STACK_OVERFLOW 不影响钩子注册。任务切换钩子默认开启,无需额外宏。
  2. 调用入口:在 main() 中创建启动任务后调用 vStartStateMonitorDemo()。
  3. 观察输出:串口终端每2秒打印一次监控报告,包含每个任务在各状态下的转换次数、累计停留时间,以及最近10条状态转换序列。

通过条件:报告中 Demo1 应出现 Running → Blocked(调用 vTaskDelay)和 Running → Suspended(调用 vTaskSuspend)的转换记录;Demo2 应周期性出现 Running → Blocked 转换。各任务状态计数与代码执行路径一致,且 stateTotalTime 总和约等于报告周期。

Running Ready Blocked Suspended 时间片/抢占 调度器选择 vTaskDelay/等待队列 超时/事件就绪 vTaskSuspend vTaskResume 监控点: 任务切换钩子(vApplicationTaskSwitchHook)

上图展示了FreeRTOS任务状态转换模型及本案例的监控点位置。钩子函数在每次任务切换时被调用,通过对比任务当前状态与上次记录状态,即可捕获所有状态转换事件。