今天继续读书笔记,“挑战内存管理”(30天自制操作系统)。
为什么对这块内容敢兴趣呢,因为曾经遇到这么一个问题。在STM32程序中想使用队列,可不是上篇讲的FIFO,而是使用了较大的内存空间,又想做队列的顺序存取管理。
在这个队列里用到了malloc,动态申请内存,一开始是直接申请不到内存,后来在启动脚本里更改了设置堆的地址值,可以申请成功,但发现申请几次后,也申请不到内存。
果然MCU级别的程序,内存这块处理起来就没有windows程序那么随心所欲了。讲了这么多,开始正题吧。
1、相关数据结构体
#define MEMMAN_FREES 4000 //最大可以有4000个独立的可用内存块 struct FREEINFO { unsigned int addr,size; }; //这是内存管理的核心数据结构体,可用内存用其开始地址和内存大小表示。 //申请内存就是在可用内存中找到大小合适的内存,返回起始地址给申请者, //同时可用内存就少了一块(这是申请内存大小恰好等于可用内存大小的情况,其他情况看后面代码) struct MEMMAN //内存管理数据结构 { int frees,maxfrees,lostsizes,losts; struct FREEINFO[MEMMAN_FREES]; } //frees表示当前独立可用内存块个数 //maxfrees用于观察,frees的最大值 //losts表示申请内存失败的次数 //lostsizes表示申请内存失败导致内存丢失的大小
2、内存管理的初始化,也就是相关数据结构的初始化
void memman_init(struct MEMMAN *man) { man->frees = 0; //还没有一块可用内存 man->maxfrees = 0; man->losts = 0; //释放失败的次数为0 man->lostsize = 0; //释放失败的总内存大小也为0 }
3、获取当前所有可用内存的总大小
unsigned int memman_total(struct MEMMAN *man) { unsigned int i,t=0; for(i=0;i<man->frees;i++) { t+= man->free[i].size; //只考虑所有可用内存占得总内存大小,不考虑内存连续(肯定有不连续的)。 } return t; }
4、内存管理关键代码1-申请内存
unsigned int memman_alloc(struct MEMMAN* man,unsigned int size) //返回值也就是申请到的内存的起始地址 { unsigned int i,a; for(i=0;i<man->frees;i++) { if(man->free[i].size > size) //遍历所有可用内存块信息 { a = man->free[i].addr; //第i块可用内存满足要求 man->free[i].addr += size; man->free[i].size -= size; //更改第i块内用内存的信息 if(man->free[i].size == 0)//恰好申请内存就与第i块可用内存大小一致 { man->frees--; //可用内存少了一块 for(;i<man->frees;i++) { man->free[i] = man->free[i+1]; //后面的可用内存信息前推 } } return a; //申请成功,返回指定地址 } } return 0; //没找到,即申请内存不成功 }
5、内存管理关键代码2-释放内存
int memman_free(struct MEMMAN *man,unsigned int addr,unsigned int size) { int i,j; /*为了管理方便,可用内存块是按照addr顺序排列的,先找到应该放 在可用内存块的哪里*/ for(i=0;i<man->frees;i++) { if(man->free[i].addr > addr) { break; } } //实际就有多种情况了,下面一一分析 /* free[i-1].addr < addr < free[i].addr */ if(i>0) { if(man->free[i-1].addr +man->free[i-1].size == addr) { //释放的内存与前面的可用内存块合在一起了 man->free[i-1].size += size; if(i<man->frees) //要考虑释放的内存与free[i]能不能合在一块 { if(addr+size == man->free[i].addr) //与后面的内存合在一块了 { memman->free[i-1].size += man->free[i].size; //这样合并就减少一条可用内存信息 man->frees --; for(;i<man->frees;i++) { man->free[i] = man->free[i+1]; //结构体迭代 } } } return 0; //释放成功 } } if(i<man->frees) //上面条件不成立,不能与前面的可用内存块信息合在一起 { if(addr+size == man->free[i].addr) //可以与后面的内存块信息合在一起 { man->free[i].addr = addr; //调整后面的可用内存块信息即可 man->free[i].size += size; return 0; //释放成功 } } /*既不能合前面的归在一起,也不能和后面的归在一起*/ if(man->frees < MEMMAN_FREES) //只能增加一条可用信息 { for(j=man->frees;j>i;j--) { man->free[j] = man->free[j-1]; } man->frees ++; if(man->maxfrees < man->frees) { man->maxfrees = man->frees; //更新最大值 } man->free[i].addr = addr; man->free[i].size = size; return 0; //释放成功 } /*不能往后移动*/ man->losts ++; man->lostsize += size; return -1; }
以上代码如何使用:
1、先初始化结构体
struct MEMMAN *memman = (struct MEMMAN *)malloc(struct MEMMAN);
2、调用memman_init初始化数据结构体
memman_init(memman);
3、先释放整个可用内存(如64K RAM,后30K设为可被动态申请内存)
memman_free(memman,&34K,30K);
4、在需要申请内存的地方进行内存申请
char *buf = (char *)memman_alloc(memman,1000); //申请1000个字节空间
5、在任何合适地方都可获取可用内存的总大小
unsigned int memtotal = memman_total(memman);
以上所有源代码即完成了一个功能,自定义了类malloc和free的函数,完成对内存空间的管理工作。