mokongking

STM32,堆栈,RAM,FLASH

刚拿到STM32时,你只编写一个死循环

详细解析STM32中的堆栈机制

编译后,就会发现这么个程序已用了1600多的RAM,这要是在51单片机上,会心疼死了,这1600多的RAM跑哪儿去了,分析.map文件,你会发现是堆和栈占用的

在startup_stm32f10x_md.s文件中,它的前面几行就有以下定义:

详细解析STM32中的堆栈机制

这下明白了吧,STM32在启动的时候,RAM首先分配给使用到的全局变量,还有调用库占用的一些数据(不太清楚是什么数据),然后再将剩余的空间分配给Heap和Stack。由于内存空间是启动时实现分配好的,所以当动态分配内存的需求过多的时候,就会产生堆栈空间不足的问题。

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

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

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

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

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

 STM32在启动的时候,RAM首先分配给使用到的全局变量,还有调用库占用的一些数据(不太清楚是什么数据),然后再将剩余的空间分配给Heap和Stack。由于内存空间是启动时实现分配好的,所以当动态分配内存的需求过多的时候,就会产生堆栈空间不足的问题。

堆和栈的区别:

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

- stack的空间有限,heap是很大的自由存储区。

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

堆和栈空间分配:

- 栈:向低地址扩展- 堆:向高地址扩展

显然如果依次定义变量,先定义的栈变量的内存地址比后定义的栈变量的内存地址要大,先定义的堆变量的内存地址比后定义的堆变量的内存地址要小。

堆和栈变量:

- 栈:临时变量,退出该作用域就会自动释放-函数调用时参数的传递也是在栈上进行。

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

写程序时应该注意:

1. 所以最好是不要调用太深。2. 局部变量不要太大太多,如局部数组,超过某个数量需定义为全局数组,因为局部数组同样储存在堆栈中。不用malloc局部变量就会存在栈中

 

一个程序被加载到内存中,这块内存首先就存在两种属性:静态分配内存和动态分配内存。 
静态分配内存:是在程序编译和链接时就确定好的内存。 
动态分配内存:是在程序加载、调入、执行的时候分配/回收的内存。

 

任何一个程序本质上都是由 bss段、data段、text段三个组成的。

C语言上分为栈、堆、bss、data、code段。

bss段:

  bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。

  bss是英文Block Started by Symbol的简称。

  bss段属于静态内存分配。 

data段:

  数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。

  数据段属于静态内存分配。 

text段:

  代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。

  这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。

  在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。 

注意:

l          BSS区(未初始化数据段):并不给该段的数据分配空间,仅仅是记录了数据所需空间的大小。

l          DATA(初始化的数据段):为数据分配空间,数据保存在目标文件中。

 

上面这三段内存就组成了我们编写的程序的本体,但是一个程序运行起来,还需要更多的数据和数据间的交互,否则这个程序就是死的,无用的。所以我们还需要为更多的数据和数据交互提供一块内存——堆栈。

 

堆(heap):

  堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。

  当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);

  当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

栈(stack)

   栈又称堆栈,是用户存放程序临时创建的局部变量,

  也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。

  除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。

  由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。

  从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<string.h>
using namespace std;

static int a=1;//全局初始化区
int b=2;//全局初始化区
char *p;//全局未初始化区
char *p2;//全局未初始化区,BSS段
int *p3;//全局未初始化区 ,BSS段
int *p4;//全局未初始化区 ,BSS段
char *p5={"555555555"};//全局初始化区

int main(){
    static int c=3;
    int d=4;//内存栈
    int e=7;//内存栈

    char *p6={"555555555"};
    p=(char*)malloc(sizeof(char)*10);//内存堆
    p2=(char*)malloc(sizeof(char)*10);//内存堆
    p3=(int*)malloc(sizeof(int));//内存堆
    p4=(int*)malloc(sizeof(int)*10);//内存堆
    for(int i=0;i<=9;i++)p4[i]=0x1;


    *p3=0x123;
    strcpy(p,"123456789");//文字常量区
    strcpy(p2,"987654321");
    strcpy(p2,"123456789");
}

Flash,SRAM寄存器和输入输出端口被组织在同一个4GB的线性地址空间内。可访问的存储器空间被分成8个主要块,每个块为512MB,如下图,SRAM和FLASH在块1和块0。

FLASH存储下载的程序,全局变量和静态变量,在运行时,系统会把这些变量搬运到SRAM。同时根据代码定义,未初始化的全局变量在开始运行时会在SRAM开辟一块空间保证使用。(pass:如果

在变量前面加了const限定,那么程序运行直接读取flash的数据,不会搬到SRAM,这是由于ARM是哈佛结构而非冯诺依曼结构   https://blog.csdn.net/qq_29344757/article/details/75730054   )

SRAM是存储运行程序中的数据,所以,在运行时,我们定义的没有初始化的全局变量和没有初始化的静态变量,使用的局部变量,堆栈都放在SRAM中。

所以,只要你不外扩存储器,写完的程序中的所有东西也就会出现在这两个存储器中。

stm32的堆栈理解:

C语言上分为栈、堆、bss、data、code段。

C语言在单片机和PC上的内存中的区域有一点不同的是,单片机的code段是直接在flash中的,是可以直接从flash读取代码并执行的,而PC的code段是在内存中的,在运行一开始需要从硬盘中将代码搬运到内存中

MDK下Code, RO-data,RW-data,ZI-data这几个段:

Code是存储程序代码的。

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

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

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

所以对应起来stm32中:

Flash=Code + RO Data + RW Data;

RAM= RW-data+ZI-data;

 这个是MDK编译之后能够得到的每个段的大小,也就能得到占用相应的FLASH和RAM的大小,但是还有两个数据段也会占用RAM,但是是在程序运行的时候,才会占用,那就是堆和栈。在stm32的启动文件.s文件里面,就有堆栈的设置,

其实这个堆栈的内存占用就是在上面RAM分配给RW-data+ZI-data之后的地址开始分配的。所以要注意合理的栈大小设置。

 

OS中的堆栈及其内存管理。

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

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

(2)​就是把编译器没有用掉的RAM部分拿来做内存分配,也就是除掉RW-data+ZI-data+编译器堆+编译器栈后剩下的RAM内存中的一部分或者全部进行内存管理和分配。这样的情况下就只需要知道内存剩下部分的首地址和内存的尾地址,然后要用多少内存,就用首地址开始挖,做一个链表,把内存获取和释放相关信息链接起来,就能及时的对内存进行管理了。内存管理的算法多种多样,不详说,这样的情况下:OS的内存分配和自身局部变量或者全局变量不冲突。

 

查看.map文件,有如下例子:

 

total ROM Size (Code + RO Data + RW Data)这样所写的程序占用的ROM的字节总数,也就是说程序所下载到ROM flash 中的大小。为什么Rom中还要存RW,因为掉电后RAM中所有数据都丢失了,每次上电RAM中的数据是被重新赋值的,每次这些固定的值就是存储在Rom中的,为什么不包含ZI段呢,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可,包含进去反而浪费存储空间。

实际上,ROM中的指令至少应该有这样的功能:
       1. 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。
       2. 将ZI所在的RAM区域全部清零,因为ZI区域并不在RAM中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中。
       在程序运行的最初阶段,RO中的指令完成了这两项工作后C程序才能正常访问变量。否则只能运行不含变量的代码。

posted on 2024-10-25 23:16  虎啸岳林  阅读(8)  评论(0编辑  收藏  举报

导航