ARM:FreeRTOS系统栈和任务栈
背景:ARM 有两个栈指针PSP和MSP, 通过Control 寄存器来决定SP(R13)使用哪个栈。我们下面谈论的系统栈和任务栈,就和这两个栈指针有关。
FreeRTOS 任务栈
FreeRTOS不同于裸机每个TASK都有一个任务栈。FreeRTOS的任务栈是在任务创建的时候从FreeRTOSConfig.h 定义的Heap 空间中申请:
#define configTOTAL_HEAP_SIZE ((size_t)1024 * 9)
具体任务栈创建的格式如下:(任务栈的大小是 usStackDepth*4)
注意:这种创建方式是动态创建栈的方式,所以会放在Heap 空间中申请。
申请任务栈空间的code如下:
FreeRTOS会定义两个栈指针来表明任务栈的大小:
1、 *pxTopOfStack栈顶指针
2、*pxStack 栈的起始地址
注意:pxTopOfStack-pxStack会小于分配的任务栈的大小,原因是arm和FreeRTOS系统会额外保存一些寄存器,用于TASK的切换和返回。
具体形式如下(栈的生长方式可以设置,这边默认是从高往低):
在创建任务栈的过程中,FreeRTOS 会切换 arm 的栈指针,将栈指针切换到PSP,
通过将Control [1] 置 1实现。
FreeRTOS 系统栈
上面跟大家讲解了什么是任务栈,这里的系统栈又是什么呢?裸机的情况下,我们在ld script 中定义一个栈空间,并在Startup.S 中将MSP指向这个栈空间,这个就是系统栈。
arm-CM3 中系统复位后给MSP赋的初值就是我们定义的系统栈。
系统栈使用MSP指针。
在 RTOS 下,任务栈是不使用这里的空间的。 既然任务栈不使用这里的栈空间,那么哪里要使用这里的栈空间呢?答案就在中断函数和中断嵌套。
**之前讲过 arm M3 内核是具有双堆栈指针,MSP 主堆栈指针和 PSP 进程堆栈指针,或者叫 PSP任务堆栈指针也是可以的。
在 FreeRTOS 操作系统中,主堆栈指针 MSP 是给系统栈空间使用的,进程堆栈指针 PSP 是给任务栈使用的。 也就是说,在 FreeRTOS 任务中,所有栈空间的使用都是通过PSP 指针进行指向的。 一旦进入了中断函数以及可能发生的中断嵌套都是用的 MSP 指针。
系统栈和任务栈的分配
FreeRTOS 中每个任务都需要自己的栈空间,栈空间的大小需要考虑如下几个方面:
函数的嵌套调用:
函数局部变量。
函数形参,一般情况下函数的形参是直接使用的 CPU 寄存器,不需要使用栈空间,但是这个函数中如果还嵌套了一个函数的话,这个存储了函数形参的 CPU 寄存器内容是要入栈的。
函数返回地址,arm中一般函数的返回地址是专门保存到 LR(LinkRegister)寄存器中的,如果这个函数里面还调用了一个函数的话,这个存储了函数返回地址的 LR 寄存器内容是要入栈的。
函数内部的状态保存操作也需要额外的栈空间。
任务切换,任务切换时所有的寄存器都需要入栈。
ARM 在任务执行过程中,如果发生中断:
M3 内核的 MCU 有 8 个寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余寄存器入栈以及发生中断嵌套都是用的系统栈。
M4 内核的 MCU 有 8 个通用寄存器和 18 个浮点寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余通用寄存器和浮点寄存器入栈以及发生中断嵌套都是用的系统栈。
注意:进入中断以后使用的局部变量以及可能发生的中断嵌套都是用的系统栈。进入中断后系统会自动将栈指针切换到MSP,退出中断后再切回PSP。
系统栈空间的分配
实际应用中系统栈空间分配多大,主要是看可能发生的中断嵌套层数,下面我们就按照最坏执行情况进行考虑,所有的寄存器都需要入栈,此时分为两种情况:
(进入中断后,系统会自动将SP切到MSP)
64 字节
对于 Cortex-M3 内核和未使用 FPU(浮点运算单元)功能的 Cortex-M4 内核在发生中断时需要将 16 个通用寄存器全部入栈,每个寄存器占用 4 个字节,也就是 16*4 = 64 字节的空间。
可能发生几次中断嵌套就是要 64 乘以几即可。 当然,这种是最坏执行情况,也就是所有的寄存器都入栈。
(注:任务执行的过程中发生中断的话,有 8 个寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余寄存器入栈以及发生中断嵌套都是用的系统栈)
200 字节
对于具有 FPU(浮点运算单元)功能的 Cortex-M4 内核,如果在任务中进行了浮点运算,那么在发生中断的时候除了 16 个通用寄存器需要入栈,还有 34 个浮点寄存器也是要入栈的,也就是(16+34)*4 = 200 字节的空间。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。
(注:任务执行的过程中发送中断的话,有 8 个通用寄存器和 18 个浮点寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余通用寄存器和浮点寄存器入栈以及发生中断嵌套都是用的系统栈)
FreeRTOS 中提供了一个比较好用的栈函数,可以计算出Task 使用了多少栈空间:
uxTaskGetStackHighWaterMark 会显示出Task历史上使用的最大栈深度是多少,返回值是历史上该Task所剩余的最小栈空间的大小。