单片机/MCU内存分配解读
目录
1.前言
2.FLASH与RAM的基本结构
3.keil 编译后的字段解析
4 .堆与栈
5 . 验证修改栈大小对程序编译的影响
6 . 验证局部变量对程序编译的影响
1.前言
本文主要针对如何合理的使用GDM32的RAM角度入手,对GDM32的RAM进行分配与计算。目的是降低RAM的使用率,将RAM的使用情况都弄清楚,从而合理的规划及分配内存。本文涉及到一些堆栈方面的思考,在MDK中查看MAP文件及堆栈使用情况的文件进行分析,得出当前程序RAM的分配情况,同时对可以缩减的地方进行分析.
2.FLASH与RAM的基本结构
单片机内存被总分为flash(rom)和sram(ram),flash里面的数据掉电可保存,sram中的数据掉电就丢失,sram的执行速度要快于flash,flash容量大于sram.
我们正常下载程序都是下载存储进flash里面,这也是为什么断电可保存的原因。单片机的程序存储分为code(代码存储区)、RO-data(只读数据存储区)、RW-data(读写数据存储区) 和 ZI-data(零初始化数据区)。Flash 存储 code和RO-data,Sram 存储 RW-data 和ZI-data.。
一个进程运行时,所占用的内存,可以分为如下几个部分:
(1)栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。
(2)堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS释放。
(32)全局变量、静态变量:初始化的全局变量和静态变量放在一块区域,未初始化的全局变量和和未初始化的静态变量在相邻的的另一块区域。程序结束后由系统自动释放。
(4)文字常量:常量字符串就是放在这里的。这些数据是只读的,分配在RO-data(只读数据存储区),则被包含在flash中,程序结束后由系统自动释放。
(5)程序代码(code):存放函数体的二进制代码。
3.keil 编译后的字段解析
随机采用一套代码做测试,初始化内容如下:
int buf[10] = {0};
int main()
{
for(int i = 0;i < 10;i++)
{
buf[i] = i;
}
}
使用 keil 编译完程序,编译成功之后,会在输出栏提示一行这样的信息 :
首先,我们来看看各自段代表的都是什么含义:
Code 段:代表的就是我们所编写的代码所占用的 flash 空间。当然需要注意的是,代码段的内容 是经过编译器优化以及汇编之后的大小,你在代码里添加注释,或者一些无意义的代码是不会增大code段的大小的。
RO-data段:即read only – data,只读数据段。在代码中定义的 const 常量,printf 打印的固定字符等,这些均会被存放到此数据段内。
RW-data(读写数据存储区):存储已初始化的全局变量和静态变量。
ZI-data(零初始化数据区):存储未初始化的全局变量和静态变量,程序开始运行时初始化为0。
关于占用 ram 和 rom 空间又是如何计算,直接提供给大家以下公式:
Total RO Size = Code + RO Data
Total RW Size = RW Data + ZI Data
Total ROM Size = Code + RO Data + RW Data
4 .堆与栈
MCU的RAM空间被划分为多个区域,其中有两个重要组成:堆和栈(当前还有其他区域),此两个区域的大小设置可在对应的启动文件(startup_xxxxx.s)内查看并设置。
栈的使用:在我们程序编码过程中,在函数内部定义一个局部变量,其实是在栈空间上申请了一个内存存放数据;程序运行过程中,进入某个函数,需要将当前函数内的变量进行现场保存,也即压栈操作,及将当前的寄存器(如r0 r1等)进行压栈操作,也是从栈空间上占用一块内存存放数据;而函数退出的时候,需要进行恢复现场,及出栈操作,也即从栈空间内读取之前进入函数时进行压栈操作的数据,恢复寄存器数据,继续跑后续代码。
堆的使用:在我们程序执行过程中,调用malloc实际便是从堆上申请一块内存用于使用。使用完成之后,需要调用free释放那块内存区域。
此外针对裸机程序,整个程序中使用的栈,均从启动文件startup_xxxx.s 文件中 Stack_Size 设置的栈空间中分配;而RTOS程序,各线程具有独立的栈空间,这一点需要注意。RTOS任务栈:大多数在移植了freertos后,没怎么用RAM,但是发现RAM内存都快没了,那是freertos中有个动态分配的任务栈空间大小的宏,configTOTAL_HEAP_SIZE,老版本的freertos中没有静态创建任务,是用动态分配一块RAM空间给任务栈。这个你是可以自己根据单片机容量大小,以及你程序大小来进行配置的。如果你MCU容量实在是有限,可以适当减少值。
5 . 验证修改栈大小对程序编译的影响
做如下实验:程序保持不变仅仅修改启动文件中栈的大小,验证栈大小设置,对程序编译的影响
(1)设置 Stack_Size EQU 0x1000
(2)设置 Stack_Size EQU 0x800
(3)设置 Stack_Size EQU 0x400
比对以上结果可知,栈大小的设置会直接影响程序RAM的消耗,由于上述我们只是简单的修改了栈的大小,缩减了栈的空间,缩减的部分栈并没有被程序使用到,故看到的只是ZI-data
数据段的变化
6 . 验证局部变量对程序编译的影响
设置启动文件,栈大小为Stack_Size EQU 0x400
,即分配的栈空间为 0x400 = 1024Byte
在main()函数内部定义一个int型局部数组buf,数组大小为10,编译程序,查看程序编译大小变化
在main()函数内 定义一个int型局部数组buf,数组大小为400*4,编译程序,查看程序编译大小变化
结论: 仅code字段发生了变化,RAM空间并未发生改变。因为局部数组buf长度的增加,增加了代码段的内容,该操作并未改变RW-data、ZI-data段内容。
在main()函数内 定义一个int型局部数组buf,数组长度为1025,编译程序,查看程序编译大小变化
结论: 编译结果显示仍然 仅code字段发生了变化,RAM空间并未发生改变,但是实际运行程序会直接崩溃,仿真运行发现程序已卡死在HardFault_Handler()
中断。
6 . 验证全局变量对程序编译的影响
设置启动文件,栈大小为Stack_Size EQU 0x400
,即分配的栈空间为 0x400*4 = 4*1024Byte
在main()函数外部定义一个int型全局数组buf,数组大小为10,编译程序,查看程序编译大小变化
结论:编译结果显示仍然 仅ZI-data字段发生了变化。
作者:@一坨阳光️