上一节学习了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