FreeRTOS 系统监控任务设计(上篇) ---MonitorTask的 基础框架

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏

专栏传送门《产品测评专栏》《Ai智能体专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

[一、MonitorTask 系统监控任务概述](#一、MonitorTask 系统监控任务概述)

[二、全局心跳变量:任务卡死的 "看门狗"](#二、全局心跳变量:任务卡死的 “看门狗”)

[2.1 定义与作用](#2.1 定义与作用)

2.2、使用方法

三、注册监控任务信息

3.1、任务信息结构体定义

3.2、任务注册函数实现

3.3、任务注册示例

[四、MonitorTask 基础框架完整代码](#四、MonitorTask 基础框架完整代码)

总结


前言

在本系列前面的文章中,我们已经完成了机器人下位机四大核心业务任务的搭建:CommTask指令解析、ControlTask运动控制、SensorTask状态采集、UplinkTask数据上报。

此时我们还需要一个系统监控任务来持续监测整机的运行健康状态。MonitorTask 就是这个角色 ------ 它周期性地检查堆内存、任务栈、任务心跳和任务列表,在问题发生前就提前预警,避免系统崩溃、死循环、内存泄漏等问题导致机器人失控。


一、MonitorTask 系统监控任务概述

MonitorTask(App_MonitorTask)是一个周期性运行的系统健康守护任务,它的核心目标是:在不影响业务逻辑的前提下,持续监测系统运行状态,并通过日志输出报警信息。

它主要负责以下几类监控:

堆内存使用情况(当前剩余、历史最低值)

各任务栈使用情况(高水位标记、使用率预警)

任务 "心跳" 信号(检测任务是否卡死)

系统任务列表(状态、优先级、栈使用情况)

通过这些监控手段,我们可以在开发和调试阶段,提前发现内存泄漏、栈溢出风险、任务死锁等问题,大幅提升系统的稳定性和可维护性。

下面是 MonitorTask 完整的执行流程时序图:

固定周期调度:以 1000ms 为周期循环运行,不占用业务任务的时间片。

堆内存监控:检查当前剩余堆内存和历史最低值,预警内存泄漏和 OOM 风险。

任务栈监控:遍历所有注册任务的栈高水位标记,计算使用率并分级报警。

任务心跳检测:检查各业务任务的心跳变量是否持续自增,判断任务是否卡死。

任务列表输出:每 10 秒打印一次完整的系统任务列表,方便调试。

二、全局心跳变量:任务卡死的 "看门狗"

2.1 定义与作用

为了监测任务是否卡死,我们定义了一组全局心跳变量,每个业务任务都有一个对应的心跳计数器。每个任务在正常运行时会定期对对应的变量进行自增。

cpp 复制代码
volatile uint32_t g_hb_comm      = 0;  // commtask的心跳
volatile uint32_t g_hb_control   = 0;  // controltask的心跳
volatile uint32_t g_hb_led       = 0;  // ledtask的心跳
volatile uint32_t g_hb_lcd       = 0;  // lcdtask的心跳
volatile uint32_t g_hb_log       = 0;  // logtask的心跳
volatile uint32_t g_hb_iwdg      = 0;  // iwdgtask的心跳
volatile uint32_t g_hb_uplink    = 0;  // uplinktask的心跳
volatile uint32_t g_hb_sensor    = 0;  // sensortask的心跳

volatile 修饰符告诉编译器:该变量可能在程序控制范围之外被修改(如另一个任务或中断)。

编译器不会对其进行优化,每次访问都必须从内存读取真实值。

如果不加 volatile,编译器可能会把变量缓存到寄存器,导致心跳值读取失败,误判任务状态。

2.2、使用方法

每个任务在主循环中定期自增心跳变量:

cpp 复制代码
void App_CommTask(void *arg) {
    for (;;) {
        // 处理通信逻辑...
        g_hb_comm++; // 每个周期增加心跳
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

MonitorTask 每秒检查各个心跳变量是否有增长。如果两次检查之间,某任务的心跳值没有变化,说明该任务可能卡死或异常退出,此时打印日志报警。

cpp 复制代码
static void monitor_heartbeat_alarm_only(void)
{
    static uint32_t last_comm = 0;
    if (g_hb_comm == last_comm) {
        LOGERROR_T("MON", "[HB] COMM stalled!");
    }
    last_comm = g_hb_comm;
    // 其他任务类似...
}

三、注册监控任务信息

为了实现对所有任务的统一管理(下篇的栈监控需要用到),我们需要先注册所有需要监控的任务信息,包括任务句柄、栈大小、任务别名。下面我们一起看下注册函数:

cpp 复制代码
void Monitor_RegisterTaskInfo(const MonTaskInfo_t *list, uint32_t count)

该函数用于注册一组需要被系统监控的任务信息,以便后续对这些任务的栈空间使用情况、状态等进行统计和告警。
list: 指向任务信息数组的指针,每个元素类型为 MonTaskInfo_t,包含了每个任务的名称、句柄和栈大小等。
**count:**任务数组的元素数量。

3.1、任务信息结构体定义

结构体定义在monitor_task.h文件,代码如下:

cpp 复制代码
// 任务监控信息结构体(存储需要监控的任务关键信息)
typedef struct {
    TaskHandle_t h;       // 任务句柄(用于获取栈信息)
    uint16_t stack_words; // 任务分配的栈空间(单位:字,1字=4字节)
    const char *alias;    // 任务别名(日志打印用,更直观)
} MonTaskInfo_t;

3.2、任务注册函数实现

通过注册函数,将所有需要监控的任务信息统一存入全局数组,注册过程加临界区保护,确保数据一致性:

cpp 复制代码
// 全局变量:存储注册的任务信息
static MonTaskInfo_t s_tasks[MON_MAX_TASKS] = {0};
static uint32_t s_task_count = 0;

// 任务信息注册函数(系统初始化时调用)
void Monitor_RegisterTaskInfo(const MonTaskInfo_t *list, uint32_t count)
{
    // 临界区保护:避免多任务并发注册导致数据错乱
    taskENTER_CRITICAL();
    // 清空原有注册信息
    memset(s_tasks, 0, sizeof(s_tasks));
    // 拷贝新的任务信息
    for (uint32_t i = 0; i < count && i < MON_MAX_TASKS; i++) {
        s_tasks[i] = list[i];
    }
    // 更新注册任务数量
    s_task_count = count;
    taskEXIT_CRITICAL();
}

3.3、任务注册示例

在所有任务创建完成后,注册需要监控的任务,后续监控功能直接复用这些信息:

cpp 复制代码
// 示例:注册所有核心任务信息
static const MonTaskInfo_t mon_tasks[] = {
    {.h = g_controlTaskHandle, .stack_words = 256, .alias = "CONTROL"}, // 控制任务
    {.h = g_commTaskHandle,    .stack_words = 256, .alias = "COMM"},    // 通信任务
    {.h = g_logTaskHandle,     .stack_words = 256, .alias = "LOG"},     // 日志任务
    {.h = g_sensorTaskHandle,  .stack_words = 256, .alias = "SENSOR"},  // 采集任务
    {.h = g_uplinkTaskHandle,  .stack_words = 256, .alias = "UPLINK"},  // 上报任务
};

// 注册任务(系统初始化阶段调用)
Monitor_RegisterTaskInfo(mon_tasks, sizeof(mon_tasks)/sizeof(mon_tasks[0]));

四、MonitorTask 基础框架完整代码

cpp 复制代码
/**
 ****************************************************************************************************
 * @file        monitor_task.c
 * @brief       系统监控任务(MonitorTask)- 上篇:基础框架与心跳守护
 * @details     本任务用于周期性监测系统运行状态,上篇重点实现心跳检测与任务注册
 * @platform    STM32F407ZGT6 + FreeRTOS + HAL
 * @copyright   Copyright (c) 2025-2035 Kaoya. All rights reserved.
 ****************************************************************************************************
 */

#include "monitor_task.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdint.h>
#include <string.h>
#include "log_task.h"

/* 监控配置 */
#define MON_PERIOD_MS         (1000u)    // 监控周期(1秒)
#define MON_MAX_TASKS         (8u)       // 最大可注册任务数

/* 全局变量 */
static MonTaskInfo_t s_tasks[MON_MAX_TASKS] = {0}; // 注册的任务信息
static uint32_t s_task_count = 0;                  // 已注册任务数量
static uint32_t s_mon_seq = 0;                    // 监控序号(用于日志区分)

/* 全局心跳变量(外部业务任务会修改) */
volatile uint32_t g_hb_comm    = 0;
volatile uint32_t g_hb_control = 0;
volatile uint32_t g_hb_log     = 0;
volatile uint32_t g_hb_sensor  = 0;
volatile uint32_t g_hb_uplink  = 0;

/* 函数声明 */
static void monitor_heartbeat_alarm_only(void);

/**
 * @brief   任务信息注册函数
 * @param   list    任务信息数组指针
 * @param   count   任务数组元素数量
 */
void Monitor_RegisterTaskInfo(const MonTaskInfo_t *list, uint32_t count)
{
    taskENTER_CRITICAL();
    memset(s_tasks, 0, sizeof(s_tasks));
    for (uint32_t i = 0; i < count && i < MON_MAX_TASKS; i++) {
        s_tasks[i] = list[i];
    }
    s_task_count = count;
    taskEXIT_CRITICAL();
}

/**
 * @brief   心跳检测(仅异常时报警)
 */
static void monitor_heartbeat_alarm_only(void)
{
    static uint32_t last_comm    = 0;
    static uint32_t last_control = 0;
    static uint32_t last_log     = 0;
    static uint32_t last_sensor  = 0;
    static uint32_t last_uplink  = 0;

    // 检测 CommTask
    if (g_hb_comm == last_comm) {
        LOGERROR_T("MON", "[HB ERROR] CommTask Stalled!");
    }
    last_comm = g_hb_comm;

    // 检测 ControlTask
    if (g_hb_control == last_control) {
        LOGERROR_T("MON", "[HB ERROR] ControlTask Stalled!");
    }
    last_control = g_hb_control;

    // 检测 LogTask
    if (g_hb_log == last_log) {
        LOGERROR_T("MON", "[HB ERROR] LogTask Stalled!");
    }
    last_log = g_hb_log;

    // 检测 SensorTask
    if (g_hb_sensor == last_sensor) {
        LOGERROR_T("MON", "[HB ERROR] SensorTask Stalled!");
    }
    last_sensor = g_hb_sensor;

    // 检测 UplinkTask
    if (g_hb_uplink == last_uplink) {
        LOGERROR_T("MON", "[HB ERROR] UplinkTask Stalled!");
    }
    last_uplink = g_hb_uplink;
}

/**
 * @brief   系统监控任务入口函数
 * @details 上篇核心:周期执行心跳检测,搭建基础监控框架
 */
void App_MonitorTask(void *argument)
{
    (void)argument;

    // 周期调度基准(精准延时)
    TickType_t last_wake = xTaskGetTickCount();

    for (;;) {
        // 1. 打印监控周期开始日志
        LOGINFO_T("MON", "================== MON BEGIN #%u ==================", s_mon_seq);

        // 2. 核心:心跳检测(异常报警)
        monitor_heartbeat_alarm_only();

        // 3. 打印监控周期结束日志
        LOGINFO_T("MON", "================== MON END #%u ====================\n", s_mon_seq);

        // 4. 监控序号自增
        s_mon_seq++;

        // 5. 固定周期延时(1秒)
        vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(MON_PERIOD_MS));
    }
}

总结

本篇作为 MonitorTask 系统监控的入门篇,重点实现了两大核心功能:

搭建了 MonitorTask 的基础运行框架,采用 1 秒固定周期调度,低侵入性、不影响业务实时性;

实现了任务心跳机制,通过 volatile 修饰的心跳变量,精准检测各业务任务是否卡死,提前输出报警日志;

完成了任务信息注册功能,为下篇的堆内存监控、任务栈监控打下基础。

相关推荐
Jason_zhao_MR1 小时前
RK3576 MIPI Camera ISP调试:客观标定与环境准备(上)
人工智能·嵌入式硬件·机器人·嵌入式·接口隔离原则
深圳市晶科鑫实业有限公司1 小时前
RTC模块vs. 32.768KHz晶振:深度对比与选型指南
stm32·单片机·嵌入式硬件·实时音视频·rtc
jghhh012 小时前
STM32F103 驱动 BMP180 气压传感器源码
stm32·单片机·嵌入式硬件
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 5 天:电阻分类、色环电阻读数、贴片电阻
单片机·嵌入式硬件
济6172 小时前
MonitorTask 系统监控任务(下篇)---完善堆内存 、任务栈监控
单片机·嵌入式·freertos
代码又报错la2 小时前
5、电源保护板
单片机·嵌入式硬件
leo__52010 小时前
STM32 MAX30102 心率血氧测量代码
stm32·单片机·嵌入式硬件
yuan1999714 小时前
STM32 IAP 电量计源码
stm32·单片机·嵌入式硬件
学不懂飞行器15 小时前
从小白到国奖:全国大学生电子设计竞赛(电赛)高质量备赛全攻略
stm32·单片机·嵌入式硬件