glibc2.5 中的malloc 与 free 之我见(一)

    大海啊,你全是水。

    骏马啊,你四条腿。

                                  --------------题记

 

       在程序设计过程中,我们往往需要借助于大量的存储来完成计算。由于受应用程序地址空间以及机器本身存储容量限制,静态申请内存应该尽量避免。而动态申请内存更加灵活,在一定程度上可以节省内存空间。

动态申请内存的方法主要有三种:

1. 栈分配:在函数内部定义的变量都是存储在栈上的。(static变量除外)。

2. 堆分配:一般是通过malloc申请(c++中的new 本质上也是从堆上分配内存)。

3. 地址映射:mmap()函数从当前的进程地址空间中申请内存块。

 

栈分配一般用于较小的存储申请,并且一旦退出函数,空间变自动被释放(其实是ESP寄存器变了)。而mmap通常用于较大内存块的分配,它也是进程间共享内存的有效方法。

在程序实现过程中,除非应用于特定场合且对效率有较高要求,我们一般都会采用glibc库中的malloc方法申请内存。了解malloc的实现机制可以使得我们更加有效地使用它。本文将从另外一个视角对其进行分析。

抛开现有的解决方案,只凭借我们自己的经验知识和逻辑思维,自己来设计一个这样的内存管理算法:

1. 在查找合适的内存块时,只应在空闲内存块中查找。因此需要设计数据结构来保存空闲内存块。链表是个不错的选择,在Linux内核中这样的思路也是比比皆是。

2. 此时我们还不知道当前块是否能够满足申请的需求,因此我们需要记录空闲块内存的大小。如果当前块大小大于申请大小,则能满足条件,我们就把这块空闲块拆成两块,一块用于满足申请,剩余块再插入到空闲块链表中。

3. 当释放空闲块时,我们直接将空闲块插入到空闲链表中。

 

程序的伪代码如下:

//----------------------------------------

struct Chuck

{

    Chunk * next;

    size_t size;

};

 

Chunk  * header = ....(初始堆的地址);

 

void * malloc(size_t size)

{

    Chunk * victim = header;

    while(victim && victim->size < size)

    {

        victim = victim->next;

    }

    if (victim)

    {

        split(victim); //拆分当前空闲块,一个用于返回,另一个再插入到空闲链表。

        return victim + 1;

    }

    else

        return NULL;

}

 

void free(void * add)

{

    Chunk * idle = ((Chunk *)add) - 1;

    insert(idle); // 将释放的内存块插入到空闲链表中。

}

//----------------------------------------

哈哈,如此一来,我们的内存管理系统 MM1.0 就大功告成了。

MM1.0 中可能会存在哪些问题呢?

如果系统中请求的块具有相同的大小,那么MM1.0 完全可以满足用户的需求。但是在实际的应用中,这中情况是极少的,一般会混杂大小不同的内存请求。这就会产生两个问题:

1.      在链表中查找满足请求大小的空闲块可能花费大量的时间。

2.      当前程序只有分割,没有合并。如果程序运行时间较长,容易产生内存碎片。这时即使内存中有足够大的连续空闲内存,但是系统中记录的是零碎的空闲内存块,从而无法满足请求。

 

针对以上问题,我们应该如何加以改进呢?

1.      如何能够快速检索出具有特定大小的空闲内存块呢?

极端的方案是针对每一个块大小都建立一个空闲链表。如果要查找相应大小的空闲块,只要到相应的空闲链表中查找就好了。如果该大小的空闲链表为空,则到更大块对应的空闲链表中查找,直到满足条件未知。

2.      如何能够合并相邻的空闲内存块呢?

为了能够合并相邻内存块,有两个问题需要解决:

1. 如何能够检索到相邻内存块? 2. 如何知道相邻内存块是否空闲?

由于Chunk是连续分布的,并且Chunk描述符总是位于内存块之前,因此只要知道了前面内存块的大小即可知道前面块的Chunk描述符的地址了。--à 问题1解决。

每一个Chunk可以增加一个Flag来记录当前Chunk是否为空闲块。--à 问题2解决。

 

改进后的伪代码如下所示:

//----------------------------------------

struct Chuck

{

    Chunk * next;

    size_t size;

    size_t preSize;

    int flag;

};

 

Chunk * bin[*****];  // 针对每种大小都要建立一个空闲链表

 

void * malloc(size_t size)

{

    for ( size_t start = size; ; start++)

    {

        Chunk * victim = bin[start];

        if (victim)

        {

            erase(victim);  //将当前空闲内存块从空闲链表中删除

            return victim + 1;

        }

     }

}

 

void free(void * add)

{

    Chunk * idle = ((Chunk *)add) - 1;

    Chunk * pre = (Chunk * )((((char *)idle) + idle->preSize)) - 1;

    Chunk * next = (Chunk * )((((char *)idle) + idle->size)) + 1;

    if ( pre->flag & CHUNK_IDLE)

    {

        erase(pre);

        idle = merge(pre, idle);

    }

    if ( next & CHUNK_IDLE)

    {

        erase(next);

        idle = merge(idle, next);

    }

    insert(idle);   //根据idle的大小将其插入到合适的空闲链表中

}

//----------------------------------------

 

哈哈,如此一来,我们的MM2.0 也已经OK了。

相对于MM1.0 它的功能更加强大:

1. 更快地检索空闲块。

2. 在一定程度上避免了内存碎片。

 

但是MM2.0 中也存在一定的问题,例如建立了太多的空闲链表等等。

这些问题我们又应该如何解决呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值