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
相关推荐
心祭丫丫2 小时前
【每天一个 Python 小案例17】Python 实现通讯录
开发语言·人工智能·笔记·python·学习
露天赏雪3 小时前
第3篇:Sentinel熔断降级实战:大模型接口抖动时的服务兜底方案
java·开发语言·人工智能·spring boot·spring·servlet·sentinel
人邮异步社区4 小时前
学习FreeRTOS推荐哪本书?
freertos·rtos·嵌入式固件工程师
yeccheng5 小时前
MySQL 索引超详解:从底层 B+ 树到面试高频考点
java·开发语言·jvm·mysql·面试
联犀5 小时前
纯 Go 的 CAD 开源库 go-cad:直接解析 DWG/DXF,现在也能导出 PDF
开发语言·golang·pdf
Nerd(BUG版)5 小时前
Flutter学习day5--事件手势与命中测试
学习·flutter
zjxtxdy6 小时前
C++面向对象程序开发-类与对象
开发语言·c++
Nerd(BUG版)7 小时前
KMP学习day4--Kotlin空安全与泛型
学习·安全·kotlin
東隅已逝,桑榆非晚7 小时前
算法6. 双指针法-查找总价格为目标的两个商品
c++·学习·算法
qq_406981038 小时前
c++拷贝构造函数
开发语言·c++