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

                                                             第四十二章 内存管理实验

序言

    本章将学习内存管理,实现对内存的动态管理。
本章分为如下几部分:
42.1 内存管理简介
42.2 硬件设计
42.3 软件设计
42.4 下载验证

42.1 内存管理简介

内存管理是什么?
是指软件运行时,对计算机内存资源的分配和使用的技术。
内存管理的目的是什么?
高效、快速地分配,并且在适当的时候释放和回收内存资源。
内存管理的实现方法是什么?
有很多,但最终都是实现两个函数:
Malloc:申请内存;
Free:释放内存。
本章介绍的方法是什么?
分块式内存管理
分块式内存管理的实现原理是什么?
内存管理实验_STM32F1开发指南_第四十二章_第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用于
释放内存。WK_UP用于切换操作内存区(内部内存/外部内存)。DS0用于指示程序运行状态。
   ③ 本章还可以通过USMART调试,测试内存管理函数。
   本实验用到的硬件资源:
    1)指示灯DS0;
    2)四个按键;
    3)串口;
    4)TFTLCD模块;
    5)IS62WV51216

42.3 软件设计

    本章,我们将内存管理部分单独做一个分组,在工程目录下新建一个MALLOC文件夹,
然后在其下面新建malloc.c和malloc.h两个文件。
     malloc.h文件代码如下:
#ifndef __MALLOC_H
#define __MALLOC_H
#ifndef NULL
#defein NULL 0
#endif
    
//定义两个内存池
#define SRAMIN 0     //内部内存池
#define SRAMEX 1     //外部内存池
   
#define SRAMBANK 2   //定义支持的SRAM块数
    
//mem1 内存参数设定.mem1完全处于内部SRAM里面
#define MEM1_BLOCK_SIZE           32                              //内存块大小为32字节
#define MEM1_MAX_SIZE             40*1024                         //最大管理内存 40k
#define MEM1_MALLOC_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     //内存表的大小
//内存管理控制器
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;          //在malloc.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 size);             //内粗释放(内部调用)
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
     分析:从上面可以看出,如果内存分块越小,那么内存管理表就越大,当分块为2字节1个块的
时候,内存管理表就和内存池一样大了(管理表的每项都是u16类型)。显然是不合适的,我们
这里取32字节,比例为1:16,内存管理表相对就比较小了。

     mallco.c文件代码如下:
.h"
//内存池(4字节对齐)
__align(4) u8 mem1base[MEM1_MAX_SIZE];                                    //内部SRAM内存池(掉电丢失)
__align(4) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0x68000000)));    //外部SRAM内存池(掉电丢失)
//1.内存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];                                                    //内部SRAM内存池MAP
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0x68000000 + MEM2_MAX_SIZE)));    //外部SRAM内存池MAP
//2.内存管理参数
const u32 memblsize[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};                      //内存总大小
//3.内存管理控制器
struct _m_mallco_dev mallco_dev = 
{
    mem_init,                 //内存初始化
    mem_perused,              //内存使用率
    mem1base, mem2base,       //内存池
    mem1mapbase, mem2mapbase, //内存管理状态表
    0, 0,                    //内存管理未就绪
};
//4.复制内存
/*
入  参: *des:目的地址;*src:源地址;n:需要复制的内存长度
返回值:无
*/
void mymemcpy(void * des, void *src, u32 n)
{
    u8 * xdes = des;
    u8 * xsrc = src;
}
//5.设置内存
/*
入  参:*s:内存首地址; c:要设置的值;count:需要设置的内存的大小(字节为单位)
返回值:无
*/
void mymemset(void *s, u8 c, u32 count)
{
    u8 * xs = s;
    while(count--)
    {
        *xs++ = c;
    }
}
//6.内存管理初始化
/*
入  参:memx:所属内存块
返回值:无
*/
void 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
}
//7.获得内存使用率
/*
入  参:memx:所属内存块
返回值:使用率(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++;
    }
    return (used*100)/(memtblsize[memx]);
}
//8.内存分配(内部调用)
/*
入  参:memx:所属内存块;size:所要分配的内存的大小(字节)
返回值:0xffff ffff表示错误,其他,内存偏移地址
*/
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);    //未初始化,先执行初始化
    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++)  //标注内存块非空
            {
                malloc_dev.memmap[memx][offset + i] = nmemb;
            }
            return (offset * memblksize[memx]);    //返回偏移地址
        }
    }
    return 0xffffffff;    //未找到符合分配条件的内存块
}
//9.释放内存(内部调用)
/*
入  参:memx:所属内存块;offset:内存地址偏移
返回值:0:释放成功;1:释放失败
*/
u8 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;  //偏移超区了
    }
}
//10.释放内存(外部调用)
/*
入  参: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);    //释放内存
}
//11.分配内存(外部调用)
/*
入  参:memx:所属内存块;size:内存大小(字节)
返回值:分配到的内存首地址
*/
void * mymalloc(u8 memx, u32 size)
{
    u32 offset;
    offset = mem_malloc(memx, size);
    if(offset == 0xffffffff)
        return NULL;
    else
        return (void *)((u32)mallco_dev.membase[memx] + offset);
}
//12.重新分配内存(外部调用)
/*
入  参: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); //拷贝旧内存内容到新内存
        myfree(memx, ptr);                                                    //释放旧内存
        return (void*)((u32)mallco_dev.membase[memx] + offset);               //返回新内存首地址
    }
}
    这里,通过内存管理控制器mallco_dev结构体,实现对两个内存池的管理控制。
一个是内部SRAM内存池,定义为:
__align(32) u8 mem1base[MEM1_MAX_SIZE];
另一个是外部SRAM内存池,定义为:
__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0x68000000)));
    其中,MEM1_MAX_SIZE和MEM2_MAX_SIZE为内存池的大小,外部内存池指定地址为0x68000000,
即从外部SRAM首地址开始,内部内存则由编译器自动分配。__align(32)定义内存池为32字节对齐,以
适应各种不同场合的需求。
    此部分代码核心函数为:mem_malloc和mem_free,分别用于内存申请和释放。不过这两个函数都只是
内部调用,外部调用我们使用mymalloc和myfree两个函数。
    
     main.c文件代码如下:
int main(void)
{
    u8 key;
    u8 i = 0;
    u8 *p = 0;
    u8 *tp = 0;
    u8 paddr[18];     //存放paddr+p地址的ASCII值
    u8 sramx = 0;    //默认为内部sram
    delay_init();        //延时函数初始化
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //设置中断优先级分组为组2
    uart_init(115200);       //串口初始化为115200
    LED_init();
    KEY_init();
    LCD_init();
    FSMC_SRAM_Init();           //初始化外部SRAM
    my_mem_init(SRAMIN);     //初始化内部内存池
    my_mem_init(SRAMEX);    //初始化外部内存池
    while()
   {
       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 KEY2_PRES:        //KEY2按下
                myfree(sramx, p);        //释放内存
                p = 0;
                break;
            case WKUP_PRES:        //KEY_UP按下
                sramx = !sramx;           //切换当前malloc/free操作对象
                break;
       }
       if(tp != p)
       {
            tp = p;
            sprintf((char*)paddr, "paddr:0x%08x", (u32)tp);
       }
       delay_ms(10);
       i++;
       if((i%20) == 0)    //DS0闪烁
       {
           LED0 = !LED0;
       }
   }
}
    这部分代码比较简单,主要是对mymalloc和myfree的调用。不过要注意,申请的内存,在用完后
一定要释放。否则多次申请未释放,会造成“内存泄漏”,久而久之,导致无内存可用。
    另外本章希望利用USMART调用内存管理,所以在USMART里面添加mymalloc和myfree两个函数,
用于测试内存分配和内存释放。

42.4 下载验证

    在代码编译成功后,下载到开发板上,得到如图所示界面:
内存管理实验_STM32F1开发指南_第四十二章_第2张图片
     可以看到,所有的内存的使用率均为0%,说明还没有任何内存被使用,此时我们按下KEY0,
就可以看到内部SRAM内存被使用了5%了。同时看到下面提示指针p所指向的地址(其实也就是被
分配到的内存地址)和内容。多次按下KEY0,可以看到内存使用率持续上升(注意对比p的值,可以
发现是递减的,说明是从顶部开始分配内存的!),此时如果按下KEY2,可以发现内存使用率降低
了5%,但再次按下KEY2就不再降低了,说明“ 内存泄漏” 了。这就是前面提到的对一个指针多次
申请内存,而之前申请的内存又没有释放,导致的。
    按下KEY_UP键,可以切换当前的操作内存,KEY1键用于更新p的内容,更新后的内容将重新
显示在LCD模块上。
   
    本章还可以借助USMART,测试内存的分配和释放,如下图所示:
  内存管理实验_STM32F1开发指南_第四十二章_第3张图片








你可能感兴趣的:(应用-,stm32,stm32,内存管理)