关于stm32的堆、栈、内存管理以及外扩ram的使用总结

时间:2024-04-03 12:43:36

刚接手STM32时,编写一个空工程,BUILD后,

Program Size: Code=340 RO-data=252 RW-data=0 ZI-data=1632 

程序已用了1600多的RAM,要是在51单片机上,会心疼死了,这1600多的RAM跑哪儿去了,

分析map,你会发现是堆和栈占用的,在startup_stm32f10x_md.s文件中,它的前面几行就有以上定义,这下该明白了吧。

Stack_Size   EQU   0x00000400

Heap_Size   EQU   0x00000200

对于一个C语言程序而言,内存空间主要由五个部分组成:代码段(.text)、数据段(.data)、静态区(.BSS)、堆和栈组成。

        BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量和静态变量 (这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就是我们这里所说的未初始化。既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间,这是BSS的主要作用)的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小,以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映象文件中并不存在,即不占用磁盘空间 而只在运行的时候占用内存空间 ,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。

  
        数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。 字符串常量等,但一般都是放在只读数据段中 。
  
        代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中 。 
  

        栈区:由系统自动分配,栈区的分配运算内置于处理器的指令集,当函数执行结束时由系统自动释放。存放局部变量。栈的缺点是:容量有限,当相应的区间被释放时,局部变量不可再使用。查询栈容量的命令:ulimits -s。栈是一块连续的区域,向高地址扩展,栈顶和容量是事先约定好的。

        

        堆区:在程序的执行过程中才能分配,由程序员决定,编译器在编译时无法为他们分配空间,只有在程序运行时分配,所以被称为动态分配。堆是不连续的区域,向高地址扩展。由于系统用链表来描述空闲的地址空间,链表的遍历是由地地址向高地址的,故堆区是不连续的动态的存储空间。

重点分析一下STM32以及在MDK里面段的划分。MDK下Code,RO-data,RW-data,ZI-data这几个段:

Code是存储程序代码的。

​RO-data是存储const常量和指令。

​RW-data是存储初始化值不为0的全局变量。

​ZI-data是存储未初始化的全局变量或初始化值为0的全局变量。

Flash=Code + RO Data + RW Data;

RAM= RW-data+ZI-data;

这个是MDK编译之后能够得到的每个段的大小,也就能得到占用相应的FLASH和RAM的大小,但是还有两个数据段也会占用RAM,但是是在程序运行的时候,才会占用,那就是堆和栈。在stm32的启动文件.s文件里面,就有堆栈的设置,其实这个堆栈的内存占用就是在上面RAM分配给RW-data+ZI-data之后的地址开始分配的。

我们知道了SRAM空间用来存放了什么东西了:1、各个文件中声明和定义的全局变量、静态数据和常量;2、HEAP区;3、STACK区

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(1)栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。

(2)堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。

(3)全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统自动释放。

(4)文字常量区:常量字符串就是存放在这里的。

(5)程序代码区:存放函数体的二进制代码。

例如:

int a=0;   //全局初始化区

char *p1;   //全局未初始化区

main()

{

int b;   //栈

char s[]="abc";   //栈

char *p3= "1234567";   //在文字常量区Flash

static int c =0 ;   //静态初始化区

p1= (char *)malloc(10);   //堆区

strcpy(p1,"123456");   //"123456"放在常量区

}

所以堆和栈的区别:

stack的空间由操作系统自动分配/释放,heap上的空间手动分配/释放。

stack的空间有限,heap是很大的*存储区。

程序在编译期和函数分配内存都是在栈上进行,且程序运行中函数调用时参数的传递也是在栈上进行。

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

1.堆和栈大小

定义大小在startup_stm32f2xx.s

Stack_Size  EQU  0x00000400

Heap_Size  EQU  0x00000200

2.堆和栈位置

通过MAP文件可知

HEAP  0x200106f8  Section  512  startup_stm32f2xx.o(HEAP) 
STACK  0x200108f8  Section  1024  startup_stm32f2xx.o(STACK)

__heap_base  0x200106f8  Data  0  startup_stm32f2xx.o(HEAP) 
__heap_limit  0x200108f8  Data  0  startup_stm32f2xx.o(HEAP) 
__initial_sp  0x20010cf8  Data  0  startup_stm32f2xx.o(STACK)

显然 Cortex-m3资料可知:__initial_sp是堆栈指针,它就是FLASH的0x8000000地址前面4个字节(它根据堆栈大小,由编译器自动生成)

显然堆和栈是相邻的。

关于stm32的堆、栈、内存管理以及外扩ram的使用总结

3.堆和栈空间分配

栈:向低地址扩展

堆:向高地址扩展

显然如果依次定义变量

先定义的栈变量的内存地址比后定义的栈变量的内存地址要大

先定义的堆变量的内存地址比后定义的堆变量的内存地址要小

4.堆和栈变量

栈:临时变量,退出该作用域就会自动释放

堆:malloc变量,通过free函数释放

另外:堆栈溢出,编译不会提示,需要注意

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

如果使用了HEAP,则必须设置HEAP大小。 如果是STACK,可以设置为0,不影响程序运行。 IAR STM8定义STACK,是预先在RAM尾端分配一个字节的区域作为堆栈预留区域。 
当程序静态变量,全局变量,或者堆与预留堆栈区域有冲突,编译器连接的时候就会报错。 你可以吧STACK设置为0,并不影响运行。(会影响调试,调试会报堆栈溢出警告)。 其实没必要这么做。 
一般程序,(在允许范围内)设置多少STACK,并不影响程序真实使用的RAM大小, (可以试验,把STACK设置多少,编译出来的HEX文件都是一样), 程序还是按照它原本的状态使用RAM,把STACK设置为0,并不是真实地减少RAM使用。仅仅是欺骗一下编译器,让程序表面上看起来少用了RAM。 而设置一定size的STACK,也并不是真的就多使用了RAM,只是让编译器帮你 
检查一下,是否能够保证有size大小的RAM没有被占用,可以用来作为堆栈。 以上仅针对IAR STM8.

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

从以上网摘来看单片机的堆和栈是分配在RAM里的,有可能是内部也有可能是外部,可以读写;

:存函数的临时变量,即局部变量,函数返回时随时有可能被其他函数栈用。所以栈是一种分时轮流使用的存储区,

      编译器里定义的Stack_Size,是为了限定函数的局部数据活动的范围,操过这么范围有可以跑飞,也就是栈溢出;

     Stack_Size不影响Hex,更不影响Hex怎么运行的,只是在Debug调试时会提示错。栈溢出也有是超过了国界进行

     活动,只要老外没有意见,你可以接着玩,有老外不让你玩,你就的得死,或是大家都死(互相撕杀),有的人写

    单片机代码在函数里定义一个大数组 int buf[8192],栈要是小于8192是会死的很惨。

 

:存的是全局变量,这变量理论上是所有函数都可以访问的,全局变量有的有初始值,但这个值不是存在RAM里的,是

     存在Hex里,下载到Flash里,上电由代码(编译器生成的汇编代码)搬过去的。有的人很“霸道”,上电就霸占已一块很

    大的RAM(Heap_Size),作为己有(malloc_init),别人用只能通过他们管家借(malloc),用完还得换(free)。所以  

    一旦有“霸道”的人出现是编译器里必须定义Heap_Size,否则和他管家借也没有用。

 

总之:堆和栈有存在RAM里,他两各分多少看函数需求,但是他两的总值不能超过单片机硬件的实际RAM尺寸,否则只能

     到海里玩(淹死了)或是自己打造船接着玩(外扩RAM)。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,他们其实最终都是要实现 2 个函数: malloc 和 free; malloc 函数用于内存申请, free 函数用于内存释放。

嵌入式系统的堆栈,不管是用什么方法来得到内存,感觉他的方式都和编程中的堆差不多。目前我知道两种获得内存情况:

(1)用庞大的全局变量数组来圈住一块内存,然后将这个内存拿来进行内存管理和分配。这种情况下,堆栈占用的内存就是上面说的:如果没有初始化数组,或者数组的初始化值为0,堆栈就是占用的RAM的ZI-data部分;如果数组初始化值不为0,堆栈就占用的RAM的RW-data部分。这种方式的好处是容易从逻辑上知道数据的来由和去向。

(2)​就是把编译器没有用掉的RAM部分拿来做内存分配,也就是除掉RW-data+ZI-data+编译器堆+编译器栈后剩下的RAM内存中的一部分或者全部进行内存管理和分配。这样的情况下就只需要知道内存剩下部分的首地址和内存的尾地址,然后要用多少内存,就用首地址开始挖,做一个链表,把内存获取和释放相关信息链接起来,就能及时的对内存进行管理了。内存管理的算法多种多样,不详说,这样的情况下:OS的内存分配和自身局部变量或者全局变量不冲突,之前我就在这上面纠结了很久,以为函数里面的变量也是从系统的动态内存中得来的。这种方式感觉更加能够明白自己地址的开始和结束。