目录
本节我们要来学习的是内存管理实验,主要用来解决的问题其实和C语言中数组遇到的问题类似,就是我们不知道到底需要多少内存,为了避免数组越界,我们只能定义一个很大的数组,但是在单片机这种“寸土寸金”的地方就很不现实了,所以我们必须引入C语言中的内存管理函数malloc和free了。
我们采用的原理是分块式内存管理,主要就是通过内存管理表来得知哪些内存是空的,我们可以去占用,哪些内存已经用完了,需要被释放。具体内容如下所示:
从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为 n 块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。 内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某个指针。 内存分配方向如图所示,是从顶到底的分配方向。即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
当指针 p 调用 malloc 申请内存的时候,先判断 p 要分配的内存块数(m),然后从第 n 项开始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0),然后将这 m 个内存管理表项的值都设置为 m(标记被占用),最后,把最后的这个空内存块的地址返回指针 p,完成一次分配。注意,如果当内存不够的时候(找到最后也没找到连续的 m 块空闲内存),则返回 NULL 给 p,表示分配失败。
当 p 申请的内存用完,需要释放的时候,调用 free 函数实现。free 函数先判断 p 指向的内存地址所对应的内存块,然后找到对应的内存管理表项目,得到 p 所占用的内存块数目 m(内存管理表项目的值就是所分配内存块的数目),将这 m 个内存管理表项目的值都清零,标记释放,完成一次内存释放。
//内存管理控制器
struct _m_mallco_dev
{
void (*init)(u8); //初始化
u8 (*perused)(u8); //内存使用率
u8 *membase[SRAMBANK]; //SRAMBANK定义管理的内存片数
//内存池 管理SRAMBANK个区域的内存,
u16 *memmap[SRAMBANK]; //内存管理状态表
u8 memrdy[SRAMBANK]; //内存管理是否就绪
};
extern struct _m_mallco_dev mallco_dev;//在mallco.c里面定义
#define SRAMIN 0 //内部内存池
#define SRAMEX 1 //外部内存池
#define SRAMCCM 2 //CCM内存池(此部分SRAM仅仅CPU可以访问!!!)
#define SRAMBANK 3 //定义支持的SRAM块数.
//mem1内存参数设定.mem1完全处于内部SRAM里面.
#define MEM1_BLOCK_SIZE 32 //内存块大小为32字节
#define MEM1_MAX_SIZE 100*1024 //最大管理内存 100K
#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE //内存表大小
//mem2内存参数设定.mem2的内存池处于外部SRAM里面
#define MEM2_BLOCK_SIZE 32 //内存块大小为32字节
#define MEM2_MAX_SIZE 960 *1024 //最大管理内存960K
#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE //内存表大小
//mem3内存参数设定.mem3处于CCM,用于管理CCM(特别注意,这部分SRAM,仅CPU可以访问!!)
#define MEM3_BLOCK_SIZE 32 //内存块大小为32字节
#define MEM3_MAX_SIZE 60 *1024 //最大管理内存60K
#define MEM3_ALLOC_TABLE_SIZE MEM3_MAX_SIZE/MEM3_BLOCK_SIZE //内存表大小
探索者开发板外扩了SRAM,同时片内有SRAM和CCM之分,所以有3片内存区域,MEM1表示内部SRAM内存池(128K字节),MEM2表示外扩内存池(1024K字节)。MEM3表示内部CCM内存池(64K字节)。
//内存池(32字节对齐),__align(32)是32位对齐的意思
__align(32) u8 mem1base[MEM1_MAX_SIZE];
//内部SRAM内存池,__attribute__,是指定内存地址的意思
__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000)));
//外部SRAM内存池
__align(32) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0X10000000)));
//内部CCM内存池
//内存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];
//内部SRAM内存池MAP
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+MEM2_MAX_SIZE))); //外部SRAM内存池MAP
u16 mem3mapbase[MEM3_ALLOC_TABLE_SIZE] __attribute__((at(0X10000000+MEM3_MAX_SIZE))); //内部CCM内存池MAP
//内存管理参数
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};
//内存总大小
// main.c
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
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
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,"KEY0:Malloc KEY2:Free");
LCD_ShowString(30,110,200,16,16,"KEY_UP:SRAMx KEY1:Read");
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(30,130,200,16,16,"SRAMIN");
LCD_ShowString(30,150,200,16,16,"SRAMIN USED: %");
LCD_ShowString(30,170,200,16,16,"SRAMEX USED: %");
LCD_ShowString(30,190,200,16,16,"SRAMCCM USED: %");
while(1)
{
key=KEY_Scan(0);//不支持连按
switch(key)
{
case 0://没有按键按下
break;
case KEY0_PRES: //KEY0按下
p=mymalloc(sramx,2048);//申请2K字节
if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);//向p写入一些内容
break;
case KEY1_PRES: //KEY1按下
if(p!=NULL)
{
sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容
LCD_ShowString(30,270,200,16,16,p); //显示P的内容
}
break;
case KEY2_PRES: //KEY2按下
myfree(sramx,p);//释放内存
p=0; //指向空地址
break;
case WKUP_PRES: //KEY UP按下
sramx++;
if(sramx>2)sramx=0;
if(sramx==0)LCD_ShowString(30,170,200,16,16,"SRAMIN ");
else if(sramx==1)LCD_ShowString(30,170,200,16,16,"SRAMEX ");
else LCD_ShowString(30,170,200,16,16,"SRAMCCM");
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,150,my_mem_perused(SRAMIN),3,16);//显示内部内存使用率
LCD_ShowNum(30+104,170,my_mem_perused(SRAMEX),3,16);//显示外部内存使用率
LCD_ShowNum(30+104,190,my_mem_perused(SRAMCCM),3,16);//显示CCM内存使用率
LED0=!LED0;
}
}
}
如果对一个指针进行多次内存申请,而之前的申请又没释放,那么将造成“内存泄露”,这是内存管理所不希望发生的,久而久之,可能导致无内存可用的情况!所以,在使用的时候,一定记得,申请的内存在用完以后,一定要释放。
内存管理在单片机里面属于十分重要的内容,在面对内存空间不足的时候更是如此,所以对这部分内容还是得多加使用。