完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 正点原子运营官 于 2020-4-13 12:05 编辑
1)实验平台:正点原子STM32mini开发板 2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第三十二章 内存管理实验 上一章,我们在 STM32 FLASH 写入的时候,需要一个 1024 字节的 16 位数组,实际上占用了 2K 字节,而这个数组几乎只能给 STMFLASH_Write 一个函数使用,其实这是非常浪费内存的一种做法,好的办法是:我需要的时候,申请 2K 字节,用完了我释放掉。这样就不会出现一个大数组仅供一个函数使用的浪费现象了,这种内存的申请与释放,就需要用到内存管理。本章,我们将学习内存管理,实现对内存的动态管理。本章分为如下几个部分: 32.1 内存管理简介 32.2 硬件设计 32.3 软件设计 32.4 下载验证 32.1 内存管理简介 内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,他们其实最终都是要实现两个函数:malloc 和 free;malloc 函数用于内存申请,free 函数用于内存释放。本章,我们介绍一种比较简单的办法来实现:分块式内存管理。下面我们介绍一下该方法的实现原理,如图 32.1.1 所示: 图 32.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 个内存管理表项目的值都清零,标记释放,完成一次内存释放。关于分块式内存管理的原理,我们就介绍到这里。 32.2 硬件设计 本章实验功能简介:开机后,显示提示信息,等待外部输入。KEY0 按键用于申请内存,每次申请 2K 字节内存。KEY1 按键用于写数据到申请到的内存里面。WK_UP 按键用于释放内存。DS0 用于指示程序运行状态。本章我们还可以通过 USMART 调试,测试内存管理函数。本实验用到的硬件资源有: 1) 指示灯 DS0 2) 四个按键 3) 串口 4) TFTLCD 模块 这些我们都已经介绍过,接下来我们开始软件设计。 32.3 软件设计 打开上一章的工程,由于本章没有用到 FLASH 模拟 EEPROM 功能,所以,先去掉 stmflash.c(此时 HARDWARE 组剩下:key.c、led.c 和 lcd.c)。 然后,我们将内存管理部分单独做一个分组,在工程目录下新建一个 MALLOC 的文件夹,然后新建 malloc.c 和 malloc.h 两个文件,将他们保存在 MALLOC 文件夹下。在上一章的工程里面新建一个 MALLOC 的组,然后将 malloc.c 文件加入到该组,并将MALLOC 文件夹添加到头文件包含路径。 打开 malloc.c 文件,输入如下代码: #include "malloc.h" //内存池(4 字节对齐) //内存池(4 字节对齐) __align(4) u8 membase[MEM_MAX_SIZE]; //SRAM 内存池 //内存管理表 u16 memmapbase[MEM_ALLOC_TABLE_SIZE]; //SRAM 内存池 MAP //内存管理参数 const u32 memtblsize=MEM_ALLOC_TABLE_SIZE; //内存表大小 const u32 memblksize=MEM_BLOCK_SIZE; //内存分块大小 const u32 memsize=MEM_MAX_SIZE; //内存总大小 //内存管理控制器 struct _m_mallco_dev mallco_dev= { mem_init, //内存初始化 mem_perused, //内存使用率 membase, //内存池 memmapbase, //内存管理状态表 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; } //内存管理初始化 void mem_init(void) { mymemset(mallco_dev.memmap, 0,memtblsize*2);//内存状态表数据清零 mymemset(mallco_dev.membase, 0,memsize); //内存池所有数据清零 mallco_dev.memrdy=1; //内存管理初始化 OK } //获取内存使用率 //返回值:使用率(0~100) u8 mem_perused(void) { u32 used=0; u32 i; for(i=0;i return (used*100)/(memtblsize); } //内存分配(内部调用) //memx:所属内存块 //size:要分配的内存大小(字节) //返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址 u32 mem_malloc(u32 size) { signed long offset=0; u16 nmemb; //需要的内存块数 u16 cmemb=0;//连续空内存块数 u32 i; if(!mallco_dev.memrdy)mallco_dev.init(); //未初始化,先执行初始化 if(size==0)return 0XFFFFFFFF; //不需要分配 nmemb=size/memblksize; //获取需要分配的连续内存块数 if(size%memblksize)nmemb++; for(offset=memtblsize-1;offset>=0;offset--) //搜索整个内存控制区 { if(!mallco_dev.memmap[offset])cmemb++; //连续空内存块数增加 else cmemb=0; //连续内存块清零 if(cmemb==nmemb) //找到了连续 nmemb 个空内存块 { for(i=0;i //返回偏移地址 } } return 0XFFFFFFFF;//未找到符合分配条件的内存块 } //释放内存(内部调用) //offset:内存地址偏移 //返回值:0,释放成功;1,释放失败; u8 mem_free(u32 offset) { int i; if(!mallco_dev.memrdy)//未初始化,先执行初始化 { mallco_dev.init(); return 1;//未初始化 } if(offset int index=offset/memblksize; //偏移所在内存块号码 int nmemb=mallco_dev.memmap[index]; //内存块数量 for(i=0;i }else return 2;//偏移超区了. } //释放内存(外部调用) //ptr:内存首地址 void myfree(void *ptr) { u32 offset; if(ptr==NULL)return;//地址为 0. offset=(u32)ptr-(u32)mallco_dev.membase; mem_free(offset); //释放内存 } //分配内存(外部调用) //size:内存大小(字节) //返回值:分配到的内存首地址. void *mymalloc(u32 size) { u32 offset; offset=mem_malloc(size); if(offset==0XFFFFFFFF)return NULL; else return (void*)((u32)mallco_dev.membase+offset); } //重新分配内存(外部调用) //*ptr:旧内存首地址 //size:要分配的内存大小(字节) //返回值:新分配到的内存首地址. void *myrealloc(void *ptr,u32 size) { u32 offset; offset=mem_malloc(size); if(offset==0XFFFFFFFF)return NULL; else { mymemcpy((void*)((u32)mallco_dev.membase+offset),ptr,size);//拷贝 myfree(ptr); //释放旧内存 return (void*)((u32)mallco_dev.membase+offset); //返回新内存首地址 } } 这里,我们通过内存管理控制器 mallco_dev 结构体(mallco_dev 结构体见 malloc.h),实现 对内存池的管理控制。内存池,定义为: __align(4) u8 membase[MEM_MAX_SIZE]; //SRAM 内存池 其中,MEM_MAX_SIZE 是在 malloc.h 里面定义的内存池大小。__align(4)定义内存池为 4字节对齐,这个非常重要!如果不加这个限制,在某些情况下(比如分配内存给结构体指针),可能出现错误,所以一定要加上这个。 此部分代码的核心函数为:mem_malloc 和 mem_free,分别用于内存申请和内存释放。思路就是我们在 32.1 接所介绍的那样分配和释放内存,不过这两个函数只是内部调用,外部调用我们使用的是 mymalloc 和 myfree 两个函数。其他函数我们就不多介绍了,保存 malloc.c,然后, 打开 malloc.h,在该文件里面输入如下代码: #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 MEM_BLOCK_SIZE 32 //内存块大小为 32 字节 #define MEM_MAX_SIZE 42*1024 //最大管理内存 42K #define MEM_ALLOC_TABLE_SIZE MEM_MAX_SIZE/MEM_BLOCK_SIZE //内存表大小 //内存管理控制器 struct _m_mallco_dev { void (*init)(void); //初始化 u8 (*perused)(void); //内存使用率 u8 *membase; //内存池 u16 *memmap; //内存管理状态表 u8 memrdy; //内存管理是否就绪 }; 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 mem_init(void); //内存管理初始化函数(外/内部调用) u32 mem_malloc(u32 size); //内存分配(内部调用) u8 mem_free(u32 offset); //内存释放(内部调用) u8 mem_perused(void); //得内存使用率(外/内部调用) //用户调用函数 void myfree(void *ptr); //内存释放(外部调用) void *mymalloc(u32 size); //内存分配(外部调用) void *myrealloc(void *ptr,u32 size); //重新分配内存(外部调用) #endif 这部分代码,定义了很多关数据:MEM_BLOCK_SIZE 是内存管理最小分配单元,为 32字节。MEM_MAX_SIZE 是内存池总大小,为 42K。MEM_ALLOC_TABLE_SIZE 代表内存池的内存管理表大小。 从这里可以看出,如果内存分块越小,那么内存管理表就越大,当分块为 2 字节 1 个块的时候,内存管理表就和内存池一样大了(管理表的每项都是 u16 类型)。显然是不合适的,我们这里取 32 字节,比例为 1:16,内存管理表相对就比较小了。 其他就不多说了,大家自行看代码理解就好。保存此部分代码。最后,打开 main.c 文件,修改代码如下: int main(void) { u8 key; u8 i=0; u8 *p=0;u8 *tp=0; u8 paddr[18]; //存放 P Addr:+p 地址的 ASCII 值 HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M delay_init(72); //初始化延时函数 uart_init(115200); //初始化串口 LED_Init(); //初始化 LED KEY_Init(); //初始化按键 LCD_Init(); //初始化 LCD mem_init(); //初始化内存池 POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(60,50,200,16,16,"Mini STM32"); LCD_ShowString(60,70,200,16,16,"MALLOC TEST"); LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(60,110,200,16,16,"2019/11/15"); LCD_ShowString(60,130,200,16,16,"KEY0:Malloc"); LCD_ShowString(60,150,200,16,16,"KEY1:Write Data"); LCD_ShowString(60,170,200,16,16,"WK_UP:Free"); POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(60,190,200,16,16,"SRAM USED: %"); while(1) { key=KEY_Scan(0);//不支持连按 switch(key) { case 0: break; //没有按键按下 case 1: //KEY0 按下 p=mymalloc(2048); //申请 2K 字节 if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);//向p写入内容 break; case 2: //KEY1 按下 if(p!=NULL) { sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容 LCD_ShowString(60,250,200,16,16,p); //显示 P 的内容 } break; case 3: //WK_UP 按下 myfree(p); //释放内存 p=0; //指向空地址 break; } if(tp!=p) { tp=p; sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp); LCD_ShowString(60,230,200,16,16,paddr); //显示 p 的地址 if(p)LCD_ShowString(60,250,200,16,16,p);//显示 P 的内容 else LCD_Fill(60,250,239,266,WHITE); //p=0,清除显示 } delay_ms(10); i++; if((i%20)==0)//DS0 闪烁. { LCD_ShowNum(60+80,190,mem_perused(),3,16);//显示内存使用率 LED0=!LED0; } } } 该部分代码比较简单,主要是对 mymalloc 和 myfree 的应用。不过这里提醒大家,如果对一个指针进行多次内存申请,而之前的申请又没释放,那么将造成“内存泄露”,这是内存管理所不希望发生的,久而久之,可能导致无内存可用的情况!所以,在使用的时候,请大家一定记得,申请的内存在用完以后,一定要释放。 另外,本章希望利用 USMART 调试内存管理,所以在 USMART 里面添加了 mymalloc 和myfree 两个函数,用于测试内存分配和内存释放。大家可以通过 USMART 自行测试。 32.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,得到如图 32.4.1 所示界面: 图 32.4.1 程序运行效果图 可以看到,内外内存的使用率均为 0%,说明还没有任何内存被使用,此时我们按下 KEY0,就可以看到内部内存被使用 4%了,同时看到下面提示了指针 p 所指向的地址(其实就是被分配到的内存地址)和内容。多按几次 KEY0,可以看到内存使用率持续上升(注意对比 p 的值,可以发现是递减的,说明是从顶部开始分配内存!),此时如果按下 WK_UP,可以发现内存使用率降低了 4%,但是再按 WK_UP 将不再降低,说明“内存泄露”了。这就是前面提到的对一个指针多次申请内存,而之前申请的内存又没释放,导致的“内存泄露”,在实际使用的时候,必须避免内存泄露。 KEY1 键用于更新 p 的内容,更新后的内容将重新显示在 LCD 模块上面。 本章,我们还可以借助 USMART,测试内存的分配和释放,有兴趣的朋友可以动手试试。如图 32.4.2 所示: 图 32.4.2 USMART 测试内存管理函数 图中,我们先申请了 4660 字节的内存,然后得到申请到的内存首地址为:0X200097FC,说明我们申请内存成功(如果不成功,则会收到 0),然后释放内存的时候,参数是指针的地址,即执行:myfree(0X200097FC),就可以释放我们申请到的内存。其他情况,大家可以自行测试并分析。 |
|
相关推荐
|
|
STM32进行DMA数据转运,为什么只有A组最后一位数据被转运到B组去?
1265 浏览 2 评论
1585 浏览 3 评论
1206 浏览 0 评论
嵌入式开发 Win11安装ST-Link 提示 设备描述符请求失败 怎么处理?
1339 浏览 0 评论
1632 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-18 06:18 , Processed in 0.479826 second(s), Total 64, Slave 47 queries .
Powered by 电子发烧友网
© 2015 www.ws-dc.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号