芯片:STM32F415
IDE:IAR
系统:FreeRTOS(庆科)
0 背景
由于代码本身基于就是庆科修改过的FreeRTOS,又加入了别的没有完全开源的库,再加上不是自己从头写的祖传代码,自己又经验不足,因此几个月来一直被这套代码折磨。其中尤为突出的一个问题就是程序复位问题。表现出来一般都是提示栈空间溢出,实际上这个提示粒度非常大,很难定位问题。
甚至发现,有些逻辑线程,只在刚开始启动时执行了一次,后来就再也没有被调用。这个就叫做程序会发生不可预知的问题。
为了彻底解决这个问题,用了一个星期的时间,重新学习梳理了一下内存、系统相关的知识。最后问题得到了良好的解决。
前两个是准备工作,后面是分析和梳理。
1 IAR的map文件
IAR可以生成一个map文件,在这个文件里包含了程序编译的情况,堆栈分析,函数入口地址,存储位置等内容。调试异常的bug时很好用。
在工程上右键,option-linker-list,勾上generate linker map file:

在advance选项卡里,勾上栈分析:

编译程序之后,在工程中就会生成一个.map文件:

2 IAR的icf文件
在工程中搜索,会找到一个后缀为icf的文件,在这个文件里,配置了芯片的堆栈等信息:
/*###ICF### Section handled by ICF editor, don't touch! ****/
/*-Editor annotation file-*/
/* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */
/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x08008000; /*中断向量表开始地址*/
/*-Memory Regions-*/
define symbol __ICFEDIT_region_ROM_start__ = 0x08008000; /*闪存起始地址*/
define symbol __ICFEDIT_region_ROM_end__ = 0x080FFFFF; /*闪存结束地址---flash大小1M*/
define symbol __ICFEDIT_region_RAM_start__ = 0x20000000; /*SRAM起始地址*/
define symbol __ICFEDIT_region_RAM_end__ = 0x2003FFFF; /*SRAM结束地址---SRAM大小256k*/
/*-Sizes-*/
define symbol __ICFEDIT_size_cstack__ = 0x300; /*栈大小*/
define symbol __ICFEDIT_size_heap__ = 0x20000; /*堆大小*/
/**** End of ICF editor section. ###ICF###*/
define memory mem with size = 4G;
define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];
define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__];
define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { };
define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { };
initialize by copy { readwrite };
do not initialize { section .noinit };
place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
place in ROM_region { readonly };
place in RAM_region { readwrite,
block CSTACK, block HEAP };
具体的含义写在了注释里,可以在IAR中修改:

3 STM32 存储结构基本知识
3.1 cortex-M内核
这里只讨论存储结构,我这个芯片是cortex-M4内核,实际上cortex-M内核的存储结构是一样的。
32位的芯片,PC指针的寻址空间为2^32=4G。我觉得这个跟误传的所谓32位芯片只能用4G内存是两码事。
这个4G只是寻址空间为4G,分为8大块,分别为代码,SRAM,外设,外部RAM,外部设备,专用外设总线-内,专用外设总线-外,特定厂商。而我们俗称的内存,指的是SRAM。
下面这个是M3内核与STM32存储的对应关系:

M3内核做了存储结构的设计,STM32以其设计为各个部分分配地址,这个“房间”是不一定要放满的,只要对于的东西在对应的房间就好了。
这里用的芯片可用flash大小是1M,就是从0x8000000到0x80fffff,但是icf里面写的是0x8008000到0x80fffff,前面的0x8000实际上分配给了BootLoader程序。
这个芯片的SRAM是256K,即从0x200000-0x2003ffff。
3.2 程序与存储
程序经过编译后,会形成以下几个部分:
- .bss
未初始化的全局变量或初始化为0的全局变量。未初始化的全局变量和初始化为0的全局变量(当然还有静态变量),都会被单独存放。这样的好处是节省编译后代码的空间,比如定义了100个变量都初始化为0或者没初始化,默认是0.在编译好的文件里,就不需要记录每个变量及它的值了,只需要记下来这些变量都是0就可以了。也就是说,凡是已经初始化了的全局变量,实际上在编译出的文件里,还要记录下它的初始化的值。 - .text段
代码段。在程序运行前,其大小以及确定,在存储器中的位置也是确定的。包括函数的入口地址都已经分配好了。 - .data段
数据段,已经初始化的全局变量。参考.bss段。 - .rodata段
rodata段就是read only data,即常量的存储区域。
上面这些编译好的部分,下载后会存储在芯片的flash中,也就是上面说的1M的ROM空间里。
程序运行的时候,局部变量会占用栈空间(由编译器生成的程序会将全局变量复制到RAM中,全局变量不占用栈空间,是单独分配的空间)。栈空间的大小就是前面设置的那个。
前面设置的栈空间和堆空间,使用的其实都是RAM空间,设置的栈空间会被全部初始化为0xcd,堆空间会被全部初始化为0xa5。程序就是通过判断0xcd和0xa5还剩余多少,来推测堆栈空间还剩多少的。所以基本上只是个参考意见。大多数情况下是对的。
,局部变量和函数调用占用栈空间,不过一般都不会占用很多。仿真时,可以打开IAR的View->Stack->Stack Window来监测栈空间还有多少。当栈提示溢出时,也不一定是真的溢出了。

当需要定义一个很大的数组时,推荐使用内存分配的方式来做,这部分空间是从堆分配的。如果是加密使用的数组,只需要只读就可以了,建议定义为常量数组,这样它会存储在ROM中,比分配空间还要好。全局变量虽然不会占用栈空间,但是过多很大的全局变量也会引发问题,至少会占用很多RAM。
堆空间除了使用malloc分配使用以外,对于有操作系统的程序。比如FreeRTOS,它在建立线程时,分配的栈空间实际上是在堆中分配的,而且,线程中使用的局部变量,调用函数的局部变量,调用函数的栈开销,也是占用的分配的堆空间。反正都是RAM空间,一样的用。需要注意的是不会占用上面所说的全局分配的栈空间,所以有操作系统之后,原来的栈空间并不需要分得很大。
4 map和线程栈空间分配
可以通过map文件,来大概的确定线程的栈空间大小。
只要不是过多使用大量很大的临时变量的线程,map计算的栈空间一般都够用。为了保证内存对齐,以防发生奇怪的bug,线程的栈空间分配应该保持是8的倍数。
安全起见,可以多分64个字节的栈空间。
在map文件中找到STACK USAGE:

搜索对应的函数,就可以看到大概的栈空间值:

在map文件的最后,还能看到总的空间使用统计:
256 660 bytes of readonly code memory
23 842 bytes of readonly data memory
172 218 bytes of readwrite data memory
网友评论