FreeRTOS学习第3篇–任务栈使用与理解
FreeRTOS中的栈
FreeRTOS是一个可剥夺型的多任务内核,它可以管理多个任务,每个任务都是一个独立的程序,通常是一个死循环。
FreeRTOS的内核负责管理所有的任务,内核决定了运行哪个任务,何时停止当前任务切换到其他任务,这个是内核的多任务管理能力。
为了实现多任务管理,FreeRTOS需要保存每个任务的上下文信息,即任务运行时的状态,包括寄存器值、局部变量等。这些信息就存储在栈中。
在FreeRTOS中,每个任务都有自己的栈空间,这样可以实现任务的切换和恢复。当内核切换任务时,它会先保存当前任务的上下文信息到当前任务的栈中,然后从即将运行的任务的栈中恢复该任务的上下文信息,最后跳转到该任务的执行地址。
FreeRTOS支持两种栈模式:静态栈和动态栈。静态栈是在编译时就分配好的固定大小的栈空间,动态栈是在运行时根据需要分配的可变大小的栈空间。静态栈的优点是节省内存,缺点是不灵活;动态栈的优点是灵活,缺点是可能产生内存碎片。
如何确定栈大小
栈大小确定的问题在实际中都比较麻烦,而且栈使用不够动不动就进入HardFault_Handler错误,挺烦的。那么有没有简单的办法来计算呢?有的,一般 IDE 开发环境都有这样的功能,比如 MDK 会生成一个 htm 文件,通过这个文件用户可以知道每个被调用函数的最大栈需求以及各个函数之间的调用关系。但是 MDK 无法确定通过函数指针实现函数调用时的栈需求。另外,发生中断或中断嵌套时的现场保护需要的栈空间也不会统计。
简单例子:确认任务的栈大小
ColorLED_Test任务中的源码
void ColorLED_Test(void * pvParameters)
{
uint32_t color = 0;
ColorLED_Init();
while (1)
{
//LCD_PrintString(0, 0, "Show Color: ");
//LCD_PrintHex(0, 2, color, 1);
ColorLED_Set(color);
color += 200000;
color &= 0x00ffffff;
mdelay(1000);
}
}
在我的这个任务中,分配的任务和栈情况如下:
static StackType_t g_pucStackOfColorTask[128];
static StaticTask_t g_TCBofColorTask;
static TaskHandle_t xColorTaskHandle;
xColorTaskHandle = xTaskCreateStatic(ColorLED_Test, "ColorTask", 128, NULL,osPriorityNormal, g_pucStackOfColorTask, &g_TCBofColorTask);
那么查看ColorLED_Test所使用的最大栈深度,查看编译出来的htm文件,
在htm文件中寻找对应的函数发现有Max Depth关键词,根据这个参考情况,也就说我的ColorLED_Test这个函数最大的栈深度是148字节。
实际分配我们分配栈大小时可以在最小栈需求的基础上乘以一个安全系数,一般取 1.5-2。
栈任务大小实践
那么基于以上的分析,我进行了实验,更改任务的栈情况:
static StackType_t g_pucStackOfColorTask[36];
static StaticTask_t g_TCBofColorTask;
static TaskHandle_t xColorTaskHandle;
xColorTaskHandle = xTaskCreateStatic(ColorLED_Test, "ColorTask", 36, NULL, osPriorityNormal, g_pucStackOfColorTask, &g_TCBofColorTask);
工程中跑的任务有三个如下:
我工程中找到对应的map文件中查找任务使用的栈地址如下:
查看栈的内存情况
实验发现,因为我ColorLED_Test函数这个函数的栈大小比较极限,我给了36*4=144字节,比编译出来的148字节小一点点,程序起初运行时各项任务的功能都正常的,随着时间的运行,大概跑了几分钟,我的其他任务功能出现了异常,已经不稳定了,但是系统现在还没死机,因为我发现ColorLED_Test能够正常运行,当我改回148字节的任务时,时间久了也一样出现问题,所以在最小需要的栈基础上乘以一个安全系数(1.5-2之间)更为保险。
总结
通过本次的工程实践,为分配任务时的栈大小情况积攒下经验,为以后更加复杂的编程积累工程经验。