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 分块式内存管理

        分块式内存管理主要由内存池内存管理表两部分组成。内存池被等分为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<memtblsize[memoryx];i++) //循环范围是在整个内存块个数之间循环
	{
		if(malloc_dev.memmap[memoryx][i]) //内存管理表和内存块是一一对应的,内存块分配内存时,如果对应内存块的内存没有被占用,那么标记为0
			//只要申请开辟内存,那么首先就要获取需要开辟的内存块数m,然后从末端依次开始开辟m个连续的内存块,只要内存足够,就将这m个连续的内存块标记为m,表示已经被占用了
		//所以只要内存被占用,那么对应的内存块的数据就不为0,至于对应数据是几,那要根据所需的内存块数进行判断
		//换句话说就是,如果对应内存块被占用,那么if判断语句一定为真
		{
			used++; //定义一个变量表示使用率,单位%,只要对应内存块被占用,used++
		}
	}
	return (used*100)/(memtblsize[memoryx]); //该公式很简单,被占用的内存块数除以有多少个内存块,然后乘以100%转换成百分比
}
//内存分配(内部调用)
//memory:所属内存块
//size:要分配的内存大小(字节)
//返回值:0xFFFFFFFF,代表错误;其他,内存偏移地址
u32 my_mem_malloc(u8 memory,u32 size)
{
	signed long offset=0;
	u32 need_memory; 		//需要的内存块数
	u32 continue_memory=0;  //连续的空内存块数
	u32 i;
	if(!malloc_dev.memrdy[memory]) //!malloc_dev.memrdy[memory]为真,则表示malloc_dev.memrdy[memory]为假
		//malloc_dev.memrdy[memory]=0表示内存管理还未就绪
	{
		malloc_dev.init(memory); //内存管理还没有就绪,那么先执行初始化
	}
	if(size==0) //表示要分配的内存大小为0
	{
		return 0xFFFFFFFF; 
	}
	need_memory=size/memblksize[memory];  //得到需要分配的连续内存块数
	//size是总的要分配的内存大小;memblksize[memory]表示所属内存块的大小,相除表示需要多少个内存块才可以储存size大小的内存
	if(size%memblksize[memory]) //该if语句表示分配过后,所需内存块的个数不是整数,结合实际,不能使用半个或者几分之几个内存块
		//所以在原本需要的内存块数上++
	{
		need_memory++;
	}
	for(offset=memtblsize[memory]-1;offset>=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<need_memory;i++) //标记这些连续的所需的内存块非空
			{
				malloc_dev.memmap[memory][offset+i]=need_memory; //将这些所需的连续的内存块的数据改为need_memory
			}
			return (offset*memblksize[memory]);  //返回偏移地址
		}
	}
	return 0xFFFFFFFF; //未找到符合分配条件的内存块
}
//释放内存
//memory:所属的内存块
//offset:内存地址偏移
//返回值:0,释放成功;1,释放失败
u8 my_mem_free(u8 memory,u32 offset)
{
	int i;
	if(!malloc_dev.memrdy[memory])//整体为真,则表示malloc_dev.memrdy[memory]为假,表示内存管理未就绪
	{
		malloc_dev.init(memory); //调用结构体指针函数指向初始化函数
		return 1;//释放失败,未初始化
	}
	if(offset<memsize[memory]) //偏移在内存池内
	{
		int index=offset/memblksize[memory];	 //偏移所在内存块号,也就是得到从末端开始偏移,偏移到第几块内存块
		int nmemb=malloc_dev.memmap[memory][index]; //定义变量记录内存块的数量
		for(i=0;i<nmemb;i++)  //内存块清零
		{
			malloc_dev.memmap[memory][index+i]=0; 
		}
		return 0; //内存释放成功
	}
	else
		return 2; //偏移超区了
}
//释放内存(外部调用)
//memory:所属内存块
//ptr:内存首地址
void myfree(u8 memory,void *ptr)
{
	u32 offset;
	if(ptr==NULL)
		return;  //首元素地址指向空指针,地址为空
	offset=(u32)ptr-(u32)malloc_dev.membase[memory]; //记录偏移量
	my_mem_free(memory,offset); //释放内存
}
//分配内存(外部调用)
//memory:s所属内存块
//size:内存大小(字节)
//返回值:分配到的内存首地址
void *mymalloc(u8 memory,u32 size)
{
	u32 offset;
	offset=my_mem_malloc(memory,size); //offset接收到动态开辟内存以后,返回的地址偏移量,得到偏移量就表示得到了动态开辟内存的大小
	if(offset==0xFFFFFFFF)
		return NULL;
	else
		return (void*)((u32)malloc_dev.membase[memory]+offset);	//该语句是C语言malloc动态开辟空间内存的表示
}
//重新分配内存(外部调用)
//memory:所属内存块
//*ptr:旧内存首地址
//size:要分配的内存大小(字节)
//返回值:新分配的内存首地址
void *myrealloc(u8 memory,void *ptr,u32 size)
{
	u32 offset;    
    offset=my_mem_malloc(memory,size);   	
    if(offset==0XFFFFFFFF)return NULL;     
    else  
    {  									   
	    mymemcpy((void*)((u32)malloc_dev.membase[memory]+offset),ptr,size);	//拷贝旧内存内容到新内存   
        myfree(memory,ptr);  											  		//释放旧内存
        return (void*)((u32)malloc_dev.membase[memory]+offset);  				//返回新内存首地址
    }  
}



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


  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值