走到今天,已经开始涉及到计算机核心一点的东西了---内存管理。经过本实验的学习,可以较为深入体会到“指针是c语言的灵魂”这句话的份量。天然对c语言的能力要求就高不少了。c++
最近有点乱,可是有关嵌入式系统的学习未曾怠慢过。本文是基于原子老师的c源码,本身的学习的心得,只是对源码做出本身的学习理解,同时也插补了一些涉及到的c语言知识。贴出本文不为别的,一来希望能有有缘人看到本文,提出指正;二来,为了那些不眠的夜,安慰一下本身。数组
1, 内存管理简介
内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,而且在适当的时候释放和回收内存资源。内存管理的实现方法有不少种,他们其实最终都是要实现2个函数:malloc和free;malloc函数用于内存申请,free函数用于内存释放。
先回顾一下c语言知识:计算机内存通常分为静态存储区用以存储全局变量或常量和动态存储区用以存储函数内部变量或形参或函数运算结果。malloc()函数的做用是请求系统在内存的动态存储区分配若干个字节的存储空间,函数的返回值是首字节地址,可见malloc()函数是指针类型。free(P)的做用是释放指针变量P所指向的动态空间。
本章,咱们介绍一种比较简单的办法来实现:分块式内存管理。下面咱们介绍一下该方法的实现原理,如图所示(示意图):
内存块1 内存块2 内存块3 ……内存块n 内存池
| | | |
第1项 第2项 第3项 ……第n项 内存管理表
<<-----分配方向
|
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个内存管理表项目的值都清零,标记释放,完成一次内存释放。
关于分块式内存管理的原理,咱们就介绍到这里。测试
2, 硬件设计:
本章实验功能简介:开机后,显示提示信息,等待外部输入。KEY0用于申请内存,每次申请2K字节内存。KEY1用于写数据到申请到的内存里面。KEY2用于释放内存。WK_UP用于切换操做内存区(内部内存/外部内存)。DS0用于指示程序运行状态。本章咱们还能够经过USMART调试,测试内存管理函数。
本实验用到的硬件资源有:
1) 指示灯DS0
2) 四个按键
3) 串口 //USMART
4) TFTLCD模块
5) IS62WV51216字体
3, 软件设计:
本章,咱们将内存管理部分单独作一个分组,在工程目录下新建一个MALLOC的文件夹,而后新建malloc.c和malloc.h两个文件,将他们保存在MALLOC文件夹下。
在MDK新建一个MALLOC的组,而后将malloc.c文件加入到该组,并将MALLOC文件夹添加到头文件包含路径。
打开malloc.c文件,输入以下代码:因为本实验涉及到的c语言知识,尤为是指针知识较多,因此就边用边学
#include "malloc.h"
//内存池(4字节对齐)
__align(4) u8 mem1base[MEM1_MAX_SIZE]; //内部SRAM内存池
/*
" u8 mem1base[MEM1_MAX_SIZE];"该数组是定义拿出内部内存池的40K的空间来作实验,为何该数组是u8类型?计算机内存是以字节为单位的存储空间,内存中的每一个字节都有惟一的编号,这个编号就叫地址。在这里就是定义40K个元素,每一个元素表明一个字节。整个数组就表明整个内部SRAM内存池的总容量即40K个元字节的总空间容量。由于不论是存储什么数据类型内存中的地址编号都是32位的,即每一个地址编号能够容纳4个字节,而不一样的数据类型存储在不一样的内存存储区,这就是为何定义变量时必定要先声明其数据类型的缘由。存储一个字符须要一个字节的存储空间,存储一个short类型须要2个字节的存储空间,存储一个int或float须要4个字节空间,就如同PLC内存中的字节,字,双字的定义规则同样(如字节MB0,MB1,MB0和MB1构成MW0;MW0和MW2构成32位的双字DW0,DW4,DW8)。“__align(4)”就是规定4个字节对齐,即每一个32的地址编号存储一个数据类型?好比,字符存储区中地址编号MB0能够存储一个字节即8个位的数据,而存储MB0这个地址编号是以32位的空间来存储,也就是说不论是什么类型数据,存储它的地址编号都是32的,因此指针值必定是32位的。
//“#define MEM1_MAX_SIZE 40*1024 //最大管理内存 40K”,意思是mem1base[MEM1_MAX_SIZE]有40k个元素
*/
__align(4) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000)));//外部SRAM内存池
//#define MEM2_MAX_SIZE 200*1024 //最大管理内存200K,意思是mem2base[MEM2_MAX_SIZE]数组有200K个u8类型元素,第一个元素的地址存储在 //外部存储器SRAM的0X68000000地址,
//内存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; //内部SRAM内存池MAP
/*
//#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE //内存表大小,MEM1_MAX_SIZE/MEM1_BLOCK_SIZE==1250
//#define MEM1_BLOCK_SIZE 32 //内存块大小为32字节;“MEM1_MAX_SIZE/MEM1_BLOCK_SIZE ”的含义是内部SRAM内存池总共40K字节的容量除以32个字节,获得一共40K/32==1250个内存块;也就是说将内部SRAM内存池划为1250个内存块。
“u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];”实际上就等于“u16 mem1mapbase[1250];”意思是定义一个有1250个内存块(元素)的数组,每一个元素是u16类型数据;数组名“mem1mapbase”就是mem1mapbase[0](该数组的第一个元素它表明1250个内存块中的第一个内存块)的地址,也能够说是指针常量;结合与之关联的结构体成员“u16 *memmap[2]={ mem1mapbase,mem2mapbase}”指针类型数组;在这里“mem2mapbase”是外部内存的第一个内存块的地址,是个指针常量用以存放u16类型数据的地址值;结合
“mymemset(mallco_dev.memmap[0], 0,memtblsize[0]*2);”函数分析:结合本程序和结构体有关定义“u16 *memmap[2]; ”,首元素memmap[0]=mem1mapbase;也就是说“mallco_dev.memmap[0]”在这里表示1250个内部内存块中第一个内存块的地址,根据“u16 *memmap[2]={ mem1mapbase,mem2mapbase}”推断出“mallco_dev.memmap[0]”是u16类型指针;
“memtblsize[0]”是什么意思呢?根据“const u32 memtblsize[2]={1250,6250};”能够得知memtblsize[0]==1250即内部内存一共有1250个管理项,
void mymemset(void *s,u8 c,u32 count)
{
u8 *xs = s;
while(count--)*xs++=c;
} //把u8类型数据c填充到以指针变量s为首地址的内存空间中,填充多少个数由count值决定.net
该函数的意思是把u8类型的数据“c”填充到u16类型指针元素memmap[0]中(根据结构体定义“u16 *memmap[2]; ”,而memmap[0]=mem1mapbase),说白了就是把u8类型的数据“c”填充到1250个内存块中的count个内存块中。
而mallco_dev.memmap[memx]是16位的,为了将其所有清零,因此乘以2.
本例中,用到了指针类型数组“u16 *memmap[2]={ mem1mapbase,mem2mapbase}”,为何要定义指针类型数组呢?mem1mapbase是数组
“u16 mem1mapbase[1250];”的首个元素地址(即*mem1mapbase等价于mem1mapbase[0]),而mem1mapbase[0]就表明内部存储器1250个存储块中的第一个存储块;根据结构体赋值定义可知:memmap[0]=mem1mapbase。因此mem1mapbase就是“mallco_dev.memmap[0]”,即mem1mapbase是函数mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2)的第一个形参,由于*mem1mapbase等价于mem1mapbase[0]),而mem1mapbase[0]就表明内部存储器1250个存储块中的第一个存储块。结合
void mymemset(void *s,u8 c,u32 count)函数分析, mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2)函数的意思是:把0写入到1250个存储块中的第一个存储块中;这样就将一个存储块的值赋值为0了。
推断出“mallco_dev.memmap[0]”是u16类型指针;
;
*/
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+MEM2_MAX_SIZE)));
/*
“#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE”
“#define MEM2_BLOCK_SIZE 32”
外部SRAM内存池MAP,同理,“MEM2_MAX_SIZE/MEM2_BLOCK_SIZE”的含义是外部SRAM内存池总共200K字节的容量除以32个字节,获得一共200K/32==6250个内存块;也就是说将外部SRAM内存池划为6250个内存块。
*/
//内存管理参数
/*
内存管理表“MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE”分别是1250和6250个“项”.
每一个内存分块大小即内部和外部SRAM每一个内存块占有32个字节空间“MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE”分别是32个字节;
内存总大小“MEM1_MAX_SIZE,MEM2_MAX_SIZE”,分别是40K和200K个字节的总容量空间
mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);
*/
const u32 memtblsize[2]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE};//内存管理表大小
const u32 memblksize[2]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE}; //内存分块大小
const u32 memsize[2]={MEM1_MAX_SIZE,MEM2_MAX_SIZE}; //内存总大小
/*
struct _m_mallco_dev //内存管理控制器,定义一个结构体类型数据,或结构体变量,
{
void (*init)(u8); //初始化
u8 (*perused)(u8); //内存使用率
u8 *membase[2]; //内存池 管理2个区域的内存 mem1base,mem2base内存池
u16 *memmap[2]; //内存管理状态表 mem1mapbase(==1250块),mem2mapbase(6250), //内存管理状态表
u8 memrdy[2]; //内存管理是否就绪
};
1,结构体成员“void (*init)(u8);”是定义了一个指向函数的指针变量,该指针变量名是init;void表示该函数没有返回值(函数的数据类型由返回值决定);u8是函数的形参。指向函数的指针变量格式:数据类型 + (*变量名)(形参)
本例中:
void mem_init(u8 memx)
{
mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);//内存状态表数据清零 memx:所属内存块,即几号内存块
mymemset(mallco_dev.membase[memx], 0,memsize[memx]); //内存池全部数据清零
mallco_dev.memrdy[memx]=1;//内存管理初始化OK
}
也就是说,本例中用指向函数的指针变量来表示函数。c语言规定函数名就是函数的入口地址,也就是说函数名也是一个指针,指向函数的入口,根据这个原理,能够将指向函数的指针做为函数的参数调用,能够在不一样的状况调用不一样的函数;若是一个指向函数的指针变量等于函数名就能够说该指向函数的指针变量指向了该函数,那么指针变量与函数就是同样的了。好比:“mem_init(memx);”就等同于“mallco_dev.init(memx);”
2,指针类型数组“u8 *membase[2];”,意思是该指针类型数组有两个“char *”类型的指针元素或者说有两个“u8 *”类型指针元素;为何要定义“u8 *”类型呢?由于内存存储区是根据数据类型来划分的,若是不明确声明类型就乱套了。
在C语言和C++语言中,数组元素全为指针的数组称为指针数组。一维指针数组的定义形式为:“类型名 *数组标识符[数组长度]”。
例如,一个一维指针数组的定义:int *ptr_array[10]。该指针数组有10个元素,每一个元素都是int类型的指针即“int *”类型;
指针类型数组“u8 *membase[2];”的赋值是mem1base,mem2base, “mem1base,mem2base”分别是内部内存池和外部内存池的数组名,是指针常量即首元素的地址;由于事先已经定义“u8 mem1base[MEM1_MAX_SIZE]”即“u8 mem1base[40K];”。
*/
//内存管理控制器,结构体变量赋值,即初始化
struct _m_mallco_dev mallco_dev=
{
mem_init, //内存初始化,将函数名“mem_init”赋给结构体成员“void (*init)(u8);”即指向函数的指针变量,
mem_perused, //内存使用率
mem1base,mem2base, //内存池
mem1mapbase,mem2mapbase, //内存管理状态表,mem1mapbase(1250项),mem2mapbase(6250项)
0,0, //内存管理未就绪
};
/*
1,“void *des”无类型指针,不能指向具体的数据,“void *des”无类型指针指向内存中的数据类型由用户本身肯定,如malloc()函数的返回值就是“void *des”无类型指针,由于malloc()函数的返回值是不肯定的是根据形参的数据类型肯定的
2,“void mymemcpy(void *des,void *src,u32 n) ”函数的理解:
des是指针,可是不肯定指向什么类型的数据,换句话说des指针存储的什么类型数据不肯定,“u8 *xdes=des;”将des指针存储的数据
存储到一个新的“u8 *”类型指针xdes中;“u8 *xsrc=src;”同理。
“*xdes++=*xsrc++; ”,当*xsrc++(自增)时,即指针“src”指针自增,也就是说把“src”指针逐位复制到des目标指针去了。复制个数就是n。
3,“*P”的意义:a),“*P”就是以指针变量P的内容(P的内容就是指针变量P里存储的某一类型数据的指针值)为地址的变量;b),指针运算符“*”若是是在定义变量时候加在前面,意思是这个变量是指针变量,如 char *a;若是是在访问指针变量的时候加在前面(如*a),意思是取指针变量指向的值,如 char b=*a; 上面定义了a是一个字符指针,这里就是把指针变量a指向的值取出来并赋给b。
*/
//复制内存,做用是将源地址的内容复制到目标地址
//*des:目的地址
//*src:源地址
//n:须要复制的内存长度(字节为单位)
void mymemcpy(void *des,void *src,u32 n)
{ //“void *des”无类型指针,不能指向具体的数据,“void *des”无类型指针指向内存中的数据类型由用户本身肯定
u8 *xdes=des;//目标地址,“*xdes”转换成u8类型,也能够理解为把目的地地址des存储到xdes指针中
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;
} //以*s为内存首地址的count个字节中,填充c,即把c写入到*s为首地址的内存中,个数多少由count值决定
//内存管理初始化
//memx:所属内存块,要么SRAMEX==1(外部内存);要么SRAMIN(内部内存)==0
/*
const u32 memtblsize[2]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE};//内存管理表大小
const u32 memblksize[2]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE}; //内存分块大小
const u32 memsize[2]={MEM1_MAX_SIZE,MEM2_MAX_SIZE}; //内存总大小设计
*/
void mem_init(u8 memx) //如“mem_init(SRAMIN);”表示内部内存块
{ //memmap,是16位的,mymemset,设置是针对8位的,那么1个16位的数据是否是2个8位组成的啊?!
mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);//内存状态表数据清零
//把u8类型的数据“0”填充到u16类型指针元素memmap[0]中(根据结构体定义“u16 *memmap[2]; ”),memmap[0]=mem1mapbase==1250,
//也就是说“mallco_dev.memmap[memx]”在这里表示1250个内部内存块用以存储u16类型指针,
//“memtblsize[memx]”是什么呢?memtblsize[memx]即memtblsize[0]==1250个内部内存管理表,
//而mallco_dev.memmap[memx]是16位的,为了将其所有清零,因此乘以2.
mymemset(mallco_dev.membase[memx], 0,memsize[memx]); //内存池全部数据清零
//memsize[0]==40K字节空间, mallco_dev.membase[memx]==40K字节空间,
mallco_dev.memrdy[memx]=1; //内存管理初始化OK
}
/*
*/
//获取内存使用率
//memx:所属内存块,要么SRAMEX==1(外部内存);要么SRAMIN(内部内存)==0
//返回值:使用率(0~100)
u8 mem_perused(u8 memx)
{
u32 used=0;
u32 i;
for(i=0;i<memtblsize[memx];i++)
{
if(mallco_dev.memmap[memx][i])used++;
} //mallco_dev.memmap[memx][i]是二维数组。当内存块初始化后该值为0,
return (used*100)/(memtblsize[memx]); //used*100,乘以100是将小数变成整数
}
//内存分配(内部调用)
//memx:所属内存块
//size:要分配的内存大小(字节数)
//返回值:0XFFFFFFFF,表明错误;其余,内存偏移地址
//向memx存储器申请size个字节的连续存储空间,并将size个字节中首个字节的地址偏移值标注出来,注意是地址偏移值而不是地址。
u32 mem_malloc(u8 memx,u32 size)
{
signed long offset=0;
u16 nmemb; //须要的内存块数
u16 cmemb=0;//连续空内存块数
u32 i;
if(!mallco_dev.memrdy[memx])mallco_dev.init(memx);//未初始化,先执行初始化
/*
“mallco_dev.init(memx);”是什么意思?mallco_dev.init(memx)是结构体变量mallco_dev的一个成员,本句中就是对结构体成员的引用,即执行
mem_init(u8 memx)函数的意思;如何引用结构体中指向函数的指针变量成员?既然是指向函数的指针变量且有赋值,在引用时按照格式:
结构体变量名.指向函数的指针变量名(形参);
*/
if(size==0)return 0XFFFFFFFF;//不须要分配 memblksize[memx]==32
nmemb=size/memblksize[memx]; //获取须要分配的连续内存块数
/*
c语言规定:除法的运算结果与运算对象的数据类型有关,两个数都是int则商(即结果)是int,若商(即结果)有小数则省略掉小数点部分。本例中
size和memblksize[memx]都是int,因此结果只能是int。假设size<32,则nmemb==0;
c语言规定取余运算的运算对象必须是int。当小数对大数取余时余(即结果)是小数自己;例如,在“if(size%memblksize[memx])nmemb++;”中 ,
假设size<32,则size%memblksize[memx]的结果是size值自己,因此执行“nmemb++;”运算,这时运算结果是nmemb==1;若是size是32的整数倍则不执行
“nmemb++;”运算;
memtblsize[0]==1250,memtblsize[1]==6250,
mallco_dev.memmap[memx][offset]是什么意思?
*/
if(size%memblksize[memx])nmemb++;
for(offset=memtblsize[memx]-1;offset>=0;offset--)//搜索整个内存控制区
{
if(!mallco_dev.memmap[memx][offset])cmemb++;//连续空内存块数增长,offset从1249->0变化
/*
如,{ memmap[0][149],memmap[0][148],...memmap[0][1],memmap[0][0]};实际上能够把“mallco_dev.memmap[memx][offset]”视为具备1250个变量的 3d
一维数组,每一个元素对应的实际意义是对应的一个内存块,顺序是offset从1249(高)->0(低)变化;若是哪一个变量等于0(即空闲)就执行
“cmemb++;”操做,这样就能够计算出连续空闲内存块数cmemb;切记!目的是要获取连续的空闲的内存块数!这样就必须结合下一句
“else cmemb=0;”来分析;若是没有出现连续的空闲内存块(即数组顺序相连的变量值没有出现相似“0,0,0,0,0”这样的状况),程序会执行下一语
句“else cmemb=0;”即把上面的“cmemb”统计值清零,这样程序就会在for循环里面继续寻找符合“if(cmemb==nmemb)”条件的状态出现,
若是for循环执行完了尚未出现符合“if(cmemb==nmemb)”条件的状态,则返回0XFFFFFFFF结束本函数表示没有找到符合条件的内存块。假
设:size=65,那么nmemb就是3即须要获取连续3个内存块来存放65个字节,再假设数组顺序相连的变量值出现了相似“0,0,0,0,0”这样的状况(即有
连续4个空闲的内存块),这时就出现了符合“if(cmemb==nmemb)”条件的状态,即当cmemb计数计到3的时候(即出现了连续相连的3个内存块)就
符合“cmemb==nmemb”了,程序就天然进入“if(cmemb==nmemb)”语句。
offset*memblksize[memx]表明什么呢?offset的取值范围是0-1249,memblksize[memx]表明每一个内存块的字节数即32,offset*memblksize[memx]就
是返回偏移地址值;也就是把连续空闲的内存块对应的地址的首地址值标注出来。
*/
else cmemb=0; //连续内存块清零
if(cmemb==nmemb) //找到了连续nmemb个空内存块
{
for(i=0;i<nmemb;i++) //标注内存块非空,以避免下一个for循环时再次将该空间计入
{
mallco_dev.memmap[memx][offset+i]=nmemb;
}
return (offset*memblksize[memx]);//返回偏移地址
}
}
return 0XFFFFFFFF;//未找到符合分配条件的内存块
}
//释放内存(内部调用)
//memx:所属内存块
//offset:内存地址偏移
//返回值:0,释放成功;1,释放失败;
u8 mem_free(u8 memx,u32 offset)
{
int i;
if(!mallco_dev.memrdy[memx])//未初始化,先执行初始化
{
mallco_dev.init(memx); //本句等价于“mem_init(memx);”
return 1;//未初始化
}
if(offset<memsize[memx])//以避免偏移在内存池内. memsize[memx]==40K字节
{
int index=offset/memblksize[memx]; //偏移所在内存块号码 memblksize[memx]==32,
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];
mem_free(memx,offset);//释放内存
}
//分配内存(外部调用)
//memx:所属内存块
//size:内存大小(字节)
//返回值:分配到的内存首地址.
//在memx存储器中,找出size个字节的连续空闲的内存空间,并将连续空闲的内存空间指针值标注出来;返回值就是这个指针值
/*
mallco_dev.membase[memx]即mallco_dev.membase[0]表明MCU内部存储器的40K字节中的第一个字节变量的地址,是u8类型指针变量,也就是说一个字节占用一个地址;换句话说,把内部存储器的40K字节的地址定义为一个“u8 mem1base[MEM1_MAX_SIZE]”数组,指针类型数组“u8 *membase[2];”的赋值是{mem1base,mem2base},而“mem1base,mem2base”分别是内部内存池和外部内存池的数组名,各自首元素的地址亦是个指针常量;由于事先已经定义
“u8 mem1base[MEM1_MAX_SIZE]”即“u8 mem1base[40K];”。如何理解“(void*)((u32)mallco_dev.membase[memx]+offset); ”呢?
1),已经说过mallco_dev.membase[memx]是首个变量的地址即40k字节中首个字节的地址值;
2),“offset”是:向memx存储器申请size个字节的连续空闲存储空间,这个找到的连续空闲空间当中首个字节的地址偏移值就是offset,offset==32(将32个字节空间组成一个内存块)*内存块号(如,假设向内部存储器申请64个字节的连续空闲存储空间,经过“mem_malloc(memx,size); ”函数获得在第五个存储块开始有连续2个存储快空闲可供使用(假设是5号和4号存储快),由于每一个存储快有32个字节即有32个地址编号,4*32==128(这里的4是指第四块),5*32==160(这里的5是指第五块),那么这个160就是40K个字节编号当中的地址偏移值offset,即128-192号就是第四块和第五块内存块所对应的指针编号);注意offset是地址偏移值而不是地址;为何要引入地址偏移值这个概念呢?假设第一个字节的地址值是0x0000 6800,那么就知道(0x0000 6800+160)的值就是第五块内存的指针。
3),“(u32)mallco_dev.membase[memx]”表明指针类型数组,意义是内部存储器40K字节中的第一个字节变量的地址,原来存放的是u8类型数据的地址,如今强制类型转换扩展为u32类型;
4),(void*)((u32)mallco_dev.membase[memx]+offset); 转换为无类型指针,指针值是32位,由此可知,“void *mymalloc(u8 memx,u32 size)”函数的返回值就是一个指针,即形参size所指向的由高向低的首个指针值;“void *mymalloc(u8 memx,u32 size)”是个指针类型函数,只能赋给指针。
*/
void *mymalloc(u8 memx,u32 size) //p=mymalloc(sramx,2048)
{
u32 offset;
offset=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=mem_malloc(memx,size);
if(offset==0XFFFFFFFF)return NULL;
else
{
mymemcpy((void*)((u32)mallco_dev.membase[memx]+offset),ptr,size); //拷贝旧内存内容到新内存
// 把size个字节指针ptr复制到“((u32)mallco_dev.membase[memx]+offset)”,
myfree(memx,ptr); //释放旧内存,由于在mem_malloc(memx,size)中已经将连续空闲内存块标注为1(已被占用),清除掉原来的标记
return (void*)((u32)mallco_dev.membase[memx]+offset); //返回新内存首地址,无类型指针
}
}指针
头文件:
#ifndef __MALLOC_H
#define __MALLOC_H
typedef unsigned long u32;
typedef unsigned short u16;
typedef unsigned char u8;
#ifndef NULL
#define NULL 0
#endif
#define SRAMIN 0 //内部内存池
#define SRAMEX 1 //外部内存池
//mem1内存参数设定.mem1彻底处于内部SRAM里面
#define MEM1_BLOCK_SIZE 32 //内存块大小为32字节
#define MEM1_MAX_SIZE 40*1024 //最大管理内存 40K
#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE //内存表大小
//mem2内存参数设定.mem2的内存池处于外部SRAM里面,其余的处于内部SRAM里面
#define MEM2_BLOCK_SIZE 32 //内存块大小为32字节
#define MEM2_MAX_SIZE 200*1024 //最大管理内存200K
#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE //内存表大小
struct _m_mallco_dev //内存管理控制器
{
void (*init)(u8); //初始化
u8 (*perused)(u8); //内存使用率
u8 *membase[2]; //内存池 管理2个区域的内存
u16 *memmap[2]; //内存管理状态表
u8 memrdy[2]; //内存管理是否就绪
};
extern struct _m_mallco_dev mallco_dev; //在mallco.c里面定义,定义全局变量,结构体变量mallco_dev
void mymemset(void *s,u8 c,u32 count); //设置内存
void mymemcpy(void *des,void *src,u32 n);//复制内存
void mem_init(u8 memx); //内存管理初始化函数(外/内部调用)
u32 mem_malloc(u8 memx,u32 size); //内存分配(内部调用)
u8 mem_free(u8 memx,u32 offset); //内存释放(内部调用)
u8 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,都是32字节。内存池总大小,内部为40K,外部为200K(最大支持到近1M字节,不过为了方便演示,这里只管理200K内存)。MEM1_ALLOC_TABLE_SIZE和MEM2_ALLOC_TABLE_SIZE,则分别表明内存池1和2的内存管理表大小。
从这里能够看出,若是内存分块越小,那么内存管理表就越大,当分块为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
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
led_init(); //初始化与LED链接的硬件接口
LCD_Init(); //初始化LCD
usmart_dev.init(72); //初始化USMART
Key_Init(); //按键初始化
FSMC_SRAM_Init(); //初始化外部SRAM,由于用到了外部sram
mem_init(SRAMIN); //初始化内部内存池,SRAMIN==0
mem_init(SRAMEX); //初始化外部内存池,SRAMEX==1
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"MALLOC TEST");
LCD_ShowString(60,90,200,16,16,"WANG YAN");
LCD_ShowString(60,110,200,16,16,"2013/12/16");
LCD_ShowString(60,130,200,16,16,"key_right:Malloc key_left:Free");
LCD_ShowString(60,150,200,16,16,"wake_up:SRAMx key_down:Read");
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,170,200,16,16,"SRAMIN");
LCD_ShowString(60,190,200,16,16,"SRAMIN USED: %");
LCD_ShowString(60,210,200,16,16,"SRAMEX USED: %");
while(1)
{
key=Key_Scan(0);//不支持连按
switch(key)
{
case 0://没有按键按下
break;
case key_right: //KEY0按下
p=mymalloc(sramx,2048);//申请2K字节,即64个内存块的空间
if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);//向p写入一些内容
break;
case key_down: //KEY1按下
if(p!=NULL) //NULL==0;
{
sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容
// LCD_ShowString(60,270,200,16,16,p);
LCD_ShowString(60,250,200,16,16,p);//显示P的内容
printf("Memory Malloc Test%03d\n",i);//将“Memory Malloc Test”用串口输出,利用串口助手能够看到输出的结果
//"03"表示参数“i”的值只显示3位,%-输出控制符;d-将“i”以十进制的形式输出;i的范围0--255;输出参数能够是多个,能够参考郝斌老师的相关视频;
//输出控制符包含:%Ld--L表明long类型;%c--表明字符类型;:%X--表明16进制并大写;
}
break;
case key_left: //KEY2按下
myfree(sramx,p);//释放内存
p=0; //指向空地址
break;
case wake_up: //KEY UP按下
sramx=!sramx;//切换当前malloc/free操做对象
if(sramx)LCD_ShowString(60,170,200,16,16,"SRAMEX");
else LCD_ShowString(60,170,200,16,16,"SRAMIN");
break;
}
if(tp!=p)
{//在内存paddr值处显示:“P Addr:0X%08X”,“0X%08X”以大写16进制显示参数tp(32位),“08”表示8位数。0X AAAA AAAA
//刚进入程序时,由于执行了“mem_init(SRAMIN);”初始化函数,因此p==0;因此LCD不会有显示
//由于程序一开始就有“u8 *tp=0;”,因此若不按下任何按键LCD就不会显示下面的内容(即“if(tp!=p)”控制的显示内容);
tp=p;//PAddr显示的是指针p自己的地址值;指针值是u32类型
sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp);//将指针p自己的地址值在LCD上打印出来即显示
LCD_ShowString(60,230,200,16,16,paddr); //显示p的地址
if(p)
LCD_ShowString(60,250,200,16,16,p);//显示P的内容,即指针p内存储的数据“Memory Malloc Test%03d”
else LCD_Fill(60,250,239,266,WHITE); //p=0,清除显示
}
delay_ms(10);
i++;
if((i%20)==0)//DS0闪烁.
{
LCD_ShowNum(60+96,190,mem_perused(SRAMIN),3,16);//显示内部内存使用率
LCD_ShowNum(60+96,210,mem_perused(SRAMEX),3,16);//显示外部内存使用率
led0=!led0;
}
}
}
总结:经过内存管理的学习,更加深入的领会到指针是c语言的灵魂,对c语言的知识是一个巩固和提升;同时也学习到了sprintf()函数的运用技巧。
本章但愿利用USMART调试内存管理,因此在USMART里面添加了mymalloc和myfree两个函数,用于测试内存分配和内存释放。你们能够经过USMART自行测试。
4,下载验证:
在代码编译成功以后,咱们经过下载代码到ALIENTEK战舰STM32开发板上,获得如图所示界面:
能够看到,内外内存的使用率均为0%,说明尚未任何内存被使用,此时咱们按下KEY0,就能够看到内部内存被使用5%(每按下一次申请2K的空间,lcd上显示的使用率递增5%;20*2K==40K)了,同时看到下面提示了指针p所指向的地址(其实就是被分配到的内存地址)和内容。多按几回KEY0,能够看到内存使用率持续上升(注意对比p的值,能够发现是递减的,说明是从顶部开始分配内存!),此时若是按下KEY2,能够发现内存使用率下降了5%,可是再按KEY2将再也不下降,说明“内存泄露”了。这就是前面提到的对一个指针屡次申请内存,而以前申请的内存又没释放,致使的“内存泄露”。
按KEY_UP按键,能够切换当前操做内存(内部内存/外部内存),KEY1键用于更新p的内容,更新后的内容将从新显示在LCD模块上面。
本章,咱们还能够借助USMART,测试内存的分配和释放,有兴趣的朋友能够动手试试。如右图USMART测试内存管理函数所示。
/////////////////////////插补:printf和sprintf函数的用法////////////////////////////
printf和sprintf函数的用法很是重要,用于程序参数调试。这两个函数都包含在系统启动代码“stdio.h”头文件中;
1,例:printf("Memory Malloc Test%03d\n",i);//将“Memory Malloc Test”用串口输出,利用串口助手能够看到输出的结果;
"03"表示参数“i”的值只显示3位,%d-输出控制符;d-将“i”以十进制的形式输出;i的范围0--255(由于是u8类型);输出参数能够是多个,能够参考郝斌老师的相关视频;输出控制符包含:%Ld--L表明long类型;%c--表明字符类型;:%X--表明16进制并大写;%s-字符串类型
2,如何理解字符串打印函数int sprintf(char * __restrict /*s*/, const char * __restrict /*format*/, ...) __attribute__((__nonnull__(1,2)));?
在内存管理实验中例如,sprintf((char*)p,"Memory Malloc Test%03d",i)函数的使用问题:
1),第一个形参(char*)p的意思是(第一个形参必须是指针类型),第二个形参即字符串“Memory Malloc Test%03d”存储在内存中的具体指针值,由于字符串是u8类型即char*类型,因此“(char*)p”与之呼应;由于第二个形参“Memory Malloc Test%03d”中有输出控制符“%03d”,因此第一个形参(char*)p的值是变化的(由于参数“i”的值在变);这里输出控制符“%03d”的意思能够参考printf()函数;
也就是说,sprintf函数的第一个形参必须是指针类型,它是第二个形参(输出内容)在存储器中存储的首地址,是一个指针变量,第三个形参就是要输出的参数;因此说sprintf函数包含的内容不少,做用很大。
2),sprintf函数的做用是在显示屏中显示相关参数,即向p写入一些内容即Memory Malloc Test%03d”;
结合LCD_ShowString(60,270,200,16,16,p)的显示结果更好理解,即显示P的存储内容即在相应的坐标处“Memory Malloc Test%03d”;”
3),例子:
u8 s[8];
char* who = "I"; //将字符“I”赋给char* 类型变量who;
char* whom = "STM32"; //将字符串“STM32”赋给char* 类型变量whom;
sprintf(s, "%s love %s.", who, whom); //产生:"I love STM32. " 这字符串写到s中
LCD_ShowString(60,250,200,16,16,s);
//sprintf(s, "%10.3f", 3.1415626); //产生:" 3.142",浮点型显示
4),sprintf函数通常状况下是用在须要字符显示的场合,好比你要显示一个数字,一般的作法是取出某一位而后加上0x30这个数,这样一位一位来比较麻烦,用sprintf这个函数呢,一次性就给你搞定了
好比你想打印3.1415926这个数值到液晶上显示,一般的作法代码就不少并且乱,有了这个函数呢,直接这样
float PI=3.1415926;
u16 strbuffer[10];
sprintf(strbuffer,"PI=:%09d",PI);
而后直接将strbuffer这个数组送去显示便可,或者打印到串口,这样就能够直接字符显示了
注意:sprintf函数必须结合LCD显示函数使用才能有效!而且形参必须定义好合适的数据类型;sprintf()函数的最大做用就是很是方便的在LCD显示屏上显示本身想要的数据类型!参考关于sprintf函数的实验。
3,疑问?
a,在51单片机中,如何将sprintf函数包含进51的启动代码中?若是不将sprintf函数包含进51的头文件,显示屏确定不能用sprintf函数显示数据。
b,在stdio.h中,找到的是int sprintf(char * __restrict /*s*/, const char * __restrict /*format*/, ...) __attribute__((__nonnull__(1,2)));怎么看不到函数内容?
sprintf是C语言标准库提供的函数, 包含在stdio.h中, 只要在文件头#include <stdio.h>便可.
原型为int sprintf ( char * str, const char * format, ... );
/////////////////////////插补:printf和sprintf函数的用法////////////////////////////
https://blog.csdn.net/doudoududu1314/article/details/78186661