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);
}
运行与验证
- 启用钩子:在
FreeRTOSConfig.h中设置#define configUSE_IDLE_HOOK 0和#define configUSE_TICK_HOOK 0,并确保configCHECK_FOR_STACK_OVERFLOW不影响钩子注册。任务切换钩子默认开启,无需额外宏。 - 调用入口:在
main()中创建启动任务后调用vStartStateMonitorDemo()。 - 观察输出:串口终端每2秒打印一次监控报告,包含每个任务在各状态下的转换次数、累计停留时间,以及最近10条状态转换序列。
通过条件:报告中 Demo1 应出现 Running → Blocked(调用 vTaskDelay)和 Running → Suspended(调用 vTaskSuspend)的转换记录;Demo2 应周期性出现 Running → Blocked 转换。各任务状态计数与代码执行路径一致,且 stateTotalTime 总和约等于报告周期。
上图展示了FreeRTOS任务状态转换模型及本案例的监控点位置。钩子函数在每次任务切换时被调用,通过对比任务当前状态与上次记录状态,即可捕获所有状态转换事件。