以cortex-M3为例,例如STM32F103

 

这篇文章要讲2个问题:

1、编译出的程序(指令)、变量的存放位置、大小?
2、在代码和keil中,“堆、栈”两者的大小如何设置?

 

keil编译完成后,会有提示,形如:

Program Size: Code=1148 RO-data=424 RW-data=20 ZI-data=1636  

其中:
① Code为代码,本质上就是一大堆ARM指令;
② RO为只读的数据,例如,char *name = "TOM";//TOM三个字符就存放在ROM中作为RO-DATA;又如,为了减小sin的计算量,把sin的各个值直接制作成表,const float sinVal[]= {.....};
③ RW为非0变量占用的RAM大小,同时还要占用等量的ROM大小用于存放这些非0变量的初值;
④ ZI为0初始化变量占用的RAM大小(该区域2个用途:0初始化的全局和静态变量+栈区)。

下面是keil自动生成的.map文件中的信息:

STM32的Code RO RW ZI区、Flash/Ram的占用情况、堆栈大小Stack_Size/Heap_Size的设置

Flash的占用量就是上图中ROM Size的大小,它包含了①+②+③的大小【ARM指令代码+只读数据+非0初始化变量的初值】
RAM的占用量包含上述③+④的大小,也即【非0初始化变量、0初始化RAM(又分为0初始化变量区+栈区)】

 

我们用jflash或者其他软件打开hex文件,看一下前四个字节:

STM32的Code RO RW ZI区、Flash/Ram的占用情况、堆栈大小Stack_Size/Heap_Size的设置

0x678=1656,而观察一下前文编译出的信息,正好1656 = RW-data(1636) +  ZI-data(20)的大小,这就证明了RAM的大小确实=RW+ZI的大小。

注:前4个字节为主堆栈指针MSP的初值。

 

 

在STM32的启动文件(.s汇编文件)中,一开始我们会看到 Stack_Size、Heap_Size 这两个汇编宏定义:
STM32的Code RO RW ZI区、Flash/Ram的占用情况、堆栈大小Stack_Size/Heap_Size的设置

 

Stack指的是栈,栈是由堆栈指针MSP/PSP自动管理的,理论上初始化时给堆栈指针赋值为RAM的最高地址即可,但是用keil编译时,keil并不是按RAM的最高地址生成的HEX前4字节(也即MSP的初值),而是按照:全局和静态变量的容量+Stack_Size之和,作为HEX文件的前4字节。

设置某块芯片的RAM的总大小,是在keil的这个地方设的:
STM32的Code RO RW ZI区、Flash/Ram的占用情况、堆栈大小Stack_Size/Heap_Size的设置

其实讲道理,我觉得keil还是把这个值+RAM起始地址(0x2000000)作为MSP的初值更好,只不过keil不是这么做的,keil仅仅是把这个值用作编译检测:检查全局变量+局部静态变量+栈区,三者所占空间之和是否超过了这个值,如果超了就编译报错。上面红框里的值唯一的用途就是这样。

根据这段理论,我们也就知道了启动文件中Stack_Size该设为多少了,只要【全局变量+局部静态变量+Stack_Size】的大小不超过芯片的RAM容量即可。

如果我们还想使用C库函数提供的内存堆(heap),那么我们要保证【全局变量 + 局部静态变量 + Heap_Size + Stack_Size】小于芯片的RAM容量。keil编译时,会把C库函数支持的malloc、free所需的内存指向heap内存区,并且当我们不断的malloc吃内存时,malloc函数会检测程序已吃掉的内存堆是否超过了Heap_Size,超了的话就会返回NULL。


如果我们的程序+启动文件的设置,导致【全局变量 + 局部静态变量 + Heap_Size + Stack_Size】小于芯片的RAM容量,那么keil会这么分配内存:

STM32的Code RO RW ZI区、Flash/Ram的占用情况、堆栈大小Stack_Size/Heap_Size的设置

上面论述的是,使用C库函数提供的mallloc时,内存堆heap的使用情形,如果我们不打算使用C库提供的动态内存分配,而打算使用自己写的内存堆管理程序,甚至干脆不打算使用动态内配功能,这些情况下,请直接把Heap_Size设为0。

 

最后总结一下Stack_Size的设置方法:①正确设置芯片的RAM大小, ②确定堆区Heap_Size的大小,这个值可以通过调试自己估计出来,如果不使用C库的malloc的话,直接设为0,③把Stack_Size设的越大越好,只要编译不报错,就把Stack_Size往尽可能大了配。over

 

PS:自己写的动态内存管理程序,本质上就是申请一个大数组,自己管理这个数组而已,相当于自己写一套malloc/free函数,很多操作系统中有这样的例子,比如freeRtos、ucos等,这些操作系统都提供了好多种动态内存管理方法,最简单的一种,跟切火腿似的,malloc出来之后就释放不掉了,直到把这个大数组切完拉倒,看似很low的内存管理方法,实际上在很多小型项目上却用起来很方便。更好的内存管理程序,那自然是支持动态切,还要支持把动态释放的空间合并起来,以便支持再次malloc,这些更好的内存管理程序,用起来更方便,同时这个管理程序自身也会占掉很多RAM/ROM/CPU资源,矛盾利弊共存吧。

 

 

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-09-13
  • 2021-11-27
  • 2021-11-05
  • 2021-06-13
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-12-11
  • 2021-11-23
  • 2021-07-09
  • 2021-06-11
  • 2021-06-02
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案