stm32内存架构及堆栈管理

stm32内存架构及管理

计算机的内存管理

学习stm32内存管理的时候有些云里雾里,网上也看了很多博客文档,但是大都没有很系统的去讲解stm32的内存架构。所以决定自己来做一个关于stm32内存架构的分析和自己的理解。

在讨论单片机内存管理之前,我想先说一下关于计算机的内存是如何管理的。根据《C++ Primer Plus(第6版)》这本书中所讲,C++(就内存管理方式而言类似C)有3种管理数据内存的方式:
①、自动存储
1. 函数内部定义的常规常量使用自动存储空间,该变量称为自动变量 (局部变量)。
2.调用函数时产生(进栈),函数结束时消亡(出栈)。后进先出(LIFO)。因此在一个函数块中,该区域将不断的
增大或者减小。
3.该区域我们一般称为栈区。
②、静态存储
1. 静态存储是一种整个程序执行期间都存在的存储方式。
2.定义该变量的方式有两种:在函数外面定义它/在声明变量时使用关键字:static。
③、动态存储
1. new(malloc)和delete(free)运算符(函数)提供了一种比自动变量和静态变量更加灵活的方法。
2.它们管理了一个内存池,这在C++中被称为自由存储空间或堆(heap)。
3.这种存储方式独立于另外两种,可以由程序员决定变量的产生和消亡。
我们大可以把这三个区域理解成stm32的SRAM存储区。

stm32的内存架构

stm32是32位单片机,可用寻址空间高达4GB。当然我们只用到了其中很小的一部分,以stm32f407为例,其内部Flash(ROM)大小为1M字节,SRAM大小为192Kb字节。
我们平时说的内存管理管理的便是SRAM中的动态存储区(堆)。而4GB寻址空间对应到stm32可由下图表示(出处见水印):
stm32内存架构及堆栈管理_第1张图片
由图可由清楚的看到Cortex-M3与stm32之间的关系,虽然我们stm32f407是基于Cortex-M4架构,但也可以通过这个图看出来4GB寻址空间和STM32之间的关系。我们的程序和常量是存储在Flash中的,调试模式下也可以看出PC指针始终在0X0800 0000后面的Flash区域,变量则存储在了SRAM中,SRAM的首地址为0X2000 0000。我做了一个架构图帮助大家更好地理解stm32的存储架构:
stm32内存架构及堆栈管理_第2张图片
其中内存地址是按照stm32f407去定义的,我们选择主芯片的时候除了看其所带外设ADC/TIM/DAC/FSMC等,还要看Flash和SRAM的大小够不够我们的项目使用。

如果我们的编译器是Keil(当然大多人都用这个)的话,会把这些存储区在细分为各个段区,比如我们随便用一个led工程编译一下:
stm32内存架构及堆栈管理_第3张图片
Flash = Code(程序代码) + RO - data(存储const常量和指令) + RW - data(初始值不为0的全局/静态变量)
SRAM = RW - data(程序运行后从Flash读入) + ZI - data(初始值为0或未初始化的全局/静态变量)

Ps:SRAM在编译时并不会把所有变量申请的内存显示完,因为SRAM是一个动态的存储的过程,所以不能看到SRAM大小满足板载SRAM大小,就认为程序满足硬件要求!当然涉及堆栈存储更为复杂,内存泄漏,内存叠加等等,这个比较重要所以我们后面单独说一下。

SRAM的内部结构

其实就我们平时写写程序就只需要知道有这么一个大小的SRAM给我们用就好了,但是如果基于操作系统OS的话,还是要更加细分SRAM区域,合理利用每一个区域去完成我们的项目,最大限度的利用单片机资源,这里我们放一张数据手册中的矩阵图:stm32内存架构及堆栈管理_第4张图片
我们可以看到192Kb的SRAM其实分为3部分,SRAM1,SRAM2,和64Kb的CCMRAM。查看数据手册的关于SRAM的介绍:
Seven slaves:
– Internal Flash memory ICode bus
– Internal Flash memory DCode bus
– Main internal SRAM1 (112 KB)
– Auxiliary internal SRAM2 (16 KB)
– AHB1 peripherals including AHB to APB bridges and APB peripherals
– AHB2 peripherals
– FSMC
我们可以看到SRAM1是主要的存储区,主要存放一些程序运行时产生的变量,SRAM2辅助内部存储区,主要作用于与外设数据有关的变量。而CCMRAM则比较特殊,stm32f1里没有这个东西,从矩阵图中也可以看出这个区域通过D总线直接和CPU相连,这意味着,CPU能以最大的系统时钟和最小的等待时间从CCM中读取数据或者代码。对于要求速度,精度高的项目我们可以尝试将变量定义在这个区域里面。定义的方法网上大概分为两种:
嫌麻烦的话就 u32 ccmTextNum attribute((at(0x10000000)))用at去定位到这个区域里面,还有一种方法分散加载文件(.sct),更为强大一些,感兴趣的可以自己去尝试一下。再Ps:CCMRAM没有与一些外设如DMA相连,说明这个区域的变量不可以通过DMA传输!

程序验证堆栈区地址及内存覆盖

说了这么多,现在大家对stm32内存架构可能会有更清晰的了解,我们现在动手验证一下我们所说的是否准确,首先我们先测试堆和栈的地址范围及容量大小(程序基于正点原子探索者):

仅验证堆栈首地址及其大小

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "stdlib.h"

//为了方便起见,STACK区(栈)我们用SRAM1来表示,HEAP区(堆)我们用SRAM2来表示,静态常量区我们用SRAM3来表示。

u8 SRAM3_Buffer[100]={0};//声明了一个初始化为0的全局数组,在静态常量区,0x2000 0000开头

int main(void)
{ 
	u8 SRAM1_i,SRAM1_j = 1,m;//i为普通的未初始化局部变量,j作为初始化的局部变量,测试可得均存在于栈区
	u8* pSRAM2 = (u8*)malloc(200);//指针pSRAM2指向堆区分配了一个u8类型200大小的数组的首地址,测试可得存在于堆区
	
	u8 SRAM1_Buffer[100] = {0};//声明了一个局部数组,存在于静态常量区。
	
	delay_init(168);		  //初始化延时函数
	LED_Init();		        //初始化LED端口
	uart_init(115200);
  
	for(m = 0;m < 100;m++)
	{
		SRAM3_Buffer[m] = m;
	}
	
	printf("未初始化局部变量SRAM1_i在栈中的地址为:0x%x\r\n",&SRAM1_i);
	printf("初始化的局部变量SRAM1_j在栈中的地址为:0x%x\r\n",&SRAM1_j);
	printf("初始化局部数组SRAM1_Buffer在栈中的地址为:0x%x\r\n",&SRAM1_Buffer);
	printf("调用malloc声明了一个在堆区的数组首地址为:0x%x\r\n",pSRAM2);
	//printf("静态常量区的数组的地址为:0x%x\r\n",SRAM3_Buffer);
	
//	printf("静态常量区的数组值为:\r\n");
//	for(m = 0;m < 100;m++)
//	{
//		printf("%d ",SRAM3_Buffer[m]);
//	}
	

	while(1)
	{
		delay_ms(500);
		
		LED1 = ~LED1;
	}
}

通过串口调试助手我们可以看到我们声明的变量的具体地址:
stm32内存架构及堆栈管理_第5张图片
可以看出前三个局部变量是在同一个区域,后一个动态变量在另一个区域,而这两个区域,就是我们平时说的堆栈区。堆栈区的大小我们可以从启动文件里得到:
在这里插入图片描述
在这里插入图片描述
从启动文件我们可以得到栈区大小为0x0000 0400(1Kb),堆区的大小为:0x0000 0200(512字节)。这里我们再放一张图,可以清楚的看到堆栈区与静态常量区的关系(出处见水印(我也看不清。。)):
stm32内存架构及堆栈管理_第6张图片
这里大家可能会发现我们申请的位于栈区的局部变量首地址,是位于栈顶的0x2000 06f0开始的,而位于堆区的动态变量首地址是从堆底0x200000f0开始的。这就涉及堆栈对于数据处理方式的不同,前面的文章也讲了,栈是后进先出,所以栈变量(局部变量)是从栈顶向下分配地址,而堆则相反,是先进后出,所以从堆底向上分配地址。那么有没有一种可能,栈变量我们申请的比较大,那么它就会一直向下申请内存,直到污染了HEAP和静态存储区,这就是比较可怕的内存叠加,我们在软件里模拟一下这个事件:

加上全局数组后的程序

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "stdlib.h"

//为了方便起见,STACK区(栈)我们用SRAM1来表示,HEAP区(堆)我们用SRAM2来表示,静态常量区我们用SRAM3来表示。

u8 SRAM3_Buffer[100]={0};//声明了一个初始化为0的全局数组,在静态常量区,0x2000 0000开头

int main(void)
{ 
	u8 SRAM1_i,SRAM1_j = 1,m;//i为普通的未初始化局部变量,j作为初始化的局部变量,测试可得均存在于栈区
	u8* pSRAM2 = (u8*)malloc(200);//指针pSRAM2指向堆区分配了一个u8类型200大小的数组的首地址,测试可得存在于堆区
	
	u8 SRAM1_Buffer[100] = {0};//声明了一个局部数组,存在于静态常量区。
	
	delay_init(168);		  //初始化延时函数
	LED_Init();		        //初始化LED端口
	uart_init(115200);
  
	for(m = 0;m < 100;m++)
	{
		SRAM3_Buffer[m] = m;
	}
	
	printf("未初始化局部变量SRAM1_i在栈中的地址为:0x%x\r\n",&SRAM1_i);
	printf("初始化的局部变量SRAM1_j在栈中的地址为:0x%x\r\n",&SRAM1_j);
	printf("初始化局部数组SRAM1_Buffer在栈中的地址为:0x%x\r\n",&SRAM1_Buffer);
	printf("调用malloc声明了一个在堆区的数组首地址为:0x%x\r\n",pSRAM2);
	printf("静态常量区的数组的地址为:0x%x\r\n",SRAM3_Buffer);
	
	printf("静态常量区的数组值为:\r\n");
	for(m = 0;m < 100;m++)
	{
		printf("%d ",SRAM3_Buffer[m]);
	}
	

	while(1)
	{
		delay_ms(500);
		
		LED1 = ~LED1;
	}
}

stm32内存架构及堆栈管理_第7张图片
这个首先我们在main函数添加了一个100位局部数组并且把0-99赋值进去,我们可以看到正常数组是位于静态存储区。这时我们声明一个位于栈区的数组,这个数组很大,大到足以污染静态存储区。

涉及内存覆盖的程序

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "stdlib.h"

//为了方便起见,STACK区(栈)我们用SRAM1来表示,HEAP区(堆)我们用SRAM2来表示,静态常量区我们用SRAM3来表示。

u8 SRAM3_Buffer[100]={0};//声明了一个初始化为0的全局数组,在静态常量区,0x2000 0000开头

int main(void)
{ 
	u8 SRAM1_i,SRAM1_j = 1,m;//i为普通的未初始化局部变量,j作为初始化的局部变量,测试可得均存在于栈区
	u8* pSRAM2 = (u8*)malloc(200);//指针pSRAM2指向堆区分配了一个u8类型200大小的数组的首地址,测试可得存在于堆区
	
	u8 SRAM1_Buffer[100] = {0};//声明了一个局部数组,存在于静态常量区。
	
	u8 SRAM2_Buffer[1500] = {0};
	
	delay_init(168);		  //初始化延时函数
	LED_Init();		        //初始化LED端口
	uart_init(115200);
  
	for(m = 0;m < 100;m++)
	{
		SRAM3_Buffer[m] = m;
	}
	
	printf("未初始化局部变量SRAM1_i在栈中的地址为:0x%x\r\n",&SRAM1_i);
	printf("初始化的局部变量SRAM1_j在栈中的地址为:0x%x\r\n",&SRAM1_j);
	printf("初始化局部数组SRAM1_Buffer在栈中的地址为:0x%x\r\n",&SRAM1_Buffer);
	printf("调用malloc声明了一个在堆区的数组首地址为:0x%x\r\n",pSRAM2);
	printf("静态常量区的数组的地址为:0x%x\r\n",SRAM3_Buffer);
	
	printf("静态常量区的数组值为:\r\n");
	for(m = 0;m < 100;m++)
	{
		printf("%d ",SRAM3_Buffer[m]);
	}
	

	while(1)
	{
		delay_ms(500);
		
		LED1 = ~LED1;
	}
}

这时我们看到位于静态存储区的数组SRAM1_Buffer里的值已经发生了变化,这是非常可怕的,栈区变量太大导致别的区域如堆区/静态存储区的变量值被影响!
stm32内存架构及堆栈管理_第8张图片
当然这是指经过编程运行后把结果显示在串口调试助手上,如果大家闲麻烦,也可以直接进入调试模式通过Memory 1显示程序运行中的数值变化:
stm32内存架构及堆栈管理_第9张图片
综上所述,我想大家已经大致理解stm32的内存架构了,有一点要说的就是堆是向上延伸,而栈是向下延伸,而且栈区的大小编译时看不出来,所以我们对于堆栈的操作一定要小心,申请的内存都要delete/free掉,不然会造成内存泄漏,养成良好的编程习惯!还有就是局部变量申请过大,导致内存覆盖的问题,也很容易造成程序的崩溃,关于stm32的内存架构就介绍到这里把。

接下来我会向大家介绍我们是如何通过程序来管理我们的内存的,这是我的第一篇博客,欢迎关注,转载标明出处即可,2020 元宵节快乐!

你可能感兴趣的:(stm32内存管理)