欢迎使用CSDN-markdown编辑器

来源:互联网 发布:网强网络管理系统 编辑:程序博客网 时间:2024/04/30 02:04

对于伙伴系统而言,有大小固定且相同的内存块,将大小相同的内存块链接在一起时,为了达到写平衡,使用双向链表实现,这样可以在回收回的内存块时,将其插入双向链表的链尾,在申请内存块时,从链头删除内存块;而将链接不同大小内存块的双向链表链接起来时,因为内存块大小种类固定所以使用顺序表实现,故使用顺序表加双向链表的形式实现伙伴系统。

双向链表设计思路及定义

细化而言,对于内存块,因为是在双向链表中,所以每个内存块中需要一个header,其中包含的信息有前驱 llink、后继 rlink;同时还需要一个标记内存块是否被占用的信息 tag,tag = 0 则内存块空闲,tag = 1则内存块占用;需要一个标记内存块大小的信息 kval,内存块大小为 2k

所以我们在实现内存池时,以一个header的大小16个字节为单位进行内存管理,每个单位称之为WORD.

Space

typedef struct WORD{    struct WORD *llink;    int tag;    int k;    struct WORD *rlink;}WORD, *Space;

顺序表设计思路及定义

对于顺序表,则需要一个记录内存块大小的信息 nodesize;一个记录链接与 nodesize 大小相同的内存块的双向链表的结点信息 first。

typedef struct HNode{    int size;    WORD *next;}HNode, *PHNode;

实现动态内存管理

内存初始化

sqlist

通过初始化实现如上图所示的内存池。同时在这里需要建立一个内存池头结点、全局变量 HEAD 记录内存池首地址,作用下文会有详细说明。

void InitMemory(PHNode *ppav)               /*初始化:创建一个需要进行内存管理的内存池*/{    WORD word[SIZE];    HNode head[K];    HEAD = word;//保存内存池首地址    word[0].llink = word;//初始化内存池    word[0].tag = 0;    word[0].k = K - 1;    word[0].rlink = word;    *ppav = head;//将顺序表链接到头指针    int i;    int size = 1;    for(i = 0; i < K; i++, size *= 2)//初始化顺序表    {        head[i].size = size;        head[i].next = NULL;    }    head[K - 1].next = word;//将内存池链接到顺序表}

动态内存申请

WORD *Mymalloc(PHNode ppav, int size)    /*动态内存申请*/{    assert(ppav != NULL);    if (ppav == NULL)    {        return NULL;    }    int i;    //int j = -1;    for (i = 0; i < K; i++)    {        //if (ppav[i].size >= size && j == -1)        //{        //  j = i;        //}        if (ppav[i].next != NULL && ppav[i].size >= size)//判断是否有足够剩余空间        {            break;        }    }    if (i == K)//剩余空间不足    {        return NULL;    }    Space q;    Space head = ppav[i].next;    Space p = head;    //有剩余空间    if (head->llink == head)//该双向链表中只有一个节点    {        //head = NULL; Error: 无法改变ppav[i].next的值,只改变head的值,函数运行完成后销毁,无用        ppav[i].next = NULL;    }    else//该双向链表不只有一个节点    {        ppav[i].next = head->rlink;        head->llink->rlink = head->rlink;        head->rlink->llink = head->llink;    }    /*方法一: 从左向右由小到大切割内存块    q = p + ppav[j].size;    p->llink = p;    p->k = j;    p->tag = 1;    p->rlink = p;    while (j < i)    {        q->llink = q;        q->tag = 0;        q->k = j;        q->rlink = q;        ppav[j].next = q;        j++;        q = q + ppav[j].size;    }    */    /*方法二: 从右向左有大到小切割内存块*/    for(i--; ppav[i].size >= size; i--)//大块内存块切割小块内存块    {        q = p + ppav[i].size;        q->llink = q;        q->tag = 0;        q->k = i;        q->rlink = q;        ppav[i].next = q;    }    p->llink = p;    p->tag = 1;    p->k = i + 1;    p->rlink = p;    return p;}

动态内存申请时要注意:

  • 当双向链表值在顺序表中的头指针为NULL的情况
  • 双向链表只有一个节点的情况

动态内存申请的难点在于处理大块内存块切割小块内存块的情况,这时有两种方法,方法一: 从左向右由小到大切割内存块,方法二: 从右向左有大到小切割内存块。

动态内存回收

void Myfree(PHNode ppav, WORD *p)        /*动态内存回收*/{    p->tag = 0;    int i;    int k = p->k;    int size = powx(2, k);    Space q;    int flag;    while(k < K)    {        if ((p - HEAD) % (size * 2) == 0)//判断左块、右块        {               flag = 0;//左块            q = p + size;        }        else        {            flag = 1;//右块            q = p - size;        }        if (q->tag == 0 && flag == 1 && q->k == p->k)//p为右块且有p的左块存在        {            if(q->rlink == q)            {                ppav[k].next = NULL;            }            else            {                ppav[k].next = q->rlink;//易错: 若p的左块为ppav[k].next所指的空间块,不写则在输出时会陷入死循环                q->llink->rlink = q->rlink;                q->rlink->llink = q->llink;            }            q->k++;            p = q;            k++;        }        else if (q->tag == 0 && flag == 0 && q->k == p->k)//p位左块且有p的右块存在        {            if(q->rlink == q)            {                ppav[k].next = NULL;            }            else            {                ppav[k].next = q->rlink;//易错: 若p的左块为ppav[k].next所指的空间块,不写则在输出时会陷入死循环                q->llink->rlink = q->rlink;                q->rlink->llink = q->llink;            }            p->k++;            k++;        }        else//p没有伙伴块        {            break;        }        size *= 2;    }    if(ppav[p->k].next == NULL)    {        p->llink = p;        p->rlink = p;        ppav[p->k].next = p;    }    else    {        q = ppav[k].next;        q->llink->rlink = p;//将p插入内存池        p->llink = q->llink;        q->llink = p;        p->rlink = q;    }}

在动态内存回收中需要注意:

  • 在判断回收的内存块的伙伴是否空闲时应使用q->tag == 0 && flag == 0 && q->k == p->k进行判断,缺一不可
  • 代码第33、50行不可缺少
  • 当双向链表值在顺序表中的头指针为NULL的情况
  • 双向链表只有一个节点的情况

动态内存回收的难点在于内存回收时对于伙伴块的合并,应使用循环,当回收的内存块有伙伴时,与伙伴合并成为新的内存块,新的内存块再次查看是否有伙伴,直至新的内存块没有伙伴,将其插入和其大小的匹配的双向链表中,正如整体设计中所说,这时双向链表的价值就体现出来,为了写平衡,双向链表可轻易将新内存块插入双向链表的链表尾。

在这其中伙伴是一个很重要的概念,何为“伙伴”?将一个大块内存切割成为两个大小相同的小块内存,两个小块内存互为伙伴

在寻找回收的内存块的伙伴时,首先需要知道回收的内存块是左块还是右块,pMOD2k+1=0则为左块,否则为右块。这里的 p 指回收的内存块相对于内存池首地址的相对地址,这时在内存初始化中的HEAD变量的作用就体现出来。

0 0
原创粉丝点击