FreeRTOS学习(二十七)——任务栈与栈溢出检测

上一节学习了FreeRTOS的动态内存管理heap_1到heap_5,知道了创建任务、队列和信号量时需要从RAM中申请内存。

创建任务时还有一个非常重要的参数:

复制代码
xTaskCreate(
    Task1,
    "Task1",
    128,
    NULL,
    2,
    NULL
);

这里的:

复制代码
128

就是任务的栈大小

栈设置过大会浪费RAM,设置过小又可能造成程序跑飞甚至HardFault。

本节学习任务栈的作用,以及FreeRTOS如何检测任务栈是否溢出。


一、任务栈是什么

FreeRTOS中每个任务都有自己独立的栈空间。

例如:

复制代码
Task1
 ↓
Task1 Stack

Task2
 ↓
Task2 Stack

任务运行过程中:

复制代码
局部变量

函数参数

函数返回地址

部分CPU现场数据

都可能需要使用任务栈。

例如:

复制代码
void Task1(void *pvParameters)
{
    uint8_t Buffer[100];

    while(1)
    {
        Buffer[0]++;

        vTaskDelay(100);
    }
}

这里:

复制代码
uint8_t Buffer[100];

就会占用Task1自己的栈空间。

二、xTaskCreate中的128是什么意思

例如:

复制代码
xTaskCreate(
    Task1,
    "Task1",
    128,
    NULL,
    2,
    NULL
);

很多初学者会直接理解成:

复制代码
128Byte

实际上并不准确。

这个参数表示:

复制代码
128个StackType_t

而STM32F407是32位MCU,通常:

复制代码
sizeof(StackType_t) = 4Byte

因此:

复制代码
128 × 4

=

512Byte

也就是说这个任务实际栈空间大约是:

复制代码
512Byte

这一点非常重要。

三、为什么每个任务都需要自己的栈

假设Task1运行到:

复制代码
FunctionA();

突然Task2抢占Task1。

FreeRTOS需要保存Task1当前的:

复制代码
寄存器状态

函数运行位置

局部变量

然后切换到Task2。

以后重新运行Task1时:

复制代码
恢复之前的运行状态

才能继续执行。

因此:

复制代码
每个任务

↓

必须拥有自己的栈空间

这也是FreeRTOS能够实现任务切换的重要基础。

四、什么是栈溢出

假设创建任务:

复制代码
xTaskCreate(
    Task1,
    "Task1",
    128,
    NULL,
    2,
    NULL
);

实际栈大约:

复制代码
512Byte

但是任务内部定义:

复制代码
uint8_t Buffer[1000];

仅这个数组就需要:

复制代码
1000Byte

显然已经超过任务栈空间。

这就是:

复制代码
Stack Overflow

即:

栈溢出。

五、栈溢出有什么现象

栈溢出以后不一定马上报错。

可能出现:

复制代码
程序莫名其妙跑飞

变量值突然改变

任务无法运行

HardFault

系统偶尔死机

而且最麻烦的是:

复制代码
程序可能运行一段时间以后

才出现问题。

因此任务栈设置是否合理,是FreeRTOS调试中非常重要的一项。


六、开启栈溢出检测

在:

复制代码
FreeRTOSConfig.h

中可以设置:

复制代码
#define configCHECK_FOR_STACK_OVERFLOW 2

然后实现:

复制代码
vApplicationStackOverflowHook();

例如:

复制代码
void vApplicationStackOverflowHook(
    TaskHandle_t xTask,
    char *pcTaskName
)
{
    printf(
        "Stack Overflow: %s\r\n",
        pcTaskName
    );

    while(1)
    {
    }
}

当FreeRTOS检测到某个任务发生栈溢出时:

复制代码
↓

进入这个函数

这样我们就可以知道:

复制代码
哪个任务的栈出现问题。

七、configCHECK_FOR_STACK_OVERFLOW怎么选

常见设置:

复制代码
#define configCHECK_FOR_STACK_OVERFLOW 0

表示:

复制代码
关闭检测。

设置:

复制代码
#define configCHECK_FOR_STACK_OVERFLOW 1

表示:

复制代码
进行基本栈溢出检测。

设置:

复制代码
#define configCHECK_FOR_STACK_OVERFLOW 2

会进行更严格的检查。

普通STM32调试阶段通常可以使用:

复制代码
#define configCHECK_FOR_STACK_OVERFLOW 2

帮助发现栈空间不足的问题。

八、查看任务还剩多少栈

FreeRTOS提供:

复制代码
uxTaskGetStackHighWaterMark();

例如:

复制代码
UBaseType_t StackLeft;

StackLeft =
    uxTaskGetStackHighWaterMark(
        Task1_Handle
    );

它返回的是:

任务运行以来,栈最紧张时还剩多少未使用空间。

例如任务创建:

复制代码
Stack = 256

运行一段时间后:

复制代码
HighWaterMark = 80

说明任务历史上最紧张的时候:

复制代码
仍然剩余80个StackType_t。

如果得到:

复制代码
HighWaterMark = 2

说明:

复制代码
栈已经非常危险。

应该适当增加任务栈。

九、实际工程怎么设置栈大小

任务栈并不是:

复制代码
越大越好。

例如10个任务:

每个任务多分配:

复制代码
1KB

就会额外消耗:

复制代码
10KB RAM

对于STM32来说已经是一笔不小的资源。

因此实际工程通常先给一个相对合理的值:

复制代码
128

256

512

然后运行程序,通过:

复制代码
uxTaskGetStackHighWaterMark();

观察实际使用情况。

最后再根据结果调整。

另外:

复制代码
大数组

大结构体

尽量不要随意定义成任务局部变量。

否则很容易快速消耗任务栈。

十、总结

这一节学习了FreeRTOS中的任务栈与栈溢出检测

创建任务:

复制代码
xTaskCreate();

其中的栈大小参数不是简单的Byte数,而是:

复制代码
StackType_t数量。

在STM32F407中通常:

复制代码
128

↓

128 × 4Byte

↓

约512Byte

任务的局部变量、函数调用以及任务切换保存的数据都会消耗栈空间。

栈太小可能导致:

复制代码
程序跑飞

数据异常

HardFault

可以通过:

复制代码
configCHECK_FOR_STACK_OVERFLOW

开启栈溢出检测,并通过:

复制代码
uxTaskGetStackHighWaterMark();

观察任务历史上的最小剩余栈空间。

实际工程中应该做到:

复制代码
栈不能太小
    ↓
防止溢出

栈也不能盲目设置过大
    ↓
避免浪费RAM
相关推荐
小弥儿1 小时前
GitHub今日热榜 | 2026-08-16:大模型微调与编码工具扎堆
学习·开源·github
丰锋ff2 小时前
3.1 Qt事件之事件处理器
开发语言·qt
多弗朗皮卡丘2 小时前
C语言梦开始的地方7:函数
c语言·开发语言
月华路2 小时前
G1 垃圾回收:脏卡队列、记忆集与并发精化线程机制
java·开发语言
gugucoding3 小时前
47. 【Java】Java内存模型(JMM)与可见性
java·开发语言
m0_380743873 小时前
从零调用 Claude 教程
开发语言·python·node.js
起床学FPGA3 小时前
AI回答问题之后,会自动跳到最下面的问题
开发语言·javascript·ecmascript
xian_wwq4 小时前
【学习笔记】光伏“四可”装置纵密模块--配置方法和原理
笔记·学习
min(a,b)4 小时前
学习第17天:Agent Memory 与 MCP 协议
python·学习