一、内存管理的原理
通过本实验,主要通过介绍程序运行过程中采用到C语言中的那些知识点,其中比较好用的是结构体,比较有趣的是C语言的灵魂“指针”。
在STM32f103芯片上的内存管理实验,主要是采用分块式内存管理,即把内存(也叫内存池)分成整数块(一般每块内存取32字节或者64字节),同时在内存池中的每一块内存都对应着内存管理表的一块。
分配过程:当要分配n块内存,从内存管理表的顶部往底层寻找,直到找到连续的n块空内存(非0表示该内存块已经被占用),以此时的内存管理表的地址为起点(包括这个地址),往上n块都值成数字“n”,方便以后删除释放内存,同时返回一个地址指针(表示内存管理表分配完内存之后所在)。看
mymalloc(memx,size)。
释放过程:当要释放内存时,要用到myfree(memx,fp),其中(u32)fp-(u32)malloc_dev.membase[memx]=offset(偏移量),根据偏移量可得出index=offset/memblksize[memx],即内存管理表所在的块,malloc_dev.memmapbase[memx][index]等于需要释放的块数n,以index块为起点,把接下来的n块内存管理表清零。
参数定义:
既然这里涉及到许多内存参数,学过C语言的同学知道可以用到结构体,其封装性可以大大提高我们对所对应的参数的属性的了解。
struct __Malloc_dev //此结构体定义在malloc.h中,是对各种参数的定义 { void (*init)(u8); //函数指针(*init), 此函数带有u8 类型的形参,返回(void ) u8 (*perused)(u8); //函数指针(*perused),此函数带有u8类型的形参,返回(u8) u8 *membase[SRAMBANK]; //一个二维数组(*membase[SRAMBANK]),此数组为u8 类型--->内存池 u16 *memmapbase[SRAMBANK]; //一个二维数组(*memmapbase[SRAMBANK]),此数组为u16 类型----〉内存管理表 u8 memready[SRAMBANK]; //一个数组(memready[SRAMBANK]),此数组为u8 类型 ----〉内存状态 };
在malloc.c文件中,对结构体初始化
struct __Malloc_dev malloc_dev= { mymalloc_init, //(*init)==mymalloc_init 函数名mymalloc_init 本质上也是一个地址指针(这个指针指向一个函数名所代表的函数) mymalloc_perused, //(*perused)==mymalloc_perused 函mymalloc_perused 本质上也是一个地址指针(这个指针指向一个函数名所代表的函数) membase1,membase2, //内存池的个数 memmapbase1,memmapbase2,//内存管理表的个数 0,0 //每个内存的装备状态(0代表还未装备好,1代表准备好了) };
各个初始化的变量都要有定义,不然编译器会报错!!!
//定义内存池和内存管理表的数组 __align(32) u8 membase1[MEM1_MAX_SIZE]; //32位对齐,定义内存池1的数组 __align(32) u8 membase2[MEM2_MAX_SIZE] __attribute__((at(0x68000000)));//32位对齐,定义内存池2的数组,因为起始地址跟SRAM的地址不一致,
//故要重新定位 u16 memmapbase1[MEM1_TABLE_SIZE]; //定义内存管理表1 u16 memmapbase2[MEM2_TABLE_SIZE] __attribute__((at(0x68000000+MEM2_MAX_SIZE)));//定义内存管理表2
//设置内存
//*s:内存首地址
//c :要设置的值
//count:需要设置的内存大小(字节为单位)
void mymemset(void *s,u8 c,u32 count)
{
u8 *xs = s;
while(count--)*xs++=c;
}
void mymalloc_init(u8 memx) { mymemset(malloc_dev.membase[memx],0,memmaxsize[memx]); //通过memset函数对内存池的所有块清零 mymemset(malloc_dev.memmapbase[memx],0,memtablesize[memx]*2); //通过memset函数对内存管理表的所有块清零 malloc_dev.memready[memx]=1; //把内存状态置一表示已经初始化了 } u8 mymalloc_perused(u8 memx) { u32 i; u32 used=0; for(i=0;i<memtablesize[memx];i++) //遍历整个内存的内存管理表 { if(malloc_dev.memmapbase[memx][i]!=NULL) used++; //找到内存管理表所有非零的项,并统计出总和 } return used*100/memtablesize[memx]; //把所有非零项总和/整个内存管理表*100,表示内存管理表的使用率 }
从上面的两个指针函数的应用,就可知指针的威力,
void (*init)(u8); //函数指针(*init), 此函数带有u8 类型的形参,返回(void ) u8 (*perused)(u8); //函数指针(*perused),此函数带有u8类型的形参,返回(u8)
通过指针(此指针指向函数)就可对一个函数完成定义,在此工程中,由于初始化结构体之后,
void (*init)(u8) 相当于 void mymalloc_init(u8 memx)
u8 (*perused)(u8) 相当于 u8 mymalloc_perused(u8 memx)
除了以上的结构体定义之外,还需要对内存块大小、内存池大小、内存管理表的数量进行定义
#ifndef NULL #define NULL 0 #endif //内存的块数 #define SRAMIN 0 //内部内存池 #define SRAMEX 1 //外部内存池 #define SRAMBANK 2 //内存池的数量 //内存1的具体参数 #define MEM1_BLK_SIZE 32 //内存块大小
#define MEM1_MAX_SIZE 40*1024 //内存池大小
#define MEM1_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLK_SIZE //内存管理表的数量
//内存2的具体参数
#define MEM2_BLK_SIZE 32 //内存块大小
#define MEM2_MAX_SIZE 32*1 //内存池大小
#define MEM2_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLK_SIZE 内存管理表的数量
为了方便调用,把这些分类地定义数组
const u32 memblksize[SRAMBANK]={MEM1_BLK_SIZE,MEM2_BLK_SIZE}; //内存块大小 const u32 memmaxsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE}; //内存池的大小 const u32 memtablesize[SRAMBANK]={MEM1_TABLE_SIZE,MEM2_TABLE_SIZE}; //内存管理表的数量
二、内存的申请和释放详解
内存申请:
//内存分配(内部调用) //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:所属内存块 //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:所属内存块 //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); //释放内存 }
三、main函数中的调用
int main(void) { u8 key; u8 i=0; u8 *p=0; u8 *tp=0; u8 paddr[18]; //存放P Addr:+p地址的ASCII值 delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级 uart_init(115200); //串口初始化为115200 LED_Init(); //初始化与LED连接的硬件接口 KEY_Init(); //初始化按键 LCD_Init(); //初始化LCD my_mem_init(SRAMIN); //初始化内部内存池 POINT_COLOR=RED; //设置字体为红色 LCD_ShowString(30,50,200,16,16,"ELITE STM32F103 ^_^"); 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,"2015/1/20"); LCD_ShowString(30,130,200,16,16,"KEY0:Malloc KEY1:Free"); LCD_ShowString(30,150,200,16,16,"KEY_UP:Write"); POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(30,170,200,16,16,"SRAMIN"); LCD_ShowString(30,190,200,16,16,"SRAMIN USED: %"); while(1) { key=KEY_Scan(0); //不支持连按 switch(key) { case 0: //没有按键按下 break; case KEY0_PRES: //KEY0按下 p=mymalloc(SRAMIN,2048);//申请2K字节 if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);//向p写入一些内容 break; case KEY1_PRES: //KEY1按下 myfree(SRAMIN,p); //释放内存 p=0; //指向空地址 break; case WKUP_PRES: //KEY UP按下 if(p!=NULL) { sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容 LCD_ShowString(30,250,200,16,16,p); //显示P的内容 } break; } if(tp!=p) { tp=p; sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp); LCD_ShowString(30,230,200,16,16,paddr); //显示p的地址 if(p)LCD_ShowString(30,250,200,16,16,p);//显示P的内容 else LCD_Fill(30,250,239,266,WHITE); //p=0,清除显示 } delay_ms(10); i++; if((i%20)==0)//DS0闪烁. { LCD_ShowNum(30+96,190,my_mem_perused(SRAMIN),3,16);//显示内部内存使用率 LED0=!LED0; } } }
其中值得注意的是sprintf(装字符串的数组,“字符串”,写入字符串的内容);
过程:
以sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp)为例子,
tp是一个指针地址,赋给"P Addr:0X%08X"这个字符串,最后赋给paddr这个数组。
sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容
这个也是一个道理,i=>"Memory Malloc Test%03d"=>p(数组)
总结:要活用这个sprintf函数,可以快速地把一个带参数字符串赋给一个数组变量。