第四十二章 内存管理实验
1.硬件平台:正点原子探索者STM32F407开发板 2.软件平台:MDK5.1 3.固件库版本:V1.4.0
上一章,我们学会了使用STM32F4驱动外部SRAM,以扩展STM32F4的内存,加上STM32F4本身自带的192K字节内存,我们可供使用的内存还是比较多的。如果我们所用的内存都像上一节的testsram那样,定义一个数组来使用,显然不是一个好办法。
本章,我们将学习内存管理,实现对内存的动态管理。本章分为如下几个部分:
42.1 内存管理简介
42.2 硬件设计
42.3 软件设计
42.4 下载验证
42.1 内存管理简介
内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,他们其实最终都是要实现2个函数:malloc和free;malloc函数用于内存申请,free函数用于内存释放。
本章,我们介绍一种比较简单的办法来实现:分块式内存管理。下面我们介绍一下该方法的实现原理,如图42.1.1所示:
图42.1.1 分块式内存管理原理
从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为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个内存管理表项目的值都清零,标记释放,完成一次内存释放。
关于分块式内存管理的原理,我们就介绍到这里。
42.2 硬件设计
本章实验功能简介:开机后,显示提示信息,等待外部输入。KEY0用于申请内存,每次申请2K字节内存。KEY1用于写数据到申请到的内存里面。KEY2用于释放内存。KEY_UP用于切换操作内存区(内部SRAM内存/外部SRAM内存/内部CCM内存)。DS0用于指示程序运行状态。本章我们还可以通过USMART调试,测试内存管理函数。
本实验用到的硬件资源有:
1) 指示灯DS0
2) 四个按键
3) 串口
4) TFTLCD模块
5) IS62WV51216
这些我们都已经介绍过,接下来我们开始软件设计。
42.3 软件设计
打开本章实验工程可以看到,我们新增了MALLOC分组,同时在分组中新建了文件malloc.c以及头文件malloc.h。 内存管理相关的函数和定义主要是在这两个文件中。
打开malloc.c文件,代码如下:
//内存池(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内存池
//内存管理表
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}; //内存总大小
//内存管理控制器
struct _m_mallco_dev mallco_dev=
{
my_mem_init, //内存初始化
my_mem_perused, //内存使用率
mem1base,mem2base,mem3base, //内存池
mem1mapbase,mem2mapbase,mem3mapbase,//内存管理状态表
0,0,0, //内存管理未就绪
};
//复制内存
//*des:目的地址
//*src:源地址
//n:需要复制的内存长度(字节为单位)
void mymemcpy(void *des,void *src,u32 n)
{
u8
*xdes=des;
u8
*xsrc=src;
while(n--)*xdes++=*xsrc++;
}
//设置内存
//*s:内存首地址
//c :要设置的值
//count:需要设置的内存大小(字节为单位)
void mymemset(void *s,u8 c,u32 count)
{
u8 *xs =
s;
while(count--)*xs++=c;
}
//内存管理初始化
//memx:所属内存块
void my_mem_init(u8 memx)
{
mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);//内存状态表数据清零
mymemset(mallco_dev.membase[memx],
0,memsize[memx]); //内存池所有数据清零
mallco_dev.memrdy[memx]=1; //内存管理初始化OK
}
//获取内存使用率
//memx:所属内存块
//返回值:使用率(0~100)
u8 my_mem_perused(u8 memx)
{
u32
used=0;u32 i;
for(i=0;i<memtblsize[memx];i++) {
if(mallco_dev.memmap[memx])used++; }
return
(used*100)/(memtblsize[memx]);
}
//内存分配(内部调用)
//memx:所属内存块
//size:要分配的内存大小(字节)
//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址
u32 my_mem_malloc(u8 memx,u32 size)
{
signed
long offset=0;
u32
nmemb; //需要的内存块数
u32
cmemb=0;//连续空内存块数
u32
i;
if(!mallco_dev.memrdy[memx])mallco_dev.init(memx);//未初始化,先执行初始化
if(size==0)return 0XFFFFFFFF; //不需要分配
nmemb=size/memblksize[memx]; //获取需要分配的连续内存块数
if(size%memblksize[memx])nmemb++;
for(offset=memtblsize[memx]-1;offset>=0;offset--) //搜索整个内存控制区
{
if(!mallco_dev.memmap[memx][offset])cmemb++;//连续空内存块数增加
else
cmemb=0; //连续内存块清零
if(cmemb==nmemb) //找到了连续nmemb个空内存块
{
for(i=0;i<nmemb;i++) //标注内存块非空
{
mallco_dev.memmap[memx][offset+i]=nmemb;
}
return (offset*memblksize[memx]);//返回偏移地址
}
}
return
0XFFFFFFFF;//未找到符合分配条件的内存块
}
//释放内存(内部调用)
//memx:所属内存块
//offset:内存地址偏移
//返回值:0,释放成功;1,释放失败;
u8 my_mem_free(u8 memx,u32 offset)
{
int
i;
if(!mallco_dev.memrdy[memx])//未初始化,先执行初始化
{
mallco_dev.init(memx); //初始化内存池
return 1; //未初始化
}
if(offset<memsize[memx])//偏移在内存池内.
{
int
index=offset/memblksize[memx]; //偏移所在内存块号码
int
nmemb=mallco_dev.memmap[memx][index]; //内存块数量
for(i=0;i<nmemb;i++) mallco_dev.memmap[memx][index+i]=0;//内存块清零
return 0;
}else
return 2;//偏移超区了.
}
//释放内存(外部调用)
//memx:所属内存块
//ptr:内存首地址
void myfree(u8 memx,void *ptr)
{
u32
offset;
if(ptr==NULL)return;//地址为0.
offset=(u32)ptr-(u32)mallco_dev.membase[memx];
my_mem_free(memx,offset); //释放内存
}
//分配内存(外部调用)
//memx:所属内存块
//size:内存大小(字节)
//返回值:分配到的内存首地址.
void *mymalloc(u8 memx,u32 size)
{
u32
offset;
offset=my_mem_malloc(memx,size);
if(offset==0XFFFFFFFF)return NULL;
else
return (void*)((u32)mallco_dev.membase[memx]+offset);
}
//重新分配内存(外部调用)
//memx:所属内存块
//*ptr:旧内存首地址
//size:要分配的内存大小(字节)
//返回值:新分配到的内存首地址.
void *myrealloc(u8 memx,void *ptr,u32 size)
{
u32
offset;
offset=my_mem_malloc(memx,size);
if(offset==0XFFFFFFFF)return NULL;
else
{
mymemcpy((void*)((u32)mallco_dev.membase[memx]+offset),ptr,size);
//拷贝旧内存内容到新内存
myfree(memx,ptr); //释放旧内存
return (void*)((u32)mallco_dev.membase[memx]+offset); //返回新内存首地址
}
}
这里,我们通过内存管理控制器mallco_dev结构体(mallco_dev结构体见malloc.h),实现对三个内存池的管理控制。为甚
首先,是内部SRAM内存池,定义为:
__align(32) u8 mem1base[MEM1_MAX_SIZE];
然后,是外部SRAM内存池,定义为:
__align(32) u8 mem2base[MEM2_MAX_SIZE]
__attribute__((at(0X68000000)));
最后,是内部CCM内存池,定义为:
__align(32) u8 mem3base[MEM3_MAX_SIZE]
__attribute__((at(0X10000000)));
这里之所以要定义成3个,是因为这三个内存区域的地址都不一样,STM32F4内部内存分为两大块:1,普通内存(又分为主要内存和辅助内存,地址从:0X2000 0000开始,共128KB),这部分内存任何外设都可以访问。2,CCM内存(地址从:0X1000 0000开始,共64KB),这部分内存仅CPU可以访问,DMA之类的不可以直接访问,使用时得特别注意!!
而外部SRAM,地址是从0X6800 0000开始的,共1024KB。所以,这样总共有3部分内存,而内存池必须是连续的内存空间,才可以,这样3个内存区域,就有3个内存池,因此,分成了3块来管理。
其中,MEM1_MAX_SIZE、MEM2_MAX_SIZE和MEM3_MAX_SIZE为在malloc.h里面定义的内存池大小,外部SRAM内存池指定地址为0X6800 0000,也就是从外部SRAM的首地址开始的,CCM内存池从0X1000 0000开始,同样是从CCM内存的首地址开始的。但是,内部SRAM内存池的首地址则由编译器自动分配。__align(32)定义内存池为32字节对齐,以适应各种不同场合的需求。
此部分代码的核心函数为:my_mem_malloc和my_mem_free,分别用于内存申请和内存释放。思路就是我们在42.1接所介绍的那样分配和释放内存,不过这两个函数只是内部调用,外部调用我们使用的是mymalloc和myfree两个函数。其他函数我们就不多介绍了,保存malloc.c,然后,打开malloc.h,代码如下:
#ifndef __MALLOC_H
#define __MALLOC_H
#include "stm32f4xx.h"#ifndef NULL
#define NULL 0
#endif
//定义三个内存池
#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
//内存表大小
//内存管理控制器
struct _m_mallco_dev
{
void
(*init)(u8); //初始化
u8
(*perused)(u8); //内存使用率
u8 *membase[SRAMBANK]; //内存池 管理SRAMBANK个区域的内存
u16
*memmap[SRAMBANK]; //内存管理状态表
u8 memrdy[SRAMBANK]; //内存管理是否就绪
};
extern struct _m_mallco_dev mallco_dev; //在mallco.c里面定义
void mymemset(void *s,u8 c,u32 count); //设置内存
void mymemcpy(void *des,void *src,u32 n); //复制内存
void my_mem_init(u8 memx); //内存管理初始化函数(外/内部调用)
u32 my_mem_malloc(u8 memx,u32 size); //内存分配(内部调用)
u8 my_mem_free(u8 memx,u32 offset); //内存释放(内部调用)
u8 my_mem_perused(u8 memx); //获得内存使用率(外/内部调用)
////////////////////////////////////////////////////////////////////////////////
//用户调用函数
void myfree(u8 memx,void *ptr); //内存释放(外部调用)
void *mymalloc(u8 memx,u32 size); //内存分配(外部调用)
void *myrealloc(u8 memx,void *ptr,u32 size);//重新分配内存(外部调用)
#endif
这部分代码,定义了很多关键数据,比如内存块大小的定义:MEM1_BLOCK_SIZE、MEM2_BLOCK_SIZE和MEM3_BLOCK_SIZE,都是32字节。内存池总大小,内部SRAM内存池大小为100K,外部SRAM内存池大小为960K,内部CCM内存池大小为60K。
MEM1_ALLOC_TABLE_SIZE、MEM2_ALLOC_TABLE_SIZE和MEM3_ALLOC_TABLE_
SIZE,则分别代表内存池1、2和3的内存管理表大小。
从这里可以看出,如果内存分块越小,那么内存管理表就越大,当分块为2字节1个块的时候,内存管理表就和内存池一样大了(管理表的每项都是u16类型)。显然是不合适的,我们这里取32字节,比例为1:16,内存管理表相对就比较小了。
其他就不多说了,大家自行看代码理解就好。接下来我们看看主函数代码:
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,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2014/5/15");
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_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,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);//使用率
LED0=!LED0;
}
}
}
该部分代码比较简单,主要是对mymalloc和myfree的应用。不过这里提醒大家,如果对一个指针进行多次内存申请,而之前的申请又没释放,那么将造成“内存泄露”,这是内存管理所不希望发生的,久而久之,可能导致无内存可用的情况!所以,在使用的时候,请大家一定记得,申请的内存在用完以后,一定要释放。
42.4 下载验证
在代码编译成功之后,我们通过下载代码到ALIENTEK探索者STM32F4开发板上,得到如图42.4.1所示界面:
图42.4.1 程序运行效果图
可以看到,所有内存的使用率均为0%,说明还没有任何内存被使用,此时我们按下KEY0,就可以看到内部SRAM内存被使用2%了,同时看到下面提示了指针p所指向的地址(其实就是被分配到的内存地址)和内容。多按几次KEY0,可以看到内存使用率持续上升(注意对比p的值,可以发现是递减的,说明是从顶部开始分配内存!),此时如果按下KEY2,可以发现内存使用率降低了2%,但是再按KEY2将不再降低,说明“内存泄露”了。这就是前面提到的对一个指针多次申请内存,而之前申请的内存又没释放,导致的“内存泄露”。
按KEY_UP按键,可以切换当前操作内存(内部SRAM内存/外部SRAM内存/内部CCM内存),KEY1键用于更新p的内容,更新后的内容将重新显示在LCD模块上面。
正点原子探索者STM32F407开发板购买地址:http://item.taobao.com/item.htm?id=41855882779
|