动态内存管理


  • 根据需要分配內存和回收内存
    通常在一块较大且连续的内存空间上进行分配和回收
  • 动态内存管理解决的问题
    内存资源稀缺,通过内存复用增加任务的并发性
  • 动态内存管理的本质
    时间换空间,通过动态分配和回收“扩大”物理内存

动态内存管理的关键


  • 时间效率
    从发出内存申请到得内存的时间越短越好
  • 空间效率
    为了管理内存而占用的内存越少越好
  • 碎片化
    最大可分配内存占空闲内存总和的比例越大越好

动态内存管理的分类



定长内存管理
将内存分为大小相同的单元,每次申请一个单元的内存



变长内存管理
每次申请需要的内存(大小不固定,以字节为单位)



定长内存管理的设计与实现
将内存分为两部分:管理单元&分配单元
管理单元与分配单元——对应



uint8_t mem2base[960*1024] __attribute__((at(0X68000000)));

表示在外部SRAM内存区域定义一个数组,这里使用的是FSMC的BANK1的NE3,对应的外部地址就是0X68000000。不明白的去看一看STM32的FSMC章节。

//内存池(32字节对齐)   
__align(32) uint8_t mem1base[100*1024]; //内部SRAM内存池 100K
__align(32) uint8_t mem2base[960*1024] __attribute__((at(0X68000000))); //外部SRAM内存池 960K
__align(32) uint8_t mem3base[60 *1024] __attribute__((at(0X10000000))); //内部CCM内存池 60K

//内存管理表
uint16_t mem1mapbase[100*1024/32]; //内部SRAM内存池MAP
uint16_t mem2mapbase[960*1024/32] __attribute__((at(0X68000000+960*1024))); //外部SRAM内存池MAP
uint16_t mem3mapbase[60 *1024/32] __attribute__((at(0X10000000+60*1024))); //内部CCM内存池MAP

//内存管理参数
const uint32_t memtblsize[SRAMBANK]={100*1024/32,960*1024/32,60 *1024/32}; //内存表大小
const uint32_t memblksize[SRAMBANK]={32,32,32}; //内存分块大小
const uint32_t memsize [SRAMBANK]={100*1024,960*1024,60*1024}; //内存总大小

malloc.c

#include "malloc.h"    


//内存管理就绪
#define MEMRDY 1
#define MEMBSY 0

//内存池(32字节对齐)
__align(32) uint8_t mem1base[100*1024]; //内部SRAM内存池 100K
__align(32) uint8_t mem2base[960*1024] __attribute__((at(0X68000000))); //外部SRAM内存池 960K
__align(32) uint8_t mem3base[60 *1024] __attribute__((at(0X10000000))); //内部CCM内存池 60K

//内存管理表 32字节为单位
uint16_t mem1mapbase[100*1024/32]; //内部SRAM内存池MAP
uint16_t mem2mapbase[960*1024/32] __attribute__((at(0X68000000+960*1024))); //外部SRAM内存池MAP
uint16_t mem3mapbase[60 *1024/32] __attribute__((at(0X10000000+60*1024))); //内部CCM内存池MAP

//内存管理参数
const uint32_t memtblsize[SRAMBANK]={100*1024/32,960*1024/32,60 *1024/32}; //内存表大小
const uint32_t memblksize[SRAMBANK]={32,32,32}; //内存分块大小
const uint32_t memsize [SRAMBANK]={100*1024,960*1024,60*1024}; //内存总大小

//内存管理控制器
MALLOC_EDV_T g_mallcoDev =
{
my_mem_init, //内存初始化
my_mem_perused, //内存使用率
mem1base, mem2base, mem3base, //内存池
mem1mapbase, mem2mapbase, mem3mapbase, //内存管理状态表
MEMBSY, MEMBSY, MEMBSY, //内存管理未就绪
};


/************************************************************************
** 函数名称: mymemcpy
** 函数功能: 复制内存
** 入口参数: void *des:目的地址
** void *src:原地址
** uint32_t n: 需要复制的内存长度(以字节为单位)
** 出口参数:
************************************************************************/
void mymemcpy(void *des,void *src,uint32_t n)
{
uint8_t *xdes=des;
uint8_t *xsrc=src;
while(n--)
*xdes++=*xsrc++;
}


/************************************************************************
** 函数名称: mymemset
** 函数功能: 设置内存
** 入口参数: void *s:内存首地址
** uint8_t c:要设置的值
** uint32_t count: 需要设置的内存长度(以字节为单位)
** 出口参数:
************************************************************************/
void mymemset(void *s,uint8_t c,uint32_t count)
{
uint8_t *xs = s;
while(count--)
*xs++=c;
}


/************************************************************************
** 函数名称: my_mem_init
** 函数功能: 内存管理初始化
** 入口参数: uint8_t memx:所属内存块
** 出口参数:
************************************************************************/
void my_mem_init(uint8_t memx)
{
//清除内存状态表-每一个内存块对应状态表中的一项
//因为内存管理状态表示16位的,所以需要乘以2
mymemset( g_mallcoDev.memmap[memx], 0, memtblsize[memx]*2);
//把所管理的内存池全部清理
mymemset(g_mallcoDev.membase[memx], 0, memsize[memx]);
//表明对应的内存管理已经就绪
g_mallcoDev.memrdy[memx] = MEMRDY;
}

/************************************************************************
** 函数名称: my_mem_perused
** 函数功能: 计算内存使用率
** 入口参数: uint8_t memx:所属内存块
** 出口参数: 使用率(0~100)
************************************************************************/
uint8_t my_mem_perused(uint8_t memx)
{
uint32_t used=0;
uint32_t i;

//逐一对每个表项进行统计
for(i=0;i<memtblsize[memx];i++)
{
//如果表项值不为0就表示对应内存块被使用了
if(g_mallcoDev.memmap[memx][i])
used++;
}

//计算百分比
return (used*100)/(memtblsize[memx]);
}

/************************************************************************
** 函数名称: my_mem_malloc
** 函数功能: 内存分配(内部调用)
** 入口参数: uint8_t memx:所属内存块
** uint32_t size:要分配的内存大小(字节)
** 出口参数: 0xFFFFFFFF-分配错误 其他-内存偏移地址
************************************************************************/
uint32_t my_mem_malloc(uint8_t memx,uint32_t size)
{
signed long offset=0;
uint32_t nmemb; //需要的内存块数
uint32_t cmemb = 0;//连续空内存块数
uint32_t i;

//未初始化,先执行初始化
if(g_mallcoDev.memrdy[memx] == MEMBSY)
g_mallcoDev.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(g_mallcoDev.memmap[memx][offset] == 0)
{
//连续空内存块数增加
cmemb++;
}
else
{
//连续内存块清零--只要找到一个被使用的就表示不连续了,要重新开始找
cmemb=0;
}

//找到了连续nmemb个空内存块
if(cmemb == nmemb)
{
//标注内存块非空--并且是连续nmemb个非空块
for(i=0;i<nmemb;i++)
{
g_mallcoDev.memmap[memx][offset+i] = nmemb;
}
//返回偏移地址
return (offset*memblksize[memx]);
}
}
//未找到符合分配条件的内存块
return 0xFFFFFFFF;
}

/************************************************************************
** 函数名称: my_mem_free
** 函数功能: 释放内存(内部调用)
** 入口参数: uint8_t memx:所属内存块
** uint32_t offset:内存地址偏移
** 出口参数: 0-释放成功;1-释放失败
************************************************************************/
uint8_t my_mem_free(uint8_t memx,uint32_t offset)
{
int i;

//未初始化,先执行初始化
if(g_mallcoDev.memrdy[memx] == MEMBSY)
{
g_mallcoDev.init(memx);
return 1;//未初始化
}

//偏移在内存池内.
if(offset < memsize[memx])
{
int index = offset/memblksize[memx]; //偏移所在内存块号码
int nmemb = g_mallcoDev.memmap[memx][index]; //内存块数量

for(i=0;i<nmemb;i++) //内存块清零
{
g_mallcoDev.memmap[memx][index+i] = 0;
}
return 0;
}
else
return 2;//偏移超区了.
}

/************************************************************************
** 函数名称: myfree
** 函数功能: 释放内存(外部调用)
** 入口参数: uint8_t memx:所属内存块
** void *ptr:内存地址偏移
** 出口参数: 无
************************************************************************/
void myfree(uint8_t memx,void *ptr)
{
uint32_t offset;

//地址为0.
if(ptr==NULL)
return;

//计算指针在相应内存块中的偏移量
offset = (uint32_t)ptr-(uint32_t)g_mallcoDev.membase[memx];

//释放内存
my_mem_free(memx,offset);
}

/************************************************************************
** 函数名称: mymalloc
** 函数功能: 内存分配(内部调用)
** 入口参数: uint8_t memx:所属内存块
** uint32_t size:要分配的内存大小(字节)
** 出口参数: 分配到的内存首地址.
************************************************************************/
void *mymalloc(uint8_t memx,uint32_t size)
{
uint32_t offset;

offset=my_mem_malloc(memx,size);

if(offset == 0xFFFFFFFF)
return NULL;
else
return (void*)((uint32_t)g_mallcoDev.membase[memx]+offset);
}

/************************************************************************
** 函数名称: myrealloc
** 函数功能: 重新分配内存(外部调用)
** 入口参数: uint8_t memx:所属内存块
** void *ptr:旧内存首地址
** uint32_t size:要分配的内存大小(字节)
** 出口参数: 新分配到的内存首地址.
************************************************************************/
void *myrealloc(uint8_t memx,void *ptr,uint32_t size)
{
uint32_t offset;

//先申请一段内存
offset = my_mem_malloc(memx,size);

//申请失败
if(offset == 0xFFFFFFFF)
return NULL;
else
{
//拷贝旧内存内容到新内存
mymemcpy((void*)((uint32_t)g_mallcoDev.membase[memx]+offset),ptr,size);

//释放旧内存
myfree(memx,ptr);

//返回新内存首地址
return (void*)((uint32_t)g_mallcoDev.membase[memx]+offset);
}
}

malloc.h

#ifndef __MALLOC_H
#define __MALLOC_H

#include "main.h"

#ifndef NULL
#define NULL 0
#endif

//定义三个内存池
#define SRAMIN 0 //内部内存池
#define SRAMEX 1 //外部内存池
#define SRAMCCM 2 //CCM内存池(此部分SRAM仅仅CPU可以访问!!!)
#define SRAMBANK 3 //定义支持的SRAM块数.

//内存管理控制器
typedef struct
{
void (*init)(uint8_t); //初始化
uint8_t (*perused)(uint8_t); //内存使用率
uint8_t *membase[SRAMBANK]; //内存池 管理SRAMBANK个区域的内存
uint16_t *memmap[SRAMBANK]; //内存管理状态表
uint8_t memrdy[SRAMBANK]; //内存管理是否就绪
}MALLOC_EDV_T;

extern MALLOC_EDV_T g_mallcoDev; //在mallco.c里面定义
/************************************************************************
** 函数名称: mymemcpy
** 函数功能: 复制内存
** 入口参数: void *des:目的地址
** void *src:原地址
** uint32_t n: 需要复制的内存长度(以字节为单位)
** 出口参数:
************************************************************************/
void mymemcpy(void *des, void *src, uint32_t n);

/************************************************************************
** 函数名称: mymemset
** 函数功能: 设置内存
** 入口参数: void *s:内存首地址
** uint8_t c:要设置的值
** uint32_t count: 需要设置的内存长度(以字节为单位)
** 出口参数:
************************************************************************/
void mymemset(void *s, uint8_t c, uint32_t count);

/************************************************************************
** 函数名称: my_mem_init
** 函数功能: 内存管理初始化
** 入口参数: uint8_t memx:所属内存块
** 出口参数:
************************************************************************/
void my_mem_init(uint8_t memx);

/************************************************************************
** 函数名称: my_mem_perused
** 函数功能: 计算内存使用率
** 入口参数: uint8_t memx:所属内存块
** 出口参数: 使用率(0~100)
************************************************************************/
uint8_t my_mem_perused(uint8_t memx);

/************************************************************************
** 函数名称: my_mem_malloc
** 函数功能: 内存分配(内部调用)
** 入口参数: uint8_t memx:所属内存块
** uint32_t size:要分配的内存大小(字节)
** 出口参数: 0xFFFFFFFF-分配错误 其他-内存偏移地址
************************************************************************/
uint32_t my_mem_malloc(uint8_t memx, uint32_t size);

/************************************************************************
** 函数名称: my_mem_free
** 函数功能: 释放内存(内部调用)
** 入口参数: uint8_t memx:所属内存块
** uint32_t offset:内存地址偏移
** 出口参数: 0-释放成功;1-释放失败
************************************************************************/
uint8_t my_mem_free(uint8_t memx, uint32_t offset);

/************************************************************************
** 函数名称: myfree
** 函数功能: 释放内存(外部调用)
** 入口参数: uint8_t memx:所属内存块
** void *ptr:内存地址偏移
** 出口参数: 无
************************************************************************/
void myfree(uint8_t memx, void *ptr);

/************************************************************************
** 函数名称: mymalloc
** 函数功能: 内存分配(内部调用)
** 入口参数: uint8_t memx:所属内存块
** uint32_t size:要分配的内存大小(字节)
** 出口参数: 分配到的内存首地址.
************************************************************************/
void *mymalloc(uint8_t memx, uint32_t size);

/************************************************************************
** 函数名称: myrealloc
** 函数功能: 重新分配内存(外部调用)
** 入口参数: uint8_t memx:所属内存块
** void *ptr:旧内存首地址
** uint32_t size:要分配的内存大小(字节)
** 出口参数: 新分配到的内存首地址.
************************************************************************/
void *myrealloc(uint8_t memx, void *ptr, uint32_t size);

#endif

用法:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "usmart.h"
#include "sram.h"
#include "malloc.h"


int main(void)
{
u8 key;
u8 i=0;
u8 *p=0;

HAL_Init(); //初始化HAL库
Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz
delay_init(168); //初始化延时函数

KEY_Init(); //初始化KEY
SRAM_Init(); //初始化外部SRAM
my_mem_init(SRAMEX); //初始化外部内存池
while(1)
{
key=KEY_Scan(0);//不支持连按
switch(key)
{
case 0://没有按键按下
break;
case KEY0_PRES: //KEY0按下
p=mymalloc(SRAMEX,10240);//申请10K字节
break;
case KEY1_PRES: //KEY1按下
if(p!=NULL)
{
sprintf((char*)p,"%d",i);//更新显示内容
LCD_ShowString(30,270,200,16,16,p); //显示P的内容
}
break;
case KEY2_PRES: //KEY2按下
myfree(SRAMEX,p);//释放内存
p=0; //指向空地址
break;
}
}