打印

【连载】STM32开发指南--第四十三章 内存管理实验

[复制链接]
3077|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
正点原子|  楼主 | 2013-4-3 23:05 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
   第四十三章 内存管理实验
上一节,我们学会了使用STM32驱动外部SRAM,以扩展STM32的内存,加上STM32本身自带的64K字节内存,我们可供使用的内存还是比较多的。如果我们所用的内存都像上一节的testsram那样,定义一个数组来使用,显然不是一个好办法。
本章,我们将学习内存管理,实现对内存的动态管理。本章分为如下几个部分:
43.1 内存管理简介
43.2 硬件设计
43.3 软件设计
43.4 下载验证

43.1 内存管理简介
内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,他们其实最终都是要实现2个函数:malloc和free;malloc函数用于内存申请,free函数用于内存释放。
本章,我们介绍一种比较简单的办法来实现:分块式内存管理。下面我们介绍一下该方法的实现原理,如图43.1.1所示:


43.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个内存管理表项目的值都清零,标记释放,完成一次内存释放。
关于分块式内存管理的原理,我们就介绍到这里。

43.2 硬件设计
本章实验功能简介:开机后,显示提示信息,等待外部输入。KEY0用于申请内存,每次申请2K字节内存。KEY1用于写数据到申请到的内存里面。KEY2用于释放内存。WK_UP用于切换操作内存区(内部内存/外部内存)。DS0用于指示程序运行状态。本章我们还可以通过USMART调试,测试内存管理函数。
本实验用到的硬件资源有:
1)  指示灯DS0
2)  四个按键
3)  串口
4)  TFTLCD模块
5)  IS62WV51216
这些我们都已经介绍过,接下来我们开始软件设计。
43.3 软件设计
本章,我们将内存管理部分单独做一个分组,在工程目录下新建一个MALLOC的文件夹,然后新建malloc.c和malloc.h两个文件,将他们保存在MALLOC文件夹下。
在MDK新建一个MALLOC的组,然后将malloc.c文件加入到该组,并将MALLOC文件夹添加到头文件包含路径。
       打开malloc.c文件,输入如下代码:
#include "malloc.h"         
//内存池(4字节对齐)
__align(4) u8 mem1base[MEM1_MAX_SIZE]; //内部SRAM内存池                                 
__align(4) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000)));
//外部SRAM内存池
//内存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];  //内部SRAM内存池MAP                        u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+
MEM2_MAX_SIZE))); //外部SRAM内存池MAP
//内存管理参数        
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 mallco_dev=
{
       mem_init,                                   //内存初始化
       mem_perused,                             //内存使用率
       mem1base,mem2base,                  //内存池
       mem1mapbase,mem2mapbase,      //内存管理状态表
       0,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;  
}        
//内存管理初始化  
//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  
}  
//获取内存使用率
//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])used++;
    return (used*100)/(memtblsize[memx]);  
}  
//内存分配(内部调用)
//memx:所属内存块
//size:要分配的内存大小(字节)
//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址
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++)                              //标注内存块非空
            {  
                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); 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];  
    mem_free(memx,offset);//释放内存     
}  
//分配内存(外部调用)
//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);  
}  
//重新分配内存(外部调用)
//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结构体(mallco_dev结构体见malloc.h),实现对两个内存池的管理控制。一个是外部内存池,定义为:
       __align(4) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000)));                         另一个是内部内存池,定义为:
__align(4) u8 mem1base[MEM1_MAX_SIZE];   
       其中,MEM1_MAX_SIZE和MEM2_MAX_SIZE为在malloc.h里面定义的内存池大小,外部内存池指定地址为0X68000000,也就是从外部SRAM的首地址开始的,内部内存则由编译器自动分配。__align(4)定义内存池为4字节对齐,这个非常重要!如果不加这个限制,在某些情况下(比如分配内存给结构体指针),可能出现错误,所以一定要加上这个。
实验38 内存管理实验.rar (189.19 KB)

《STM32开发指南》第四十三章 内存管理实验.rar (574.29 KB)



沙发
正点原子|  楼主 | 2013-4-3 23:05 | 只看该作者
       此部分代码的核心函数为:mem_malloc和mem_free,分别用于内存申请和内存释放。思路就是我们在43.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 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里面定义
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,内存管理表相对就比较小了。
其他就不多说了,大家自行看代码理解就好。保存此部分代码。最后,打开test.c文件,修改代码如下:
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
       mem_init(SRAMIN);            //初始化内部内存池
       mem_init(SRAMEX);           //初始化外部内存池
      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,"ATOM@ALIENTEK");
       LCD_ShowString(60,110,200,16,16,"2012/9/16");  
       LCD_ShowString(60,130,200,16,16,"KEY0:Malloc  KEY2:Free");
       LCD_ShowString(60,150,200,16,16,"KEY_UP:SRAMx KEY1: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字节
                            if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);
//向p写入一些内容
                            break;
                     case KEY_DOWN: //KEY1按下     
                            if(p!=NULL)
                            {
                                   sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容      
                                   LCD_ShowString(60,250,200,16,16,p);                     //显示P的内容
                            }
                            break;
                     case KEY_LEFT:   //KEY2按下   
                            myfree(sramx,p);//释放内存
                            p=0;               //指向空地址
                            break;
                     case KEY_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)
              {
                     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+96,190,mem_perused(SRAMIN),3,16);
//显示内部内存使用率
                     LCD_ShowNum(60+96,210,mem_perused(SRAMEX),3,16);
//显示外部内存使用率
                    LED0=!LED0;
             }
       }        
}
       该部分代码比较简单,主要是对mymalloc和myfree的应用。不过这里提醒大家,如果对一个指针进行多次内存申请,而之前的申请又没释放,那么将造成“内存泄露”,这是内存管理所不希望发生的,久而久之,可能导致无内存可用的情况!所以,在使用的时候,请大家一定记得,申请的内存在用完以后,一定要释放。
       另外,本章希望利用USMART调试内存管理,所以在USMART里面添加了mymalloc和myfree两个函数,用于测试内存分配和内存释放。大家可以通过USMART自行测试。
43.4 下载验证
在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32开发板上,得到如图43.4.1所示界面:


43.4.1 程序运行效果图
可以看到,内外内存的使用率均为0%,说明还没有任何内存被使用,此时我们按下KEY0,就可以看到内部内存被使用5%了,同时看到下面提示了指针p所指向的地址(其实就是被分配到的内存地址)和内容。多按几次KEY0,可以看到内存使用率持续上升(注意对比p的值,可以发现是递减的,说明是从顶部开始分配内存!),此时如果按下KEY2,可以发现内存使用率降低了5%,但是再按KEY2将不再降低,说明“内存泄露”了。这就是前面提到的对一个指针多次申请内存,而之前申请的内存又没释放,导致的“内存泄露”。
       按KEY_UP按键,可以切换当前操作内存(内部内存/外部内存),KEY1键用于更新p的内容,更新后的内容将重新显示在LCD模块上面。
       本章,我们还可以借助USMART,测试内存的分配和释放,有兴趣的朋友可以动手试试。如图43.4.2所示:


43.4.2 USMART测试内存管理函数

使用特权

评论回复
板凳
cjhk| | 2013-4-4 19:01 | 只看该作者
资料很不错  需要顶起来  呵呵  很不错  楼主   谢谢你的共享   谢谢了   呵呵

使用特权

评论回复
地板
cjhk| | 2013-4-4 19:02 | 只看该作者
楼主   呵呵  这个帖子需要加精   希望版主能看到   呵呵   顶起来

使用特权

评论回复
5
正点原子|  楼主 | 2013-4-4 22:57 | 只看该作者
cjhk 发表于 2013-4-4 19:02
楼主   呵呵  这个帖子需要加精   希望版主能看到   呵呵   顶起来

谢谢支持

使用特权

评论回复
6
longfenghugui| | 2014-8-21 15:25 | 只看该作者

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:我的STM32开发板店铺:http://openedv.taobao.com 我的技术论坛论坛:www.openedv.com

91

主题

264

帖子

71

粉丝