单片机简单内存管理

前言

老生常谈的问题。单片机那么点RAM用得着内存管理吗?我可以回答,99%的情况都用不到,但是有些技术就是为了那1%存在的。

实现内存管理的原理

一个100间房的宾馆年接客30000人。实现的原理就是内存管理的原理。3万个客人不会同时订房,同样应用程序一般也不会同时使用内存。客人退房后打扫干净住下一个客人,同样一个程序使用完一段内存,回收再分配给另一个程序。
宾馆要给客人开房,就要知道那间房空着,同要分配内存就要知道哪个内存还没被使用。开好房后要把钥匙给客人,同理分配好内存要把内存的地址告诉应用程序。

基于状态表的内存管理

要实现内存管理,最核心的问题就是要知道哪些内存空着,哪些已经被分配。
我们可以把可分配的连续内存均匀划分成很多份,每一份叫做内存块。然后再建一个表,表里的每个元素一一对应内存块,标记没个内存块是否被分配。比如某个内存块被分配了,就往表里对应的元素写1,释放了就写回0表示内存空闲
在这里插入图片描述

单单只是标记了分配还是未分配还是有问题,因为应用程序不一定就只用到1个内存快的内存。如上图APP1data用到2个内存块,但是从内存表上只能看出前3个连续的内存块被分配,但是没法看出是一起分配给了同一个应用,还是分配给不同的应用。那么要释放内存时,我们就不知道要释放几个内存块。那么内存状态表的元素需要记录更多的信息才行。
内存分配出去后,应用程序唯一会保留的信息就是分配的内存首地址。释放内存也是靠这个地址。要释放肯定就要操作状态表,首先 (分配出去的地址 - 内存池首地址)÷ 内存块大小 = 分配出去的内存是第几个内存块开始。 我们也就知道了状态标志是第几个开始。定位了状态标志,那么就是要取得为程序分配了的内存大小或内存块数量,这个就是要记录的第二个信息。
那么状态表的标志改为0是空,非0是已分配,并且这个非0的数就是该程序分配的内存快大小。如下图,由APPdata1的首地址可以定位到状态表元素是第1个元素(0开始计数),第1个元素的值是2代表APPdata1分配了2个内存块。同理APPdata2也是2个内存块。至于状态表的第2个和第4个元素也是2,只是因为要写非0值区分空闲内存,因此也直接写等于分配了的内存块数,其实写任何非0值都可以。
在这里插入图片描述

程序实现

#define  MEM_BLOCK_SIZE    128//每个内存块字节
#define  MEM_MAX_SIZE  32*1024  //内存池总大小 32K字节 要是内存块的整数倍
//内存池(32字节对齐)
__align(32) u8 membase[MEM_MAX_SIZE];	//内存池数组 4字节对齐	
u16 memmapbase[MEM_MAX_SIZE/MEM_BLOCK_SIZE];	//内存状态表											

//设置内存
//*s:内存首地址
//c :要设置的值
//count:需要设置的内存大小(字节为单位)
void mymemset(void *s,u8 c,u32 count)  
{  
    u8 *xs = s;  
    while(count--)*xs++=c;  
}	   
//内存管理初始化  
//memx:所属内存块
void my_mem_init(u8 memx)  
{  
    mymemset(membase, 0,MEM_MAX_SIZE  );//内存状态表数据清零							
}  

//memx:所属内存块
//size:要分配的内存大小(字节)
//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址 
u32 my_mem_malloc(u32 size)  
{  
    signed long offset=0;  
    u32 nmemb;	//需要的内存块数  
	u32 cmemb=0;//连续空内存块数
    u32 i;   
    if(size==0)return 0XFFFFFFFF;//不需要分配
    nmemb=size/MEM_BLOCK_SIZE      	//获取需要分配的连续内存块数
    if(size%MEM_BLOCK_SIZE)nmemb++;  
    for(offset=MEM_MAX_SIZE/MEM_BLOCK_SIZE;offset>=0;offset--)//搜索整个内存控制区  
    {     
		if(memmapbase[offset]==0)cmemb++;//连续空内存块数增加
		else cmemb=0;								//连续内存块清零
		if(cmemb==nmemb)							//找到了连续nmemb个空内存块
		{
            for(i=0;i<nmemb;i++)  					//标注内存块非空 
            {  
                memmapbase[offset+i]=nmemb;  
            }  
            return (offset*MEM_BLOCK_SIZE);//返回偏移地址  
		}
    }  
    return 0XFFFFFFFF;//未找到符合分配条件的内存块  
}  
//释放内存(内部调用) 
//memx:所属内存块
//offset:内存地址偏移
//返回值:0,释放成功;1,释放失败;  
u8 my_mem_free(u8 memx,u32 offset)  
{  
    int i;  
    if(offset<MEM_MAX_SIZE)//偏移在内存池内. 
    {  
        int index=offset/MEM_BLOCK_SIZE;			//偏移所在内存块号码  
        int nmemb=memmapbase[index];	        //内存块数量
        for(i=0;i<nmemb;i++)  						//内存块清零
        {  
            memmapbase[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)membase;     
    my_mem_free(memx,offset);	//释放内存      
}  
//分配内存(外部调用)
//memx:所属内存块
//size:内存大小(字节)
//返回值:分配到的内存首地址.
void *mymalloc(u8 memx,u32 size)  
{  
    u32 offset;   
	offset=my_mem_malloc(memx,size);  	   	 	   
    if(offset==0XFFFFFFFF)return NULL;  
    else return (void*)((u32)membase+offset);  
}  

基于链表的形式

上边用状态表的方式是额外用数组来记录状态,而且内存只能按块分配。下面介绍可以分配任意大小的方式。

基本思想

用状态表的方式我能能通过遍历数组知道那些内存空着,而另外一种能实现遍历功能的就是链表。首先定义一个链表结构体保存信息:
typedef struct node* PNode;//定义节点指针
typedef struct node
{ u32 offset; //与内存池首地址的偏移 (当前节点位置)
u32 size;//本节点的内存大小
PNode Prior;//前驱指针域 (下一节点位置)
PNode Pnext;//后继指针域 (前一节点位置)
}Node;
在这里插入图片描述

如上图,每次分配内存就是创建一个节点,而且节点存放的位置就是分配的内存前边。
这样我们顺着链表节点遍历下去,能知道这段内存的位置,大小。还可以计算节点间空闲内存大小 如(节点4的位置 - 节点3的位置)- 节点3的内存大小 = 节点3和节点4之间空闲内存的大小。要分配这些内存。只要在节点3和4间再插入一个节点就行。要释放内存更简单,应为分配出去的内存地址的前边就是节点信息,能直接点位节点。直接在链表里删除该节点就行。

程序实现

#define MEM_MAX_SIZE			(32*1024)  						//最大管理内存 32K

typedef struct node* PNode;//定义节点指针  
typedef struct node  
{   u32 offset; //与内存首地址的偏移 
	  u32 size;//本节点带的内存大小 
    PNode Prior;//前驱指针域	
    PNode Pnext;//后继指针域
}Node;	
__align(4) u8 membase[MEM_MAX_SIZE];			//SRAM内存池 4字节对齐

Node HedaNode,EndNode;  //头尾节点
void mem_init()             //初始化内存链表
{
HedaNode.offset=0;          //头节点初始化
HedaNode.size=0;
HedaNode.Prior=NULL;
HedaNode.Pnext=&EndNode; //刚开始指向尾节点

EndNode.offset=MEM_MAX_SIZE;//尾节点初始化
EndNode.size=0;
EndNode.Prior=&HedaNode; //刚开始指向头节点
EndNode.Pnext=NULL;	
}
//设置内存
//*s:内存首地址
//c :要设置的值
//count:需要设置的内存大小(字节为单位)
void mymemset(void *s,u8 c,u32 count)  
{  
    u8 *xs = s;  
    while(count--)*xs++=c;  
}	
//内存分配
//size:需要分配的内存大小(字节为单位)
void *mymalloc(u32 size)
{
 PNode p=NULL;
 PNode pnew=NULL;
 u32 relsize;
 u32 menaddr;
 relsize=size+sizeof(Node); //实际要申请的内存大小 包含节点空间
 if(relsize%4)//4字节对齐 
 relsize+=(4-(relsize%4)); //不满4字节就补齐
 p=&HedaNode; //导入头节点
 while(p->Pnext)//下一节点存在
 {
	if((p->Pnext->offset - (p->offset + p->size))>relsize)//本节点和下节点间剩余的内存>要申请的内存大小
	{ menaddr=(u32)membase+(p->offset)+(p->size);
		pnew=(PNode)menaddr;//新节点的地址=内存池基地址+本节点偏移量+本节点内存大小
		pnew->Prior=p; 
    pnew->Pnext=p->Pnext;                          //新节点的Pnext=本节点Pnext
    pnew->size=relsize;                             //新节点的内存大小
	  pnew->offset=p->offset+p->size;               //新节点偏移量
	  p->Pnext=pnew;                               //本节点Pnext=新节点的地址
	  mymemset( pnew+1,0,size);                    //初始化内存
    return (void*)(( (PNode)menaddr )+1);                         //返回去除节点信息的地址
   }
   else 
	  p=p->Pnext;	//导入下一个节点	
 }
    return NULL;  //申请失败	 
}
//释放内存
//ptr:内存首地址 
void myfree(void *ptr) 
{
 PNode p=NULL;
 if(ptr==NULL)return;//地址为0.
 p=((PNode)ptr)-1;//点位到内存地址前的节点信息
 p->Prior->Pnext=p->Pnext;//本上节点的上一节点的Pnext=本节点的下一节点地址
 p->Pnext->Prior=p->Prior;//本上节点的下一节点的Prior=本节点的上一节点地址
 p->Prior=NULL;           
 p->Pnext=NULL;	 
}

链表方式能实现分配任意大小内存,而且不用额外的状态表。但是一旦应用程序操作内存越界误操作了节点信息,整个内存链表就会断裂。

  • 9
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
单片机内存管理框架主要是为了有效地利用有限的内存资源,避免内存碎片化和浪费。以下是一个简单单片机内存管理框架的实现思路: 1. 定义内存池:首先需要定义一个内存池,用于存储可用的内存块,内存池可以是一个数组或链表,每个内存块大小相同。 2. 初始化内存池:在系统启动时,需要将整个内存池初始化为一系列可用的内存块,这些内存块可以用于分配给任务或其他部分使用。 3. 分配内存:当需要分配内存时,内存管理框架会从内存池中找到一个可用的内存块,将其标记为已分配,并返回该内存块的地址。 4. 释放内存:当任务完成了对内存块的使用,需要将其释放回内存池中,以便其他任务可以重新使用该内存块。 5. 管理内存池:内存管理框架还需要实现一些管理内存池的函数,例如检查内存池的状态、打印内存池的使用情况等。 下面是一个简单单片机内存管理框架的代码示例: ```c #define MEM_POOL_SIZE 1024 // 内存池大小 typedef struct mem_block { uint8_t data[MEM_BLOCK_SIZE]; // 内存块数据 struct mem_block *next; // 指向下一个内存块 uint8_t used; // 是否被使用 } mem_block_t; static mem_block_t mem_pool[MEM_POOL_SIZE]; // 内存池 void mem_init(void) { // 初始化内存池 for (int i = 0; i < MEM_POOL_SIZE; i++) { mem_pool[i].used = 0; mem_pool[i].next = &mem_pool[i+1]; } mem_pool[MEM_POOL_SIZE-1].next = NULL; } void *mem_alloc(size_t size) { // 分配内存 if (size > MEM_BLOCK_SIZE) { return NULL; } mem_block_t *block = mem_pool; while (block != NULL) { if (!block->used) { block->used = 1; return block->data; } block = block->next; } return NULL; } void mem_free(void *ptr) { // 释放内存 if (ptr == NULL) { return; } mem_block_t *block = (mem_block_t *)((uint8_t *)ptr - offsetof(mem_block_t, data)); block->used = 0; } void mem_print(void) { // 打印内存池状态 int free_blocks = 0; int used_blocks = 0; mem_block_t *block = mem_pool; while (block != NULL) { if (block->used) { used_blocks++; } else { free_blocks++; } block = block->next; } printf("free blocks: %d, used blocks: %d\n", free_blocks, used_blocks); } ``` 这个简单内存管理框架采用了一个静态数组作为内存池,并实现了分配、释放和打印内存池状态等基本功能。在实际应用中,可以根据需求修改和扩展该框架。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值