dlmalloc源码剖析之:mALLOc

本文深入剖析了dlmalloc 2.7.0版本的mALLOc函数实现细节,介绍了其如何通过不同的bin来高效地分配内存。针对不同大小的请求,dlmalloc采取了灵活的策略,包括直接从fastbin分配小块内存、从smallbin分配小于512字节的内存块、以及在未找到合适内存时如何扩展top等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

dlmalloc源码剖析之:mALLOc

版权声明: 本文章由vt.buxiu发布在 www.vtzone.org,版权归vtzone研究小组所有,转载请保持此声明!!!

@@内容摘要:这个函数应该是所有使用C/C++的人最熟悉的malloc调用的实现,c语言标准库提供的malloc函数.如果你使用linux, douglea malloc已经默认作为glibc的malloc,新的版本可能用的是ptmalloc(dlmalloc的多线程版本),如果你用的bsd4.2及以前系统libc用的kingsley的malloc; BSD(包括freebsd,netbsd,openbsd)4.2以后版本libc用的是PHKmalloc;如果你用的windows系统用的是microsoft的分配器算法;不过其他各个系统很容易使用doug lea malloc替换现有malloc函数.
本文以dlmalloc2.7.0版本为基础,先以伪代码的形式介绍sYSTRIm函数的主要流程。其中一些次要情节已略!@@

/*如果你使用linux, douglea malloc已经默认作为glibc的malloc,
新的版本可能用的是ptmalloc(dlmalloc的多线程版本)
如果你用的bsd4.2及以前系统libc用的kingsley的malloc;
BSD(包括freebsd,netbsd,openbsd)4.2以后版本libc用的是PHKmalloc;
如果你用的windows系统用的是microsoft的分配器算法;
不过其他各个系统很容易使用doug lea malloc替换现有的malloc*/
//c语言标准库提供的malloc函数;请注意malloc的几个return出口;
void* mALLOc(size_t bytes)
{
    //0~4bytes->nb=16;>4bytes->nb=bytes+2个4字节头,然后对其到8bytes
    checked_request2size(bytes, nb);

    //如果在fastbin中有可用的块直接从fastbin中分配
    if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
        fb = &(av->fastbins[(fastbin_index(nb))]);
        if ( (victim = *fb) != 0) {  //静态变量成员fastbin初始化为0
            *fb = victim->fd;
            check_remalloced_chunk(victim, nb);
            return chunk2mem(victim);
        }
    }

    //如果是<512bytes的小块请求,从smallbin中取一块
    if (in_smallbin_range(nb))
    {
        //根据nb大小定位到smallbin
        idx = smallbin_index(nb);
        bin = bin_at(av,idx);

        //如果该大小的bin[i]列表不为空
        if ( (victim = last(bin)) != bin)
        {
            if (victim == 0)         //静态变量成员smallbin初始化是0
              malloc_consolidate(av);//第一次进来这里调用init_state函数进行初始化
            else {//有空闲块
                /*       victim
                             |
                            //
                   bin->first_chunk->chunk->chunk->...->last_chunk
                    |                                                         //
                    --------------------------------->|
                 */
                //按上图将victim从链表中删除,设置victim的下一块的pbit=inuse
                //将victim块返回给应用
                return chunk2mem(victim);
            }
        }
    }

    else {//>512bytes,先释放fastbin中的块
        idx = largebin_index(nb);
        if (have_fastchunks(av))    //初始化的时候静态变量0,这个条件成立,
            malloc_consolidate(av); //合并fastbin中的chunk,放入unsorted_bin
    }

    //这里是唯一将chunks放入bin的地方
    //处理最近被释放或剩余的chunks,如果上次小请求没有完全匹配
    //分割出小chunk就会发生

    //最外面的for(;;)需要,因为我们无法知道在malloc结束前有合并操作
    //因此需要多尝试一次,最多多循环一次

    for(;;){
        /*
           unsorted chunks,所有的从一个chunk中分割出来的剩余chunk首先放到
           unsorted chunks链表中,下次malloc调用中有一次被再次使用的机会。
           作为一个队列维护。
           当free或malloc_consolidate函数中    将剩余chunk放入unsorted chunks链表,
           而在malloc函数中被分配或放入其他    正常bin中。
        */

        //循环unsorted中每一块,与插入顺序相反,从后面开始匹配查找
        while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
            bck = victim->bk;//victim:unsorted's last chunk;bck:unsorted's last-two chunk
            size = chunksize(victim);
            if (in_smallbin_range(nb)    //<512bytes
                && bck == unsorted_chunks(av) //unsorted队列中只有一块
                && victim == av->last_remainder //并且这一块是上一次分割剩下的
                && (unsigned long)(size) > (unsigned long)(nb + MINSIZE)) //剩余的chunk必须大于MINSIZE
            {
                //分割nb出去,剩余的继续放在reminder和unsorted中

                return chunk2mem(victim);
            }

            //unsorted_bin中多于一块chunk,或者剩余一块但不是上一次分割剩余的
            //或者剩余的一块大小太小,继续向下
            //把最后一块从unsorted freelist中删除
            unsorted_chunks(av)->bk = bck;
            bck->fd = unsorted_chunks(av);

            //如果正好完全匹配,则return
            if (size == nb) {
                set_inuse_bit_at_offset(victim, size);
                check_malloced_chunk(victim, nb);
                return chunk2mem(victim);
            }
            //不是完全匹配就从unsorted_bin移到normal_bin中
            if(in_smallbin_range(size)){}
            else//注意large-bin中的内存块是有序的,FIFO
            {}

        }//end while

        //对于<512bytes的请求,使用best-fit策略查找当前bin
        //注意当前bin是根据应用请求的size直接index定位到的bin
        if (!in_smallbin_range(nb)) {//best-fit
            if ((victim = last(bin)) != bin   //empty   or //first最大,但也不能满足请求
              &&(unsigned long)(first(bin)->size) >= (unsigned long)(nb)) {
                //从后往前找,找到第一个满足请求的
                while (((unsigned long)(size = chunksize(victim)) < (unsigned long)(nb)))
                    victim = victim->bk;

                //如果剩余的大小<MINSIZE不进行分割,直接将该块返回给应用
                if (remainder_size < MINSIZE)  {
                    return chunk2mem(victim);
                }
                else{//分割该块,返回给应用,剩余的块 放入unsorted-bin
                    return chunk2mem(victim);
                }

            }
        }

        //如果unsorted-bin,当前bin都没有满足的,依次查找下一个更大的bin,直到找到一个满足的为止
        for (;;) {
            //这里使用了bitmap技巧快速查找到匹配的large-bin,具体信息可以参考www.vtzone.org其他文章
            //bit > map说明这个32位中bit后面的bin都是空
            /*bit==0??啥意思,index%32==0
               index->bit
                 32  ->1  2^0
                 1    ->2  2^1
                 2    ->4  2^2
                 ...
                 31  ->    2^31
            */
            if (bit > map || bit == 0) {//bit怎么会是0???   //从下一个32开始
                do {
                  if (++block >= BINMAPSIZE)  /* out of bins */
                    goto use_top; //所有bin都找完了还没找到,跳到下面use_top
                } while ( (map = av->binmap[block]) == 0);
               
                bin = bin_at(av, (block << BINMAPSHIFT));
                bit = 1;//从第一位开始
            }
            //......

            //如果找到一块满足的,将该块进行分割
            //如果剩余的大小<MINSIZE不进行分割,直接将该块返回给应用
            if (remainder_size < MINSIZE)  {
                return chunk2mem(victim);
            }
            else{//分割该块,返回给应用,剩余的块 放入unsorted-bin
                return chunk2mem(victim);
            }
        }//end for(;;),遍历normal-bin

        //如果所有bin都没有可用的块
    use_top:
        //如果top足够大,从top取一块return给用户,修改top指针
        if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) {

            return chunk2mem(victim);
        }
        //上面入口处如果请求>512bytes会触发合并fastbin
        //在这里:如果fastbins无法满足,smallbins也无法满足,
        //而后合并fastbins放入unsorted_bins,
        //对于大块再到unsorted_bins找,如果没有精确匹配放入normal_bin
        //然后再到normal_bins找best-fit
        //如果还没找到,扩展top

        //由此可知,如果请求smallsize,则不会触发上述合并fastbins,
        //然后会触发到unsorted_bins查找,只有一块上次剩余的小块才会
        //被分配,或者精确匹配,否则放入normal_bins
        //然后不管larger或small都到normal_bins中查找
        //所以在这里对fastbins合并再尝试一次
        else if (have_fastchunks(av)) {
            assert(in_smallbin_range(nb));
            malloc_consolidate(av);
            idx = smallbin_index(nb); /* restore original bin index */
        }
        else  //top也没法满足,向OS扩展内存
            return sYSMALLOc(nb, av);
        }

    }//最外层的for(;;)

    //end
}

作者: vt.buxiu@www.vtzone.org
 
dlmalloc是目前一个十分流行的内存分配器,其由Doug Lea(主页为http://gee.cs.oswego.edu/)从1987年开始编写,到目前为止,最新版本为2.8.3(可以从ftp://g.oswego.edu/pub/misc/malloc.c获取),由于其高效率等特点被广泛的使用(比如一些linux系统等用的就是dlmalloc或其变形,比如ptmalloc,主页为http://www.malloc.de/en/index.html)和研究(各位可以搜索关键字“GCspy”)。 dlmalloc的实现只有一个源文件(还有一个头文件),大概5000行,其内注释占了大量篇幅,由于有这么多注释存在的情况下,表面上看上去很容易懂,的确如此,在不追求细节的情况,对其大致思想的确很容易了解(没错,就只是了解而已),但是dlmalloc作为一个高品质的佳作,实现上使用了非常多的技巧,在实现细节上不花费一定的精力是没有办法深入理解其为什么这么做,这么做的好处在哪,只有当真正读懂后回味起来才发现它是如此美妙。 lenky0401个人博客将陆续推出对dlmalloc的解析(针对Doug Lea Malloc的最新版Version 2.8.3,未做说明的情况下以32位平台,8字节对齐作为假定平台环境设置考虑),由于个人水平有限,因此也不能完全保证对dlmalloc的所有理解都准备无误, 但是所有内容均出自个人的理解而并非存心妄自揣测来愚人耳目,所以如果读者发现其中有什么错误,请勿见怪,如果可以则请来信告之,并欢迎来信讨论(lenky0401@163.com)。 这一系列文章是lenky0401在看完dlmalloc的大部分代码后的再总结,不能保证对dlmalloc的整体完全把握,贴出这些只是希望可以提前收到对此有研究的网友的指点,以便在最后对这一系列文章整理而形成的PDF文档中错误能少一些。至于对于现在贴出来的内容中包含的错误给大家造成的不便提前说声抱歉。:) 描述的内容不会包含dlmalloc全部代码,但会将这其中涉及到的一些技巧尽量讲出,我相信对dlmalloc源代码不感兴趣的朋友也可以学到这些独立的技巧而使用在自己的编程实践中。:) 最后,转载请保留本博客地址连接[http://lenky0401.cublog.cn],谢谢。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值