内存管理实验_STM32F1开发指南_第四十二章

                                                             第四十二章 内存管理实验

序言

    本章将学习内存管理,实现对内存的动态管理。

本章分为如下几部分:

42.1 内存管理简介

42.2 硬件设计

42.3 软件设计

42.4 下载验证

42.1 内存管理简介

内存管理是什么?

是指软件运行时,对计算机内存资源的分配和使用的技术。

内存管理的目的是什么?

高效、快速地分配,并且在适当的时候释放和回收内存资源。

内存管理的实现方法是什么?

有很多,但最终都是实现两个函数:

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个内存管理表项目的值清零,标记释

放,完成一次内存释放。

42.2 硬件设计

    本章实验功能简介:

   ① 开机后,显示提示信息,等待外部输入。

   ② KEY0用于申请内存,每次申请2k字节内存。KEY1用于写数据到申请的内存里面。KEY2用于

释放内存。WK_UP用于切换操作内存区(内部内存/外部内存)。DS0用于指示程序运行状态。

   ③ 本章还可以通过USMART调试,测试内存管理函数。

   本实验用到的硬件资源:

    1)指示灯DS0;

    2)四个按键;

    3)串口;

    4)TFTLCD模块;

    5)IS62WV51216

42.3 软件设计

    本章,我们将内存管理部分单独做一个分组,在工程目录下新建一个MALLOC文件夹,

然后在其下面新建malloc.c和malloc.h两个文件。

   ** malloc.h**文件代码如下:

1#ifndef __MALLOC_H 2#define __MALLOC_H 3#ifndef NULL 4#defein NULL 0 5#endif 6 7//定义两个内存池 8#define SRAMIN 0 //内部内存池 9#define SRAMEX 1 //外部内存池 10 11#define SRAMBANK 2 //定义支持的SRAM块数 12 13//mem1 内存参数设定.mem1完全处于内部SRAM里面 14#define MEM1_BLOCK_SIZE 32 //内存块大小为32字节 15#define MEM1_MAX_SIZE 40*1024 //最大管理内存 40k 16#define MEM1_MALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE //内存表的大小 17//mem2 内存参数设定.mem2的内存池处于外部SRAM里面 18#define MEM2_BLOCK_SIZE 32 //内存块大小为32字节 19#define MEM2_MAX_SIZE 960*1024 //最大管理内存960k 20#define MEM2_ALLOC_TABLE_SIZE MEM2_MAX_SIZE/MEM2_BLOCK_SIZE //内存表的大小 21//内存管理控制器 22struct _m_mallco_dev 23{ 24 void(*init)(u8); //初始化 25 u8 (*perused)(u8); //内存使用率 26 u8 * membase[SRAMBANK]; //内存池 管理SRAMBANK个区域的内存 27 u16 * memmap[SRAMBANK]; //内存管理状态表 28 u8 memrdy[SRAMBANK]; //内存管理是否就绪 29}; 30extern struct _m_mallco_dev mallco_dev; //在malloc.c里面定义 31void mymemset(void *s, u8 c, u32 count); //设置内存 32void mymemcpy(void *des, void *src, u32 n); //复制内存 33void my_mem_init(u8 memx); //内存管理初始化函数(外/内部调用) 34u32 my_mem_malloc(u8 memx, u32 size); //内存分配(内部调用) 35u8 my_mem_free(u8 memx, u32 size); //内粗释放(内部调用) 36u8 my_mem_perused(u8 memx); //获得内存使用率(内/外部调用) 37////////////////////////////////////////////////////// 38//用户调用 39void myfree(u8 memx, void *ptr); //内存释放(外部调用) 40void *mymalloc(u8 memx, u32 size); //内存分配(外部调用) 41void *myrealloc(u8 memx, void *ptr, u32 size); //重新分配内存(外部调用) 42#endif 43

    **分析:**从上面可以看出,如果内存分块越小,那么内存管理表就越大,当分块为2字节1个块的

时候,内存管理表就和内存池一样大了(管理表的每项都是u16类型)。显然是不合适的,我们

这里取32字节,比例为1:16,内存管理表相对就比较小了。

   ** mallco.c**文件代码如下:

1.h" 2//内存池(4字节对齐) 3__align(4) u8 mem1base[MEM1_MAX_SIZE]; //内部SRAM内存池(掉电丢失) 4__align(4) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0x68000000))); //外部SRAM内存池(掉电丢失) 5//1.内存管理表 6u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; //内部SRAM内存池MAP 7u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0x68000000 + MEM2_MAX_SIZE))); //外部SRAM内存池MAP 8//2.内存管理参数 9const u32 memblsize[2] = {MEM1_ALLOC_TABLE_SIZE, MEM2_ALLOC_TABLE_SIZE}; //内存表的大小 10const u32 memblksize[2] = {MEM1_BLOCK_SIZE, MEM2_BLOCK_SIZE}; //内存分块大小 11const u32 memsize[2] = {MEM1_MAX_SIZE, MEM2_MAX_SIZE}; //内存总大小 12//3.内存管理控制器 13struct _m_mallco_dev mallco_dev = 14{ 15 mem_init, //内存初始化 16 mem_perused, //内存使用率 17 mem1base, mem2base, //内存池 18 mem1mapbase, mem2mapbase, //内存管理状态表 19 00//内存管理未就绪 20}; 21//4.复制内存 22/* 23入 参: *des:目的地址;*src:源地址;n:需要复制的内存长度 24返回值:无 25*/ 26void mymemcpy(void * des, void *src, u32 n) 27{ 28 u8 * xdes = des; 29 u8 * xsrc = src; 30} 31//5.设置内存 32/* 33入 参:*s:内存首地址; c:要设置的值;count:需要设置的内存的大小(字节为单位) 34返回值:无 35*/ 36void mymemset(void *s, u8 c, u32 count) 37{ 38 u8 * xs = s; 39 while(count--) 40 { 41 *xs++ = c; 42 } 43} 44//6.内存管理初始化 45/* 46入 参:memx:所属内存块 47返回值:无 48*/ 49void mem_init(u8 memx) 50{ 51 mymemset(mallco_dev.memmap[memx], 0, memtblsize[memx]*2); //内存状态表数据清零 52 mymemset(mallco_dev.membase[memx], 0, memsize[memx]); //内存池所有数据清零 53 mallco_dev.memrdy[memx] = 1; //内存管理初始化OK 54} 55//7.获得内存使用率 56/* 57入 参:memx:所属内存块 58返回值:使用率(0~100) 59*/ 60u8 mem_perused(u8 memx) 61{ 62 u32 used = 0; 63 u32 i; 64 for(i = 0; i < memtblsize[memx]; i++) 65 { 66 if(mallco_dev.memmap[memx][i]) 67 used++; 68 } 69 return (used*100)/(memtblsize[memx]); 70} 71//8.内存分配(内部调用) 72/* 73入 参:memx:所属内存块;size:所要分配的内存的大小(字节) 74返回值:0xffff ffff表示错误,其他,内存偏移地址 75*/ 76u32 mem_malloc(u8 memx, u32 size) 77{ 78 signed long offset = 0; 79 u16 nmemb; //需要的内存块数 80 u16 cmemb = 0; //连续空内存块数 81 u32 i; 82 if(!mallco_dev.memrdy[memx]) 83 mallco_dev.init(memx); //未初始化,先执行初始化 84 if(size == 0) 85 return 0xffffffff; //不需要分配 86 nmemb = size / memblksize[memx]; //获取需要分配的连续内存块数 87 if(size % memblksize[memx]) 88 nmemb++89 for(offset = memtblsize[memx] - 1; offset >= 0; offset--) //搜索整个内存控制区 90 { 91 if(!mallco_dev.memmap[memx][offset]) 92 cmemb++; //连续空内存块数增加 93 else 94 cmemb = 0; //连续内存块数清零 95 if(cmemb == nmemb) //找到了连续nmemb个空内存块 96 { 97 for(i = 0; i < nmemb; i++) //标注内存块非空 98 { 99 malloc_dev.memmap[memx][offset + i] = nmemb; 100 } 101 return (offset * memblksize[memx]); //返回偏移地址 102 } 103 } 104 return 0xffffffff; //未找到符合分配条件的内存块 105} 106//9.释放内存(内部调用) 107/* 108入 参:memx:所属内存块;offset:内存地址偏移 109返回值:0:释放成功;1:释放失败 110*/ 111u8 mem_free(u8 memx, u32 offset) 112{ 113 int i; 114 if(!mallco_dev.memrdy[memx]) //未初始化,先执行初始化 115 { 116 mallco_dev.init(memx); 117 return 1; //未初始化 118 } 119 if(offset < memsize[memx]) //偏移在内存池内 120 { 121 int index = offset / memblksize[memx]; //偏移所在的内存块号码 122 int nmemb = mallco_dev.memmap[memx][index]; //内存块数量 123 for(i = 0; i < nmemb; i++) 124 { 125 mallco_dev.memmap[memx][index + i] = 0; 126 } 127 return 0; 128 } 129 else 130 { 131 return 2; //偏移超区了 132 } 133} 134//10.释放内存(外部调用) 135/* 136入 参:memx:所属内存块;ptr:内存首地址 137返回值:无 138*/ 139void myfree(u8 memx, void *ptr) 140{ 141 u32 offset; 142 if(ptr == NULL) 143 return; //地址为0 144 offset = (u32)ptr - (u32)mallco_dev.membase[memx]; 145 mem_free(memx, offset); //释放内存 146} 147//11.分配内存(外部调用) 148/* 149入 参:memx:所属内存块;size:内存大小(字节) 150返回值:分配到的内存首地址 151*/ 152void * mymalloc(u8 memx, u32 size) 153{ 154 u32 offset; 155 offset = mem_malloc(memx, size); 156 if(offset == 0xffffffff) 157 return NULL; 158 else 159 return (void *)((u32)mallco_dev.membase[memx] + offset); 160} 161//12.重新分配内存(外部调用) 162/* 163入 参:memx:所属内存块;*ptr:旧内存首地址;size:要分配的内存大小(字节) 164返回值:新分配到内存首地址 165*/ 166void *myrealloc(u8 memx, void *ptr, u32 size) 167{ 168 u32 offset; 169 offset = mem_malloc(memx, size); 170 if(offset == 0xffffffff) 171 return NULL; 172 else 173 { 174 mymemcpy((void*)((u32)mallco_dev.membase[memx] + offset), ptr, size); //拷贝旧内存内容到新内存 175 myfree(memx, ptr); //释放旧内存 176 return (void*)((u32)mallco_dev.membase[memx] + offset); //返回新内存首地址 177 } 178} 179

    这里,通过内存管理控制器mallco_dev结构体,实现对两个内存池的管理控制。

一个是内部SRAM内存池,定义为:

1__align(32) u8 mem1base[MEM1_MAX_SIZE]; 2

另一个是外部SRAM内存池,定义为:

1__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0x68000000))); 2

    其中,MEM1_MAX_SIZE和MEM2_MAX_SIZE为内存池的大小,外部内存池指定地址为0x68000000,

即从外部SRAM首地址开始,内部内存则由编译器自动分配。__align(32)定义内存池为32字节对齐,以

适应各种不同场合的需求。

    此部分代码核心函数为:mem_malloc和mem_free,分别用于内存申请和释放。不过这两个函数都只是

内部调用,外部调用我们使用mymalloc和myfree两个函数。

    

    main.c文件代码如下:

1int main(void) 2{ 3 u8 key; 4 u8 i = 0; 5 u8 *p = 0; 6 u8 *tp = 0; 7 u8 paddr[18]; //存放paddr+p地址的ASCII值 8 u8 sramx = 0; //默认为内部sram 9 delay_init(); //延时函数初始化 10 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组为组2 11 uart_init(115200); //串口初始化为115200 12 LED_init(); 13 KEY_init(); 14 LCD_init(); 15 FSMC_SRAM_Init(); //初始化外部SRAM 16 my_mem_init(SRAMIN); //初始化内部内存池 17 my_mem_init(SRAMEX); //初始化外部内存池 18 while() 19 { 20 key = KEY_Scan(0); //不支持连按 21 switch(key) 22 { 23 case 0: //没有按键按下 24 break; 25 case KEY0_PRES: //KEY0按下 26 p = mymalloc(sramx, 2048); //申请2k字节 27 if(p != NULL) 28 sprintf((char*)p, "Memory Malloc Test%03d", i); //向p写入一些内容 29 break; 30 case KEY2_PRES: //KEY2按下 31 myfree(sramx, p); //释放内存 32 p = 0; 33 break; 34 case WKUP_PRES: //KEY_UP按下 35 sramx = !sramx; //切换当前malloc/free操作对象 36 break; 37 } 38 if(tp != p) 39 { 40 tp = p; 41 sprintf((char*)paddr, "paddr:0x%08x", (u32)tp); 42 } 43 delay_ms(10); 44 i++; 45 if((i%20) == 0) //DS0闪烁 46 { 47 LED0 = !LED0; 48 } 49 } 50} 51

    这部分代码比较简单,主要是对mymalloc和myfree的调用。不过要注意,申请的内存,在用完后

一定要释放。否则多次申请未释放,会造成“内存泄漏”,久而久之,导致无内存可用。

    另外本章希望利用USMART调用内存管理,所以在USMART里面添加mymalloc和myfree两个函数,

用于测试内存分配和内存释放。

42.4 下载验证

    在代码编译成功后,下载到开发板上,得到如图所示界面:

     可以看到,所有的内存的使用率均为0%,说明还没有任何内存被使用,此时我们按下KEY0,

就可以看到内部SRAM内存被使用了5%了。同时看到下面提示指针p所指向的地址(其实也就是被

分配到的内存地址)和内容。多次按下KEY0,可以看到内存使用率持续上升(注意对比p的值,可以

发现是递减的,说明是从顶部开始分配内存的!),此时如果按下KEY2,可以发现内存使用率降低

了5%,但再次按下KEY2就不再降低了,说明“ 内存泄漏” 了。这就是前面提到的对一个指针多次

申请内存,而之前申请的内存又没有释放,导致的。

    按下KEY_UP键,可以切换当前的操作内存,KEY1键用于更新p的内容,更新后的内容将重新

显示在LCD模块上。

   

    本章还可以借助USMART,测试内存的分配和释放,如下图所示:

 

代码交流 2021