单片机内存管理
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、任务栈的管理
任务栈需要结合具体的操作系统,说明会更有效果。