stm32以及freertos 堆栈解析

以前在mcu编程的时候没有太注意堆栈的情况,只知道需要将堆栈设置的大一点。现在逐步使用freertos,在freertos中也有关于堆栈的设置,freertos的堆栈和启动文件中的堆栈关系是什么?为了以后使用的无误,本次一次性把这些弄清楚。


1、定义

堆栈是一个特定的存储区或者寄存器。一般在内存总开辟一块区域作为堆栈,叫做软件堆栈;用寄存器构成的堆栈,叫硬件堆栈。大多数情况下,我们使用的都是软件堆栈。

stm32以及freertos 堆栈解析_第1张图片
在stm或者gd32的启动文件中的堆栈就是软件堆栈。

堆栈中数据的存储,都要遵循先进后出的原则,可以类比为木桶,先放进去的数据在桶的底部,后放进去的数据在桶的顶部。取数据的时候是先在桶的顶部取数据,在从桶的底部取数据。

单片机应用中,堆栈是个特殊存储区,堆栈属于RAM空间的一部分,堆栈用于函数调用、中断切换时保存和恢复现场数据。堆栈中的物体具有一个特性:第一个放入堆栈中的物体总是被最后拿出来, 这个特性通常称为先进后出 (FILO—First-In/Last-Out)。 堆栈中定义了一些操作, 两个最重要的是PUSH和POP。 PUSH(入栈)操作:堆栈指针(SP)加1,然后在堆栈的顶部加入一 个元素。POP(出栈)操作相反,出栈则先将SP所指示的内部ram单元中内容送入直接地址寻址的单元中(目的位置),然后再将堆栈指针(SP)减1。这两种操作实现了数据项的插入和删除。

关于堆栈的理论知识就简述这么多,部分来自于百度百科。在mcu中我们只要知道上图设置的堆栈实际是位于mcu的ram区就可以了。在mcu编程中,貌似对ram的关注都不多,更多的是关注flash的容量,falsh大小能不能存储的了我的固件之类的。对ram就没有知道有这个东西,但真没有细致关注。

2、堆栈的空间分配

在mcu中,heap和stack的使用者是不同的。
stack(栈):由系统自动分配释放,存放的函数的参数值,局部变量的值。这个空间用户操作不了的。

heap(堆):由用户分配及释放,也就是调用malloc 和free,时操作的空间就是堆空间。

站原子论坛上,建议,如果没有调用系统的malloc和free函数,heap空间可以设置为0.

从空间分配上,最好理解的就是heap,完全由用户操作。比较难理解的是stack,那么系统到底是怎么使用的栈空间的呢。

3、stm32栈空间及编译的固件大小

用keil编译工程完成后,output窗口会显示如下信息:
stm32以及freertos 堆栈解析_第2张图片
上图的中的code ro-data rw-data zi-data是什么意思,哪些是站ram空间,哪些是站flash空间呢?在上文中我们说到stack是有系统自动分配的,那么我把启动文件中的stack_size改变一下,再看下编译输出结果。
stm32以及freertos 堆栈解析_第3张图片
将stack_size 由0x2000改为0x1000,有8k字节改为4k字节。编译后的结果如下图。

stm32以及freertos 堆栈解析_第4张图片
与上一次的编译对比发现,只有zi-data段变化了由10508变成了6412.二者的差值刚好是4096,就是stack_size的减少值。

在将heap_size设置为0x1000,发现编译出来的没有任何变化。

3.1 存储数据段

Code是代码占用的空间;

RO-data是 Read Only 只读常量的大小,如const型;

RW-data是(Read Write) 初始化了的可读写变量的大小;

ZI-data是(Zero Initialize) 没有初始化的可读写变量的大小。

最终,烧写时flash被占有的空间为:

falsh = Code + RO-Data + RW-Data

程序运行时ram被占有的空间为

ram = RW-Data + ZI-Data

计算一下falsh占有空间 = 12568+ 368+ 180 = 13116字节
stm32以及freertos 堆栈解析_第5张图片
看下最终生成的bin文件刚好是13116字节大小。

找到了flash占用的空间,我们再来看看ram占用的空间。ram占用空间在生成的.map文件中。
stm32以及freertos 堆栈解析_第6张图片
map文件中显示ram占用的size为0x19c0,转换为10进制为6592,而生成RW-Data + ZI-Data=180+6412=6592.这个也得到了验证。

根据上面的图还可以得出:

** RW-Data= .data** data的空间累加即为RW-Data
** ZI-Data= .bss+STACK** .bss的空间累加+STACK即为ZI-Data

上图中也可以看出STACK=0x1000刚好是在启动文件中设定的值。也可以看出当前芯片的ram总空间为0x18000=96kB。

到这里我们基本梳理出来的ram和falsh空间的构成。接下来看看freertos的堆栈是啥。

3.2freertos堆栈

我使用的是freertos是v10版本,内存分配采用的heap4.c
在freertos的FreeRTOSConfig.h中,需要设置堆的大小

#define configTOTAL_HEAP_SIZE		( ( size_t ) ( 1 * 1024 ) )

这里做测试我就只设置为1024字节。全局搜索下configTOTAL_HEAP_SIZE ,发现是在heap_4.c中使用了。
stm32以及freertos 堆栈解析_第7张图片
按照当前的设置可以看到,freertos是直接定义了一个静态数组 hcHeap大小为configTOTAL_HEAP_SIZE。

configAPPLICATION_ALLOCATED_HEAP这个宏定义一般是将堆设置为外部sram才会用到。这里暂时不多讲。

从上图可以得出这样的结论,默认情况下,freertos的堆就是自定义的一个大型数组,与启动文件中设置heap_size没有任何关系。所以只要只要没有调用系统的malloc函数,启动文件heap_size还真可以设置为0.

接下来,将configTOTAL_HEAP_SIZE改成20Kb进行编译。结果如下
stm32以及freertos 堆栈解析_第8张图片
zi-data由6142变成了25868,二者差值刚好是19kB。编译后的map文件内容如下:

stm32以及freertos 堆栈解析_第9张图片
与上一次的对比,.bss段的heap_4.o占用的空间由0x400(1kB)变成了0x5000(20kB),刚好是configTOTAL_HEAP_SIZE宏定义设定的大小。

4、结论

经过了上面的实验分析可以得出如下结论:

  1. 当freertos采用heap_4内存分配方案时,stm32启动文件中的stack_size 和heap_size与freertos中设置的堆大小没有任何关系。
  2. 只要代码中没有使用系统malloc函数,启动文件heap_size可以设置为0
  3. mcu运行时的ram空间= RW-Data+ZI-Data+启动文件中的heap_size,所以可以根据这个公式来设置freertos堆的大小。freertos的堆尽量要设置的大一点。

5、freertos堆常用函数heap_4

stm32以及freertos 堆栈解析_第10张图片
上还是那个图来自freertos的官方文档,可以看出freertos的栈实际实际上也是放到了堆空间。任务控制块TCB、queue 、pvPortMalloc等使用都是堆空间,对空间大小有configTOTAL_HEAP_SIZE决定。

5.1常用heap相关函数

获取剩余heap空间大小。

size_t xPortGetFreeHeapSize( void );

获取最小未分配的空间大小

size_t xPortGetMinimumEverFreeHeapSize( void );

动态内存分配及释放函数

void * pvPortMalloc( size_t xWantedSize )
void vPortFree( void * pv )

5.2任务栈空间占用的大小。

freertos中每个任务新建的时候都要设置栈空间。栈空间可以在FreeRTOSConfig.h中设定一个最小值

#define configMINIMAL_STACK_SIZE	( ( unsigned short ) 128 )  //设置最小栈空间为256字节

记住这里设定的和任务新建时设定的大小单位并不是字节,而是字,字占4个字节,所以上述设定的最小栈空间为256字节。由上文可知,freertos的栈空间实际是在堆里面。

    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask )

pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) )

 #define pvPortMallocStack    pvPortMalloc

在新建任务函数中,可以看到,实际上使用pvPortMalloc分配的一段空间作为栈空间,大小为usStackDepth *sizeof( StackType_t ) 。而StackType_t 大小实际上是uint32_t 为4字节。

stm32以及freertos 堆栈解析_第11张图片

你可能感兴趣的:(freeRTOS,内存分配,freertos,stm32,堆栈,单片机)