
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》
❄专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连
目录
[一、MonitorTask 系统监控任务概述](#一、MonitorTask 系统监控任务概述)
[二、全局心跳变量:任务卡死的 "看门狗"](#二、全局心跳变量:任务卡死的 “看门狗”)
[2.1 定义与作用](#2.1 定义与作用)
[四、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 修饰的心跳变量,精准检测各业务任务是否卡死,提前输出报警日志;
完成了任务信息注册功能,为下篇的堆内存监控、任务栈监控打下基础。