首页 > 内存管理器(二)边界标识法

内存管理器(二)边界标识法

边界标识算法

前言

首先说明,我们这里的内存管理器主要是以模拟各种内存分配算法为主,从内存申请一片内存然后根据我们所选定的数据结构和算法,实现程序的堆空间的分配,至于内存分配详情我们会在Linux内核内存管理的简单分析中探讨。

这个算法是什么

边界标识法是操作系统中用以进行动态分配的一种存储管理的方法,系统将所有的空闲块链接在一个双重循环链表结构中;分配可以按照首次匹配,最佳匹配方法执行,其次个人觉得先学这个算法然后在学伙伴算法能更简单点吧。

这个算法的特点

在每个内存区的头部和底部两个边界上分别设有标识,以标识该区域的占用块和空闲块,使得在回收用户释放的空闲块时易于判别在物理上位置上与其相邻的内存区域是否为空闲块,以便将所有地址连续的空闲存储区组合成一个尽可能更大的空闲块。

图解这个算法

数据结构

这就是抽象的链表节点,分别由头head,空间(内存块),尾tail ,组成。

其中表头由4个部分组成。

llink : 这个数据结构主体是循环链表,所以这个指针指向前一个节点。

tag : 标示位:1标示为已分配块,0标示未分配块。

size : 标示这个节点的大小(包括头部和尾部)。

rlink : 由于是双向循环链表,这个指针指向后一个节点。

表尾由2个部分组成:

uplink : 指向本节点的头部。

tag : 同上标示分配情况。

边界标示法

其中这些节点的链接形式如图所示,基础是双向循环链表并且包含上述结构体。

节点数据结构

typedef struct WORD{   //WORD :内存字类型union{WORD *llink ; //头和尾都是同一个节点WORD *uplink;};int tag ; //块标志,0:空闲,1:占用int size; //块大小WORD *rlink; //指向后继节点OtherType other; //其它部分}WORD,head,foot,*Space;

这个算法其他一些要注意的地方

  1. 假设每次需要找m 个大小,但是我们每次都分配n 给它,那么久而久之,就会有很多的m-n 个空间散落于链表中,所以我们需要设置一个标准值e,当m-n <= e 的时候,就将m 的空闲整块分配给它,反之,我们就分配想当需求大小的空间。
  2. 如果收每一次都从头开始寻找就是首次匹配,由于已经进行多次,必然前边会聚集较多的小块,所以我们应当每次分配一次就将,表头指向它已经分配的后边的一个节点,这样就能基本保证每一次的进行首次匹配的效果了。

回收算法:

回收的思想很简单,根据它前后块的不同,总共有4中情况。

1.前后都已经占用

直接将内存块插入。

2.前一个已经被占用,后一个没有被占用。

我们直接将后一个,和待回收的块合并成一个完整的块。

3前一个没有被占用,后一个已经被占用。

我们将前一个和待回收的块合并成一个完整的块。

4前一个与后一个都没有被占用。

我们将三个块全部合并成一个完整的未分配块。

下面就来看一个使用边界标识法的空间管理简例

#include
#include#define MAXSIZE    1000
#define ALLOC_MIN  100 
#define FootLoc(p)  (p+(p->size) - 1)typedef struct WORD{    //WORD:内存字类型union {                //头和尾都指向同一个位置使用联合体struct WORD *llink;struct WORD *uplink;};int tag ; //块标识:1:占用 0: 空闲int size ; //块的大小struct WORD *rlink;   //指向后继节点// OtherType other; //字的其他部分这里暂时用不到}*Space;Space user[MAXSIZE] = { NULL} ; //用户空间数组int usCount = 0;Space AllocBoundTag(Space *pav,int n){Space p = * pav;if(NULL == p){printf("The memory is NULL 
");return NULL;}for(p;p != NULL && p->size < n && p->rlink != *pav; p = p->rlink ){if(p == NULL || p->size < n){printf("error is :%d
",__LINE__);return NULL;}*pav = p->rlink;    //防止小的零碎空间全部集中在前边if(p->size - n > ALLOC_MIN){  // 找到可以分配的块开始分配 ,同样也为了减少碎片 ,从高位截取p,且设置新的底部p->size -= n;             //计算剩余块的大小Space foot = FootLoc(p);  //指向剩余块的底部foot->uplink = p;         //设置剩余块的底部foot->tag = 0 ;           //设置剩余块底部的标识p = foot + 1  ;           //指向分配块的头部p->size = n ;          //设置分配块的头部foot = FootLoc(p);     //指向分配块的底部p->tag = 1 ;        //设置分配块的头部foot ->tag = 1;    //同上foot->uplink = p ;}else{ //分配后剩余空间小于规定的ALLOC_MINif(p == *pav){  //如果只剩下一个空间了,清空指针*pav = NULL ;}else{ //直接分配整个块出去,虽然会浪费一部分空间Space foot = FootLoc(p);foot->tag = p->tag = 1;p->llink->rlink = p->rlink ;p->rlink->llink = p->llink ;}}}user[usCount++] = p; return p;
}void Space_init(Space * freeSpace, Space *pav){*freeSpace = (Space)malloc(sizeof(struct WORD)*(MAXSIZE + 2)); //初始化空间链表Space head = *freeSpace ;   //头指针head->tag = 1;             //设置头指针标示符head++;                    //头指针指向第一个节点head->tag = 0;             //设置第一个节点为空闲块              head->llink = head->rlink = head;  //设置循环链表head->size = MAXSIZE ;            //设置块的大小*pav = head ;                 //设置头指针    Space foot = FootLoc(head);   foot->tag = 0;foot->uplink = head ;foot++;foot->tag = 1;               //设置尾边界为已经使用}void reclaimBoundTag(Space *pav ,Space sp){Space  pre = (sp - 1)->uplink ;Space  next = sp + sp->size ;int pTag = pre->tag ;int nTag = next->tag ;           //声明两个节点,分别得到前一个和后一个节点的信息,并且记录占用情况,根据占用情况选择合并的手段if(pTag == 1 && nTag == 1 ){  //前后都是满的直接插入Space foot = FootLoc(sp);foot->tag = sp->tag = 0;if(pav == NULL){*pav = sp->llink = sp->rlink = sp;}else{sp->rlink = *pav ;sp->llink = (*pav)->llink;(*pre).llink = sp ;sp->llink->rlink = sp;*pav = sp;}}else if(pTag == 0 && nTag == 1){  // 前边的可以合并pre->size += sp->size ;Space foot = FootLoc(pre);foot->tag = 0;foot->uplink = pre;}else if(pTag == 1 && nTag == 0){  //后边的可以合并sp->llink = next->llink;sp->rlink = next->rlink;next->llink->rlink = sp ;next->rlink->llink = sp ;sp->size += next->size ;Space foot = FootLoc(sp);sp->tag = foot->tag = 0 ;foot->uplink = sp;}else{   //三个块一起合并pre->rlink = next->rlink;pre->size += sp->size + next->size;Space foot = FootLoc(pre);foot->uplink = pre ;}int i ;for(i = 0;i < usCount ;i++){if(sp == user[i]){user[i] = NULL;}}
}void print(Space s){printf("The head is %0x SIZE: %d 
 pre is %0x ,next is %0x
",s,s->size,s->llink,s->rlink);
}void print_space(Space pav){if(pav != NULL){printf("you an use the list:
");Space pos = pav;for(pos = pos->rlink;pos != pav;pos = pos->rlink){print(pos);}}printf("_____________________________
");int i ;for(i = 0;i< usCount ;i++){Space us = user[i];if(us){printf("the head is %0x  SIZE is %d
",us,us->size);}}}int main(){Space freeSpace = NULL;Space pav = NULL;Space_init(&freeSpace,&pav);print(pav);printf("malloc a 300 and 300 
");Space m3 = AllocBoundTag(&pav,300);print_space(pav);Space t3 = AllocBoundTag(&pav,300);print_space(pav);printf("free 300 
");reclaimBoundTag(&pav,m3);print_space(pav);return 0;
}

参考资料:

《CSAPP》

《数据结构(严尉敏)》

http://blog.csdn.net/fuming0210sc/

版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://www.cnblogs.com/zmrlinux/p/4921381.html

更多相关:

  • STL 基本的六大组件作用以及功能如下: 可以看到allocator是数据存储组件container的幕后支持者,负责为其数据存储分配对应的存储空间。 operator::new 在详细介绍alloctor之前,先描述一下new运算符,我们使用C++ new一个对象的时候就是调用底层operator::new运算符,实现如下:...

  • 权限分配可以有两种方法,第一种方法是根据部门职位分配权限,第二种是根据角色分配权限; FR自带有三个JQ对象,用以保存用户名参数/角色参数/部门参数——$fr_username/$fr_authority/$fr_userposition 根据部门职位: 以管理员身份进入平台,就打开权限分配模块(分配结束之后一定不要忘记保存编辑):...