单片机开发也是嵌入式开发中的一个大群体,有许多的的人是进行单片机逻辑开发的,也有些人是单片机+嵌入式实时操作系统,当然也有单片机+linux+人工智能技术的。
当然,不管你是什么样的组合方式,只要你最终开发的产品中有使用到MCU,进行程序开发时,都应该会涉及到内存的分配问题。只要是开发程序过程中有过动态申请内存的朋友,对malloc、free估计是不会陌生的。
很多时候单片机的内存分配是会让人感觉到头疼的,有些单片机的内存很大,可以肆无忌惮的申请,有些内存空间又很有限,捉襟见肘,既要完美的实现产品功能,又要考虑内存的问题,不断的各种优化进行适配。而且可能很多人玩了几年的单片机可能都不清楚单片机内部的内存是如何分配的。
想要清楚单片机内部的内存是怎么分配的,我觉得首先应该要知道单片机内部都有哪些可供使用的存储空间。
常见的单片机它的内存一般有两部分:ROM + RAM。这两个到底有什么不同呢?
1、ROM & RAM
RAM+ROM 的组合方式就像电脑里面的运行内存(ram)+硬盘(rom)的组合方式。
(1)ROM
ROM:我们常说的存储数据的部分,它的一般的特点是只读(only read)。ROM在单片机中常用于BootLoader、应用程序App、OTA升级包存放、某些掉电以后希望被保存的数据等等的。
最早的出现的ROM是PROM,它只能编程一次,无法重复写入,当时基本用于固化程序或者保存一些重要数据。但是也有弊端,就是一旦出现写入错误,因为不能重复写入的缘故,就只能废弃。
后面随着相关技术的进步和发展,发展出紫外线照射下可写可擦的EPROM,但是那个时候的EPROM的擦写是很麻烦的,需要专门的仪器。
鉴于EPROM的擦写麻烦,又发展出了EEPROM,它可以通过电可擦的的方式进行擦写,使用的门槛降低了,成本可以大幅减低,应用场景是很大的。
还有到今天为止常见的Flash,这也是属于ROM类的存储方式。很多的单片机都有自带的Flash,而且现在的单片机Flash容量也是越做越大。还有我们常说的 Nor Flash、NAND Flash等等的。
这几类ROM的特点如下:
(2)RAM
RAM:随机存储器,可按字节读写,读写速度快,但它也有缺点,就是掉电以后会丢失数据。所以,RAM一般用作运行时的内存空间分配。这就跟我们的手机的运行内存是一个道理的,运行内存的空间小了手机还会卡。
RAM 的特点和作用如下:
在单片机中,RAM一般都被分配为堆、栈、变量等的空间,还有某些编译时放在ROM的变量也会在上电过程中拷贝到RAM中。
而且,我们常说的内存泄漏和内存溢出,它其实也都是发生在RAM中的。
2、内存模型 & 空间分配
在很多的单片机中,它的内存分配的一般模型如下图:
在内存中的分配模型中,包含了五个部分:
1、栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。
2、堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS释放。内存碎片化也是出现在这个部分。
3、数据区(.bss、.data):初始化的全局变量和静态变量放在一块区域,未初始化的全局变量和和未初始化的静态变量在相邻的的另一块区域。程序结束后由系统自动释放。
4、文字常量:常量字符串就是放在这里的。这些数据是只读的,分配在RO-data(只读数据存储区),则被包含在flash中,程序结束后由系统自动释放。
5、程序代码(code):存放程序代码的,一般是函数体的二进制代码。
单片机程序中的定义变量的所在空间说明如下:
3、STM单片机的内存分配的分析
使用过STM32的朋友应该知道map文件是可以提供程序的内存和链接的相关信息的,下面就用STM32的map文件说明一下它的内存分配方式。
在map文件的最后,都会有这样的汇总信息,如下图:
上图中:
Code:代码存储区。这部分是存放代码的。
RO-Data:只读数据区。这部分保存程序中用 const 定义的全局常量数据和字符串。
RW-Data:已初始化的读写数据。程序中定义的已经初始化的全局变量和静态变量。
ZI-Data:未初始化的读写数据。程序中定义的未初始化的全局变量和静态变量。这部分内容是在程序运行的时候保存在RAM中的。
map文件中还做了统计:
从上图可以看出:
RO:只读数据。这部分包含 Code 和 RO-Data 这部分是存放在Flash中的。
RW:可读写数据。这部分包含 RW-Data 和 ZI-Data,这部分是存放在RAM中的,就是占据运行内存的。
ROM Size:总的ROM的大小。这部分包含Code、RO-data、RW-Data,是程序中占Flash的实际大小。
为了方便理解,可以参考下图: