STM32F4_内存管理(Malloc、Free)

目录

前言

1. 内存管理介绍

1.1 分块式内存管理

2. 实验程序

2.1 main.c

2.2 Malloc.c

2.3 Malloc.h


前言

        相信大家在学习C语言的过程中,都会学习到 malloc 动态开辟函数free 释放内存函数;这两个函数带给我们的优越性是:

        我们在使用一块内存的时候,通常都不会恰到好处的使用完定义的内存,可能我们定义的内存不够我们使用,也可能我们定义的内存会多,这样会造成内存浪费;所以在此基础之上引入malloc动态开辟函数,可以在定义一块内存的基础之上,随着我们的需要进行动态内存开辟;简单来说就是,定义的内存不够使用,就随着我们的需求一次一次的动态开辟内存;如果动态开辟的内存多余,就是要 free 函数释放掉动态开辟的空间;当然,当程序运行完以后,动态开辟的内存会自动的释放掉!

        STM32扩展的内存也存在这种情况;如果我们每次使用扩展内存的时候,都定义一个峰值的存储空间,那么难免造成内存空间的浪费,所以本节我们学习基于STM32的 malloc 动态内存开辟!

如果对malloc动态开辟函数和 free 空间释放函数不够了解,可参照以下笔记进行学习!!!

C语言_动态内存管理_light_2025的博客-CSDN博客

1. 内存管理介绍

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

1.1 分块式内存管理

STM32F4_内存管理(Malloc、Free)_第1张图片

        分块式内存管理主要由内存池内存管理表两部分组成。内存池被等分为n块,对应的内存管理表大小也为n,内存管理表的每一项对应内存池的一块内存。

内存管理表的项数表示的意义是:

        如果项数为0,那么相对应的内存块数量也为0,也就表示此时没有内存块被占用;如果项数非零,那么对应的内存块数也非零,此时项数为几对应的内存块数也为几,代表对应的内存块已经被占用,其数值代表被连续占用的内存块数。比方说:如果项数为10,那么算上这个项数,就表示分配了10个内存块给外部的指针。

内存分配方向:

        内存分配是从顶到底的分配方向。也就是首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。

分配原理:

        当指针p调用malloc函数动态申请内存的时候,先去判断指针p要分配的内存块数m,然后从末端开始查找,直到找到m块连续的空内存块,然后把这m块空的内存块的内存管理项设置为m,表示对应块的内存被占用,最后将这m块对应的内存块地址返回给指针p,完成一次分配。

        :如果从末端开始查找,查找结束还是没有找到对应的连续空内存块,就表示内存不足,此时将NULL返回给指针p,表示分配失败。

释放原理:

        当指针p申请的动态内存用完,需要释放的时候,调用free函数实现。free函数先找到内存地址所对应的内存块,然后找到与内存块所对应的内存管理表项目,得到指针p所占用的内存块数目m。然后将这m个内存管理表的项目都清零,标记完成一次内存释放。

2. 实验程序

实验功能:

        开机后,显示提示信息、KEY0用于申请内存,每次申请2K字节内存。KEY1用于写数据到申请的内存里面。KEY2用于释放内存。KEY_UP用于切换操作内存区(内部SRAM内存/外部SRAM内存/内部CCM内存)

2.1 main.c

#include "stm32f4xx.h"                 
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "Key.h"
#include "usmart.h"
#include "SRAM.h"
#include "Malloc.h"

 
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
	LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
	led_set(sta);
}
 
int main(void)
{        
	u8 key;		 
 	u8 i=0;	    
	u8 *p=0;
	u8 *tp=0;
	u8 paddr[18];				//存放P Addr:+p地址的ASCII值
	u8 sramx=0;					//默认为内部SRAM

	delay_init(168);  //初始化延时函数
	uart_init(115200);		//初始化串口波特率为115200
	
	LED_Init();					//初始化LED 
 	LCD_Init();					//LCD初始化  
 	Key_Init();					//按键初始化 
 	FSMC_SRAM_Init();			//初始化外部SRAM  
	
	my_mem_init(SRAMIN);		//初始化内部内存池
	my_mem_init(SRAMEX);		//初始化外部内存池
	my_mem_init(SRAMCCM);		//初始化CCM内存池
	
 	POINT_COLOR=RED;//设置字体为红色 
	LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");	
	LCD_ShowString(30,70,200,16,16,"MALLOC TEST");	
	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,110,200,16,16,"2023/08/02");   
	LCD_ShowString(30,130,200,16,16,"KEY0:Malloc  KEY2:Free");
	LCD_ShowString(30,150,200,16,16,"KEY_UP:SRAMx KEY1:Read"); 
 	POINT_COLOR=BLUE;//设置字体为蓝色 
	LCD_ShowString(30,170,200,16,16,"SRAMIN");
	LCD_ShowString(30,190,200,16,16,"SRAMIN  USED:   %");
	LCD_ShowString(30,210,200,16,16,"SRAMEX  USED:   %");
	LCD_ShowString(30,230,200,16,16,"SRAMCCM USED:   %");
 	while(1)
	{	
		key=KEY_Scan(0);//不支持连按	
		switch(key)
		{
			case 0://没有按键按下	
				break;
			case KEY0_PRESS:	//KEY0按下
				p=mymalloc(sramx,2048);//申请2K字节
				if(p!=NULL)
					sprintf((char*)p,"Memory Malloc Test%03d",i);//向p写入一些内容
				break;
			case KEY1_PRESS:	//KEY1按下	   
				if(p!=NULL)
				{
					sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容 	 
					LCD_ShowString(30,270,200,16,16,p);			 //显示P的内容
				}
				break;
			case KEY2_PRESS:	//KEY2按下	  
				myfree(sramx,p);//释放内存
				p=0;			//指向空地址
				break;
			case KEY_UP_PRESS:	//KEY UP按下 
				sramx++; 
				if(sramx>2) //因为只有三个存储区
					sramx=0;
				if(sramx==0)
					LCD_ShowString(30,170,200,16,16,"SRAMIN "); //内部SRAM存储区
				else if(sramx==1)
					LCD_ShowString(30,170,200,16,16,"SRAMEX "); //外部扩展存储区
				else 
					LCD_ShowString(30,170,200,16,16,"SRAMCCM"); //内部CCM存储区
				break;
		}
		if(tp!=p)
		{
			tp=p;
			sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp);
			LCD_ShowString(30,250,200,16,16,paddr);	//显示p的地址
			if(p)
				LCD_ShowString(30,270,200,16,16,p);//显示P的内容
		    else 
				LCD_Fill(30,270,239,266,WHITE);	//p=0,清除显示
		}
		delay_ms(10);   
		i++;
		if((i%20)==0)//DS0闪烁.
		{
			LCD_ShowNum(30+104,190,my_mem_perused(SRAMIN),3,16);//显示内部内存使用率
			LCD_ShowNum(30+104,210,my_mem_perused(SRAMEX),3,16);//显示外部内存使用率
			LCD_ShowNum(30+104,230,my_mem_perused(SRAMCCM),3,16);//显示CCM内存使用率
 			LED0=!LED0;
 		}
	}	   
}

2.2 Malloc.c

#include "stm32f4xx.h"            
#include "Malloc.h"


//内存池(32字节对齐)
__align(32) u8 mem1base[MEM1_MAX_SIZE];  //内部SRAM内存池
__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0x68000000)));  //外部SRAM内存池
__align(32) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0x10000000)));  //内部CCM内存池
//注:
//	__align(32)是负责内存对齐的宏,它会补充一些数据以便下面的数据对齐;简单来说就是让接下来的数据或者指令32位对齐
//  __attribute__((at(0x68000000)))表示定义的数据的起始地址是从0x68000000开始的
//	u8 mem1base[MEM1_MAX_SIZE];表示拿出内部内存池的32K的空间来做实验。之所以定义u8类型,是因为计算机内存都是以字节为单位的存储空间
//	内存中的每个字节都有唯一的编号,这个编号叫做地址。
//	不管存储什么数据类型的地址编号都是32位的,1个字节是8位,那么每个地址编号可以容纳四个字节,这也是为什么定义变量之前一定要先声明数据类型的原因,一个字符char占1个字节,一个整型int占4个字节
//	__align(32)让接下来的数据和指令32位对齐,也就是让对应的地址编号和指针32位对齐

//内存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];  //内部SRAM内存管理表  实际上就是u16 mem1mapbase[3200],意思是定义一个有3200个内存块的数组
//	数组名mem1mapbase就是mem1mapbase[0] 该数组的第一个元素代表3200个内存块中的第一个内存块
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+MEM2_MAX_SIZE)));  //外部SRAM内存池
u16 mem3mapbase[MEM3_ALLOC_TABLE_SIZE] __attribute__((at(0X10000000+MEM3_MAX_SIZE)));  //内部CCM内存池
//注:之所以是__attribute__((at(0X68000000+MEM2_MAX_SIZE)));是因为分块式内存管理下,外部SRAM区动态开辟时,是从末端依次向里的,所以起始地址应该是0x68000000加上最大的外部管理内存MEM2_MAX_SIZE

//内存管理参数
const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE,MEM3_ALLOC_TABLE_SIZE}; //内存表大小
const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE,MEM3_BLOCK_SIZE}; //内存分块大小
const u32 memsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE,MEM3_MAX_SIZE};   //内存总大小


//内存管理控制器   初始化结构体成员
struct _m_malloc_dev malloc_dev=
{
	my_mem_init,  //内存初始化
	my_mem_perused, //内存使用率
	mem1base,mem2base,mem3base,  //内存池  mem1base有100K元素,mem2base有960K元素,mem3base有60K元素
	mem1mapbase,mem2mapbase,mem3mapbase,  //内存管理状态表
	0,0,0,        //内存管理未就绪
};

//复制内存
//*destinct:目的地址
//*source:源地址
//n:需要复制的内存长度(单位字节)
void mymemcpy(void *destinct,void *source,u32 n) //该函数表示从source处复制n个字节长度的数据到destinct
{
	u8 *xdestinct=destinct; //定义一个指针xdestinct指向目标地址的首元素
	u8 *xsource=source;   //定义一个指针xsource指向源地址的首元素
	while(n--) //通过while循环一个字节一个字节的复制
	{
		*xdestinct++=*xsource++; //字符串拷贝函数的逻辑,
	}
}
//设置内存
//*S:内存首地址
//C:要设置的值
//Count:需要设置的内存大小(字节为单位)
void mymemset(void *S,u8 C,u8 Count)
{
	u8 *xs=S;  //定义一个新指针*xs指向内存首地址对应的元素
	while(Count--) //每设置一个,需要设置的内存大小就减少一点
		*xs++=C;  //把u8类型数据C填充到以指针变量xs为首地址的内存空间中,填充个数由Count值决定
}
//内存管理初始化
//memoryx:所属的内存块
void my_mem_init(u8 memoryx)
{
	mymemset(malloc_dev.memmap[memoryx],0,memtblsize[memoryx]*2); //内存状态表数据清零
	//第一个参数是内存首地址,malloc_dev.memmap[memoryx]调用结构体表示拿到了内存池中第一个内存块的地址,以此地址作为存储内存的首地址
	//初始化设置状态表数据为0
	//清零自然是整个内存表的数据全部清零,所以设置的大小就是内存块对应的整个内存表的大小
	//之所以乘以2是因为:调用的mymemset函数是将u8类型的数据C填充到对应内存块的首地址,而设置的*memmap[SRAMBANK]是u16位的
	//所以需要乘以2保证将其全部清零
	mymemset(malloc_dev.membase[memoryx],0,memsize[memoryx]);  //内存池数据清零
	//对应内存池设置的结构体u8 *membase[SRAMBANK];所以不需要乘以2就可以实现内存池数据清零
	malloc_dev.memrdy[memoryx]=1;  //内存管理初始化OK,0表示内存管理初始化未就绪
}
//获取内存使用率
//memoryx:所属的内存块  也可以说是我们想要获得哪个内存块的使用率
//返回值:使用率(0~100)
u8 my_mem_perused(u8 memoryx)
{
	u32 used=0;
	u32 i;
	for(i=0;i=0;offset--)  //搜索整个内存控制区,寻找有无连续个need_memory个内存块
	//offset-- 表示从内存管理表的末端开始搜索,依次向前搜索need_memory个连续的内存块
	{
		if(!malloc_dev.memmap[memory][offset]) //整体!malloc_dev.memmap[memory][offset]取反为真,那么malloc_dev.memmap[memory][offset]为假
			//malloc_dev.memmap[memory][offset]=0;表示对应的内存块的数值为0,对应内存块为空,也就表示该内存块可以被使用
		{
			continue_memory++; //连续空内存块数增加
		}
		else
			continue_memory=0; //只要找到一个内存块不为空,那么就表示所需的内存块不连续了,continue_memory=0;
		if(continue_memory==need_memory) //搜索找到的连续的内存块数恰好等于所需的内存块数
		{
			for(i=0;i

2.3 Malloc.h

#ifndef _Malloc__H_
#define _Malloc__H_
#include "stm32f4xx.h" 

/
#ifndef NULL

#define NULL 0   //宏定义空指针返回0,C语言标准规定当内存不足时,或者动态开辟内存失败时,返回空指针

#endif
/

//定义三个内存池
#define SRAMIN 0  //STM32内部的SRAM内存池 192K
#define SRAMEX 1  //STM32外部的SRAM扩展内存池 1M
#define SRAMCCM 2  //CCM内存池(此部分SRAM仅仅CPU可以访问!!!)

#define SRAMBANK 3  //定义支持的SRAM块数  这里定义支持的块数是3,包括内部SRAM、外部SRAM、CCM内存池

//Memory1内存参数设定,Memory1完全处于内部SRAM里面 192K
#define MEM1_BLOCK_SIZE 32    //内存块大小为32个字节,也就是说我动态开辟了1个内存块,就相当于开辟了一个32字节大小的空间
#define MEM1_MAX_SIZE 100*1024  //最大管理内存 100K  1K=1024个字节  相当于我设定的内存峰值是100K
#define MEM1_ALLOC_TABLE_SIZE  MEM1_MAX_SIZE/MEM1_BLOCK_SIZE  //内存管理表大小 相当于设置了3200个内存块  也就是说将内部SRAM内存池划分为3200个内存块

//Memory2内存参数设定,Memory2的内存池处于外部SRAM里面 1M
#define MEM2_BLOCK_SIZE 32   //内存块大小32个字节
#define MEM2_MAX_SIZE 960*1024  //外部SRAM最大管理内存960K
#define MEM2_ALLOC_TABLE_SIZE  MEM2_MAX_SIZE/MEM2_BLOCK_SIZE  //内存管理表大小 相当于设置了30720个内存块

//Memory3内存参数设定,Memory3处于CCM,用于管理CCM(特别注意:这部分的SRAM,仅CPU可以访问!!!)
#define MEM3_BLOCK_SIZE 32   //内存块大小为32个字节
#define MEM3_MAX_SIZE 60*1024  //内部CCM最大管理内存60K
#define MEM3_ALLOC_TABLE_SIZE  MEM3_MAX_SIZE/MEM3_BLOCK_SIZE  //内存管理表大小 相当于设置了1920个内存块


//内存管理控制器
struct _m_malloc_dev
{
	void (*init)(u8);   //定义一个init指针,该指针指向初始化函数,初始化函数有一个unsigned char型参数
	//void (*init)(u8); 定义了一个指向函数的指针变量,该指针变量名是init;void表示该函数没有返回值,u8是函数的形参
	//数据类型+(*变量名)(形参);
	u8 (*perused)(u8);  //定义一个perused指针,该指针指向一个内存使用率的函数,该函数有一个参数
	u8 *membase[SRAMBANK];  //定义一个membase指针指向内存池,管理三个(SRAMBANK)区域的内存
	u16 *memmap[SRAMBANK];  //定义一个memmap指针指向内存管理状态表,该数组成员表示三个SRAM块
//	u16 *memmap[3]={mem1mapbase,mem2mapbase,mem3mapbase};其中mem2mapbase就表示外部扩展的SRAM的第一个内存块的地址
//	也就是说该数组中对应的三个成员分别代表内部SRAM、外部扩展的SRAM、CCM的第一个内存块的地址
	u8 memrdy[SRAMBANK];   //内存管理是否就绪
};
extern struct _m_malloc_dev malloc_dev; //定义一个结构体变量,该变量在malloc.c里面定义




void mymemcpy(void *destinct,void *source,u32 n);
void mymemset(void *S,u8 C,u8 Count);
void my_mem_init(u8 memoryx);
u8 my_mem_perused(u8 memoryx);
u32 my_mem_malloc(u8 memory,u32 size);
u8 my_mem_free(u8 memory,u32 offset);
void myfree(u8 memory,void *ptr);
void *mymalloc(u8 memory,u32 size);
void *myrealloc(u8 memory,void *ptr,u32 size);



#endif


你可能感兴趣的:(STM32,stm32,单片机,嵌入式硬件)