单片机内存管理

1、随机存储器

RAM是随机存储器,读写速度快,但掉电以后数据会丢失。它分为SRAM(静态RAM)和DRAM(动态RAM)。SRAM无需刷新就可以保存数据;DRAM需要不断刷新才可以保存数据。在CPU内部的RAM,就叫内部RAM,在CPU外部的RAM,就叫外部RAM。单片机应用中,一般很少扩展外部RAM。

2、单片机内存由哪几部分构成

单片机内存位于RAM中,它被分成四个区:静态存储区、栈区、堆区和未用区。

静态存储区用来存放全局变量和static型变量,它在程序编译的时候就已经被分配好了。

用来保护现场和恢复现场的栈很多,主要有:函数栈和任务栈。在使用操作系统时,我们会申请一块数组用作任务栈,任务栈位于静态存储区,其实函数栈也位于静态存储区,只是我们硬要把他们分开叫而已。严格来讲,内存只有堆区和静态存储区两种。

栈区和堆区的大小由软件工程师设置。例如在STM32F103的启动文件中,有两个宏定义:Stack_Size和Heap_Size,其中Stack_Size用来设置栈区的大小,Heap_Size用来设置

堆区的大小,它们在startup_stm32f10x_hd.s文件中。格式如下:

Stack_Size   EQU     0x00000400

Heap_Size   EQU     0x00000200

注意:如果没有使用标准库的malloc,这里Heap_Size可以设置为0。例如:在使用其他操作系统或自定义的malloc.c文件时,都需要定义一个数组用作堆区,用作内存申请和释放。

3、堆区管理

下面主要介绍我的malloc.c文件,了解是如何进行堆管理。在单片机应用中,一般很少扩展外部RAM,通常会根据实际情况,选择合适的CPU来满足设计需求。堆的生长方向,本程序是向下的。

//定义两个内存池

#define InternalRAM          0    //内部堆区内存池

#define ExternalRAM       1    //外部堆区内存池

#define RAMBankNumber   2    //堆区种类为2

/内部堆定义开始/

#define InternalRAM_BlockSize    32

//定义内部堆区最小的数据块为32字节对齐

#define InternalRAM_TotalNumberOfBytes  30*1024  //定义内部堆区的大小为30K

#define InternalRAM_NumberOfBlock   InternalRAM_TotalNumberOfBytes/InternalRAM_BlockSize

//定义内部堆区中块的总数量为1280个

__align(32) u8 InternalRAM_Array[InternalRAM_TotalNumberOfBytes];

//定义内部堆区内存池首地址为InternalRAM_Array,共分配InternalRAM_TotalNumberOfBytes个字节空间,并指定按照32位对齐。

u16 InternalRAM_MemoryStatusTableArray[InternalRAM_NumberOfBlock ];

//内部堆区的”内存状态表”

//InternalRAM_MemoryStatusTableArray[i]=0表示块i是空闲的,可以使用;

/内部堆定义结束/

/外部堆定义开始/

#define ExternalRAM_BlockSize  InternalRAM_BlockSize

//定义外部堆区最小的数据块为32字节对齐,保证内部堆和外部堆的大小相同

#define ExternalRAM_TotalNumberOfBytes  960 *1024 //定义外部堆区的大小为1M

#define ExternalRAM_NumberOfBlock   ExternalRAM_TotalNumberOfBytes/ExternalRAM_BlockSize

//定义外部SRAM中块的总数量为30720个

__align(32) u8 ExternalRAM_Array[ExternalRAM_TotalNumberOfBytes]  __attribute__((at(0X68000000)));

//定义外部堆区的内存池首地址为ExternalRAM_Array,共分配ExternalRAM_TotalNumberOfBytes个字节空间,并指定按照32位对齐,进行数据访问

//定义外部内存内存池的物理首地址为0X68000000

u16 ExternalRAM_MemoryStatusTableArray[ExternalRAM_NumberOfBlock] __attribute__((at(0X68000000+ ExternalRAM_TotalNumberOfBytes)));

//外部堆区的”内存状态表”,其首地址为0X68000000+ ExternalRAM_TotalNumberOfBytes

// ExternalRAM_MemoryStatusTableArray[i]=0表示块i是空闲的,可以使用;

/外部堆定义结束/

struct  _m_mallco_dev

{//内存管理控制器

  void (*init)(u8);             //初始化函数

  u8   (*perused)(u8);        //内存使用率统计函数

  u8   *membase[RAMBankNumber];

//指针数组用来存放内存池数组首地址,内存池数量为RAMBankNumber个

  u16  *memmap[RAMBankNumber];

//指针数组用来存放内存管理状态表, 内存管理状态表数量为RAMBankNumber个

  u8  memrdy[RAMBankNumber];   //内存管理是否就绪

};

//extern struct  _m_mallco_dev   mallco_dev;

//内存管理参数

const u32 NumberOfMyBlock[RAMBankNumber]={ InternalRAM_NumberOfBlock, ExternalRAM_NumberOfBlock };

//记录内部堆和外部堆中分别含有块的总数量

const u32 MyBlockSize[RAMBankNumber]={ InternalRAM_BlockSize, ExternalRAM_BlockSize };

//记录内部堆和外部堆中的块的大小

const u32 MyTotalNumberOfBytes[RAMBankNumber]={ InternalRAM_TotalNumberOfBytes, ExternalRAM_TotalNumberOfBytes };

//记录内部堆和外部堆的内存池大小

//函数功能:将src为首地址的存储块,复制前n个字节到des为首地址的存储块中

//*des:目的地址

//*src:源地址

//n:需要复制的内存长度(字节为单位)

void MyMemoryCopy(void *des,void *src,u32 n)

{

  u8 *xdes=des;

  u8 *xsrc=src;

  while(n–) *xdes++=*xsrc++;

}

//函数功能:将s为首地址的存储块的前count个字节全部设置为c的值

//*s:内存首地址

//c :要设置的值

//count:需要设置的内存大小(字节为单位)

void MyMemorySet(void *s,u8 c,u32 count)

{

  u8 *xs = s;

  while(count–) *xs++=c;

}

//函数功能: 清除”内存状态表”和堆区的内存池

//当memx= InternalRAM,初始化内部堆区;当memx= ExternalRAM,初始化外部堆区

void MyMemoryInit(u8 memx)

{

  MyMemorySet( mallco_dev.memmap[memx], 0, NumberOfMyBlock[memx]*2 );

         //将堆区的”内存状态表”数组清零,NumberOfMyBlock[]表示块的总数量

//InternalRAM_MemoryStatusTableArray[]和ExternalRAM_MemoryStatusTableArray[]存储的是双字节

//NumberOfMyBlock[memx]也就是双字节,所以这里乘以2;

  MyMemorySet( mallco_dev.membase[memx], 0, MyTotalNumberOfBytes[memx] );

         //将堆区的内存池所有数据清零

// InternalRAM_Array[]和ExternalRAM_Array[]存储的是单字节,

//mallco_dev.membase[memx]也就是单字节存储区

  mallco_dev.memrdy[memx]=1; //内存管理初始化OK

}

//函数功能:

//当memx= InternalRAM,计算内部堆区的使用率;

//当memx= ExternalRAM,计算外部堆区的使用率

//返回值:使用率(0%~100%)

u8 Get_MyMemoryUsed(u8 memx)

{

  u32 used=0;

  u32 i;

  for(i=0;i<NumberOfMyBlock[memx];i++)

  {

if( mallco_dev.memmap[memx][i] ) used++;

//计算堆区内存池中空闲块的数量,

//堆区内存池中”块的总数量”保存在NumberOfMyBlock[memx]中

//mallco_dev.memmap[0][y]=0表示内部堆的块y是空闲的

//mallco_dev.memmap[1][y]=0表示外部堆的块y是空闲的

  }

  return (used*100)/(NumberOfMyBlock[memx]);

}

//内存管理控制器,创建结构变量时,就开始初始化一次

struct _m_mallco_dev   mallco_dev=

{

         MyMemoryInit,       //调用MyMemoryInit(),内存初始化

         Get_MyMemoryUsed,   //调用Get_MyMemoryUsed(),统计内存使用率

         InternalRAM_Array,  //传入InternalRAM_Array[]数组首地址

ExternalRAM_Array,  //传入ExternalRAM_Array[]数组首地址

         InternalRAM_MemoryStatusTableArray,

//传入InternalRAM_MemoryStatusTableArray[]数组首地址

ExternalRAM_MemoryStatusTableArray,    

//传入ExternalRAM_MemoryStatusTableArray[]数组首地址

         0,                //内部堆区管理未就绪

0                //外部堆区管理未就绪

};

//函数功能:

//当memx= InternalRAM,从内部堆区内存池中分配size个字节;

//当memx= ExternalRAM,从外部堆区内存池中分配size个字节;

//size:要分配的内存大小(字节)

//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址

u32 MyMemoryMalloc(u8 memx,u32 size)

{

  signed long offset=0;

  u32 nmemb;    //需要的内存块数

  u32 cmemb=0;  //连续空内存块数

  u32 i;

  if( !mallco_dev.memrdy[memx] )

mallco_dev.init(memx); //未初始化,先执行初始化

  if( size==0 ) return 0XFFFFFFFF;//不需要分配

nmemb=size/MyBlockSize[memx];

//计算需要多少个整块, MyBlockSize[memx]为块的大小

  if( size%MyBlockSize[memx] ) nmemb++;

//剩余的字节空间不满一个块的,则按一个整块操作

///MyBlockSize[memx]为块的大小,NumberOfMyBlock[memx]为块的总数量

  for( offset=NumberOfMyBlock[memx]-1;offset>=0;offset– )//搜索内存池,offset为块的号码

  {//堆区按照向下生成方式分配空间, NumberOfMyBlock[]表示块的总数量

if( !mallco_dev.memmap[memx][offset] ) cmemb++;//若块空间空闲,则cmemb加1

         else cmemb=0; //若连续空闲块数量小于nmemb,则cmemb=0

    if(cmemb==nmemb) //找到了nmemb个连续空闲块

    {

      for(i=0;i<nmemb;i++) //标注内存块非空

      {

        mallco_dev.memmap[memx][offset+i]=nmemb;

//将”nmemb个连续空闲块”对应的”内存状态表”设置为nmemb

//连续的”内存状态表”的值相同,表示为同批次分配

      }

      return ( offset*MyBlockSize[memx] );//返回分配到的内存池偏移地址

    }

  }

  return 0XFFFFFFFF;//未找到符合分配条件的内存块

}

//函数功能:释放内存(内部调用)

//当memx= InternalRAM,从内部堆区内存池中释放偏移地址为offset的数据块;

//当memx= ExternalRAM,从外部堆区内存池中释放偏移地址为offset的数据块;

//offset:内存地址偏移

//返回值:0,释放成功;1,释放失败;

u8 MyMemoryFree(u8 memx,u32 offset)

{

  int i;

  if( !mallco_dev.memrdy[memx] )//未初始化,先执行初始化

  {

    mallco_dev.init(memx);//初始化内存池

    return 1;//未初始化

  }

  if(offset< MyTotalNumberOfBytes[memx])//偏移在内存池内

  {

    int index=offset/MyBlockSize[memx]; //计算偏移地址offset在内存块中的号码

    int nmemb=mallco_dev.memmap[memx][index]; //读取要释放的块数量

    for(i=0;i<nmemb;i++)

    {

      mallco_dev.memmap[memx][index+i]=0;

//将”nmemb个连续空闲块”对应的”内存状态表”设置为0

    }

    return 0;//释放内存成功

  }

  else return 2;//偏移超区了

}

//函数功能:分配内存(外部调用)

//当memx= InternalRAM,从内部堆区内存池中分配size个字节;

//当memx= ExternalRAM,从外部堆区内存池中分配size个字节;

//返回值:分配到的内存首地址.

void * MyMalloc(u8 memx,u32 size)

{

  u32 offset;

  offset= MyMemoryMalloc(memx,size);//读分配size个字节空间的偏移地址

  if(offset==0XFFFFFFFF)//没有分配到数据块

 return NULL;

  else //分配到数据

return (void*)((u32)mallco_dev.membase[memx]+offset);

//返回分配到的数据块首地址

}

//函数功能:释放内存(外部调用)

//当memx= InternalRAM,从内部堆区内存池中释放首移地址为ptr的字节空间;

//当memx= ExternalRAM,从外部堆区内存池中释放首移地址为ptr的字节空间;

//释放的块数为在内存状态表中

//ptr:内存首地址

void MyFree(u8 memx,void *ptr)

{

  u32 offset;

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

  offset=(u32)ptr-(u32)mallco_dev.membase[memx];

  //计算偏移地址

  MyMemoryFree(memx,offset);//释放内存

}

//函数功能:

//将ptr为首地址的前size个字节拷贝到新分配的内存中,再释放ptr为首地址的内存

//当memx= InternalRAM,从内部堆区内存池中分配size个字节;

//当memx= ExternalRAM,从外部堆区内存池中分配size个字节;

//size:要分配的内存大小(字节)

//返回值:新分配到的内存首地址

void *MyMalloc_CopyOldData_And_FreeOldDataBlock(u8 memx,void *ptr,u32 size)

{

  u32 offset;

  offset=MyMemoryMalloc(memx,size);//读分配size个字节空间的偏移地址

  if(offset==0XFFFFFFFF)

return NULL;

  else

  {

MyMemoryCopy( (void*)((u32)mallco_dev.membase[memx]+offset),ptr,size );

//拷贝旧内存内容到新内存

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

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

  }

}

//函数功能:动态分配内存,

//注意:为了和FreeRTOS兼容定义为pvPortMalloc(u32 size)

/*

在使用FreeRTOS系统时,最好用它自带的内存管理heap_4.c,系统所有总的堆大小,体现在ZI-DATA中

在FreeRTOSConfig.h中,有个宏定义:#define configTOTAL_HEAP_SIZE   ((size_t)(33*1024))

*/

void *pvPortMalloc(u32 size)                     

{

         return (void*)MyMalloc(InternalRAM,size);

}

//函数功能:释放内部RAM的内存

//注意:为了和FreeRTOS兼容定义为vPortFree()

//在使用FreeRTOS系统时,最好用它自带的内存管理heap_4.c

/*

在使用FreeRTOS系统时,最好用它自带的内存管理heap_4.c,系统所有总的堆大小,体现在ZI-DATA中

在FreeRTOSConfig.h中,有个宏定义:#define configTOTAL_HEAP_SIZE   ((size_t)(33*1024))

*/

void vPortFree(void* mf)

{

         myfree(InternalRAM,mf);

}

u8 Myused;

int main(void)

{

         u8 *p;

         MyMemoryInit(InternalRAM); //初始化内部内存池

         Myused=Get_MyMemoryUsed(InternalRAM);//读内存使用率

         p=pvPortMalloc(15*1024);

         Myused=Get_MyMemoryUsed(InternalRAM);//读内存使用率

         vPortFree(p);

         Myused=Get_MyMemoryUsed(InternalRAM);//读内存使用率

         while(1)

         {

         }

}

4、函数栈的管理

函数栈也中断栈,或叫硬件栈,书本上没有严格的定义。栈区用来存放局部变量和一些寄存器数据。局部变量在函数内部,其存储空间位于栈中。当进入函数时,会对根据局部变量需求,在栈上申请一段内存空间,供局部变量使用。当局部变量生命周期结束后,在栈上释放。在C语言程序中,是由编译器系统完成申请和释放的。CPU栈的增长方向,通常是向下的。

在C语言程序中,“函数A”调用“函数B”时,需要将一些寄存器数据和“函数A”中的局部变量压入到“指定的RAM”中(入栈代码是由编译器系统完成的),这叫现场保护;接着再执行“函数B”;“函数B”被执行完毕后再回到“函数A”,此时需要将从“指定的RAM”中把“以前压入栈中的数据”返回给寄存器和“函数A”中的局部变量(出栈代码是由编译器系统完成的),叫恢复现场。这个“指定的RAM”,叫“函数栈”或“中断栈”。在C语言中,入栈和出栈是由编译器系统自动完成的,但在汇编语言中,入栈和出栈代码就需要人工完成。见下图:

5、任务栈的管理

任务栈需要结合具体的操作系统,说明会更有效果。

物联沃分享整理
物联沃-IOTWORD物联网 » 单片机内存管理详解

发表评论