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
相关推荐
weixin_BYSJ19873 分钟前
【计算机毕设】基于Spring_Boot_Vue的马拉松报名与选手成绩追踪系统05445
java·vue.js·spring boot·学习·spring·django·课程设计
艾莉丝努力练剑6 分钟前
【AI大模型接入SDK】ChatServer HTTP接口模块实现总览
网络·c++·人工智能·网络协议·学习·http·架构
坤坤子吖14 分钟前
Linux 线程概念与控制——线程创建、退出与等待
linux·开发语言·c++·笔记·学习
薛一半14 分钟前
Redux实战:组件状态管理全解析
开发语言·前端·javascript·react.js
weixin_3077791318 分钟前
C++代码实现MATLAB中的fitnlm函数功能
开发语言·c++·算法·matlab
m4Rk_19 分钟前
【论文阅读】Agent 记忆机制(103):Dynamic Coding Memory——记忆为何让 Agent 更可靠,却可能更平庸
论文阅读·人工智能·学习·开源·github
qq_2842740529 分钟前
间歇运动机构与机械设计概论:棘轮与槽轮机构运动系数、零件失效与强度刚度准则、结构工艺性清根及标准件(含考点)
笔记·学习·平面·自动化·制造
qq_401700415 小时前
RTOS址与值 void*任务传参的坑 走void*万能转的天坑
freertos
白帽攻防录9 小时前
SRC 挖洞:PostScript 打印机驱动双 RCE,打印服务器怎么变成内网跳板
开发语言·网络安全·php·内网安全