现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

glibc free源码分析

2017-11-13 22:51 工业·编程 ⁄ 共 12554字 ⁄ 字号 暂无评论

Public_fREe()

void public_fREe(Void_t* mem)

{  

    mstate ar_ptr;   mchunkptr p;                         

    /* chunk corresponding to mem */

    void (*hook) (__malloc_ptr_t, __const __malloc_ptr_t)    

    = force_reg (__free_hook); 

    if (__builtin_expect (hook != NULL, 0))

    {               

        (*hook)(mem, RETURN_ADDRESS (0));    

        return;  

    }

如果存在__free_hook,执行hook函数。

     if (mem == 0)                             

     /* free(0) has no effect */

     return;

     p = mem2chunk(mem);

free NULL指针直接返回,然后根据内存指针获得chunk的指针。

#if HAVE_MMAP  

     if (chunk_is_mmapped(p))                      

     /* release mmapped memory. */

     {       

     /* see if the dynamic brk/mmap threshold needs adjusting */

         if (!mp_.no_dyn_threshold        

              && p->size > mp_.mmap_threshold        

              && p->size <= DEFAULT_MMAP_THRESHOLD_MAX)               

         {       

              mp_.mmap_threshold = chunksize (p);        

              mp_.trim_threshold = 2 * mp_.mmap_threshold;      

         }      

         munmap_chunk(p);    

         return;  

     }

#endif

如果当前free的chunk是通过mmap()分配的,调用munmap_chunk()函数unmap本chunk。 munmap_chunk()函数调用 munmap()函数释放mmap()分配的内存块。同时查看是否开启了mmap分配阈值动态调整机制,默认是开启的,如果当前free的chunk的大小大于设置的mmap分配阈值,小于mmap分配阈值的最大值,将当前chunk的大小赋值给mmap分配阈值,并修改mmap收缩阈值为mmap分配阈值的2倍。默认情况下mmap分配阈值与mmap收缩阈值相等,都为128KB。

     ar_ptr = arena_for_chunk(p);

根据chunk指针获得分配区的指针。

#ifdef ATOMIC_FASTBINS  

     _int_free(ar_ptr, p, 0);

如果开启了ATOMIC_FASTBINS优化,不需要对分配区加锁,调用_int_free()函数执行实际的释放工作。

#else

#if THREAD_STATS  

     if(!mutex_trylock(&ar_ptr->mutex))    

     ++(ar_ptr->stat_lock_direct);  

     else

     {

        (void)mutex_lock(&ar_ptr->mutex);    

        ++(ar_ptr->stat_lock_wait);  

     }

#else  

     (void)mutex_lock(&ar_ptr->mutex);

#endif  

     _int_free(ar_ptr, p);  

     (void)mutex_unlock(&ar_ptr->mutex);

#endif

}

如果没有开启了ATOMIC_FASTBINS优化,或去分配区的锁,调用_int_free()函数执行实际的释放工作,然后对分配区解锁。

_int_free()

static void

#ifdef ATOMIC_FASTBINS

_int_free(mstate av, mchunkptr p, int have_lock)

#else

_int_free(mstate av, mchunkptr p)

#endif

{  

    INTERNAL_SIZE_T size;        /* its size */

    mfastbinptr*    fb;          /* associated fastbin */

    mchunkptr       nextchunk;   /* next contiguous chunk */

    INTERNAL_SIZE_T nextsize;    /* its size */

    int             nextinuse;   /* true if nextchunk is used */

    INTERNAL_SIZE_T prevsize;    /* size of previous contiguous chunk */

    mchunkptr       bck;         /* misc temp for linking */

    mchunkptr       fwd;         /* misc temp for linking */

    const char *errstr = NULL;

#ifdef ATOMIC_FASTBINS  

    int locked = 0;

#endif

    size = chunksize(p);

获取需要释放的chunk的大小。

    if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)      

        || __builtin_expect (misaligned_chunk (p), 0))    

    {      

        errstr = "free(): invalid pointer";    

errout:

#ifdef ATOMIC_FASTBINS      

    if (! have_lock && locked)        

        (void)mutex_unlock(&av->mutex);

#endif

    malloc_printerr (check_action, errstr, chunk2mem(p));      

    return;    

    }  

    /* We know that each chunk is at least MINSIZE bytes in size.  */

    if (__builtin_expect (size < MINSIZE, 0))    

    {      

        errstr = "free(): invalid size";      

        goto errout;    

    }

    check_inuse_chunk(av, p); 

上面的代码用于安全检查,chunk的指针地址不能溢出,chunk的大小必须大于等于MINSIZE。

  /*

     If eligible, place chunk on a fastbin so it can be found

     and used quickly in malloc.

   */

   if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())

 

#if TRIM_FASTBINS      

        /*

         If TRIM_FASTBINS set, don't place chunks

         bordering top into fastbins

       */

       && (chunk_at_offset(p, size) != av->top)

#endif)

   {

        if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)        

            || __builtin_expect (chunksize (chunk_at_offset (p, size))                                         

                >= av->system_mem, 0))      

        {

#ifdef ATOMIC_FASTBINS        

        /* We might not have a lock at this point and concurrent modifications

            of system_mem might have let to a false positive.  Redo the test

            after getting the lock.  */

            if (have_lock            

                || ({ assert (locked == 0);                  

                      mutex_lock(&av->mutex);                  

                      locked = 1;                  

                      chunk_at_offset (p, size)->size <= 2 * SIZE_SZ                    

                        || chunksize (chunk_at_offset (p, size)) >= av->system_mem;                      

                }))

 

#endif          

            {            

                errstr = "free(): invalid next size (fast)";            

                goto errout;          

            }

#ifdef ATOMIC_FASTBINS        

            if (! have_lock)          

            {            

                (void)mutex_unlock(&av->mutex);            

                locked = 0;          

            }

#endif      

        }

如果当前free的chunk属于fast bins,查看下一个相邻的chunk的大小是否小于等于2*SIZE_SZ,下一个相邻chunk的大小是否大于分配区所分配的内存总量,如果是,报错。

        if (__builtin_expect (perturb_byte, 0))      

            free_perturb (chunk2mem(p), size - SIZE_SZ);

        set_fastchunks(av);    

        unsigned int idx = fastbin_index(size);    

        fb = &fastbin (av, idx);

设置当前分配区的fast bin flag,表示当前分配区的fast bins中已有空闲chunk。然后根据当前free的chunk大小获取所属的fast bin。

#ifdef ATOMIC_FASTBINS    

        mchunkptr fd;    

        mchunkptr old = *fb;    

        unsigned int old_idx = ~0u;    

        do      

        {        

         /* Another simple check: make sure the top of the bin is not the

             record we are going to add (i.e., double free).  */

            if (__builtin_expect (old == p, 0))          

            {            

                errstr = "double free or corruption (fasttop)";            

                goto errout;          

            }        

            if (old != NULL)

                old_idx = fastbin_index(chunksize(old));        

            p->fd = fd = old;      

        }while ((old = catomic_compare_and_exchange_val_rel (fb, p, fd)) != fd);

        if (fd != NULL && __builtin_expect (old_idx != idx, 0))      

        {        

            errstr = "invalid fastbin entry (free)";       

            goto errout;

        }     

如果开启了ATOMIC_FASTBINS优化,使用lock-free技术实现fast bin的单向链表插入操作。

#else         

        if (__builtin_expect (*fb == p, 0))      

        {        

            errstr = "double free or corruption (fasttop)";        

            goto errout;      

        }    

        if (*fb != NULL        

            && __builtin_expect (fastbin_index(chunksize(*fb)) != idx, 0))      

        {        

            errstr = "invalid fastbin entry (free)";        

            goto errout;      

        }

        p->fd = *fb; *fb = p

#endif

    }

如果没有开启了ATOMIC_FASTBINS优化,将free的chunk加入fast bin的单向链表中, 修改过链表表头为当前free的chunk。同时需要校验是否为double free错误,校验表头不为NULL情况下,保证表头chunk的所属的fast bin与当前free的chunk所属的fast bin相同。

    else if (!chunk_is_mmapped(p))

    {

        #ifdef ATOMIC_FASTBINS

        if (! have_lock)

        {

# if THREAD_STATS      

            if(!mutex_trylock(&av->mutex))        

                ++(av->stat_lock_direct);      

            else

            {        

                (void)mutex_lock(&av->mutex);        

                ++(av->stat_lock_wait);      

            }

# else      

            (void)mutex_lock(&av->mutex);

# endif      

            locked = 1;    

        }

#endif 

如果当前 free 的 chunk 不是通过 mmap()分配的,并且当前还没有获得分配区的锁,获取分配区的锁。

nextchunk = chunk_at_offset(p, size);

获取当前 free 的 chunk 的下一个相邻的 chunk。

        if (__builtin_expect (p == av->top, 0))      

        {        

            errstr = "double free or corruption (top)";        

            goto errout;      

        }    

        /* Or whether the next chunk is beyond the boundaries of the arena.  */

        if (__builtin_expect (contiguous (av)                          

            && (char *) nextchunk                          

            >= ((char *) av->top + chunksize(av->top)), 0))      

        {        

            errstr = "double free or corruption (out)";        

            goto errout;      

        }    

        /* Or whether the block is actually not marked used.  */

        if (__builtin_expect (!prev_inuse(nextchunk), 0))      

        {        

            errstr = "double free or corruption (!prev)";        

            goto errout;      

        }

安全检查,当前 free 的 chunk 不能为 top chunk,因为 top chunk 为空闲 chunk,如果再次 free 就可能为double free 错误了。如果当前 free 的 chunk 是通过 sbrk()分配的,并且下一个相邻的 chunk 的地址已经超过了 top chunk 的结束地址,超过了当前分配区的结束地址,报错。如果当前 free 的 chunk 的下一个相邻 chunk

的 size 中标志位没有标识当前 free chunk 为 inuse 状态,可能为 double free 错误。

        nextsize = chunksize(nextchunk);    

        if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)        

            || __builtin_expect (nextsize >= av->system_mem, 0))      

        {        

            errstr = "free(): invalid next size (normal)";        

            goto errout;      

        }

        if (__builtin_expect (perturb_byte, 0))      

            free_perturb (chunk2mem(p), size - SIZE_SZ);

计算当前 free 的 chunk 的下一个相邻 chunk 的大小,该大小如果小于等于 2*SIZE_SZ 或是大于了分配区所分配区的内存总量,报错。

        /* consolidate backward */

        if (!prev_inuse(p))

        {      

            prevsize = p->prev_size;      

            size += prevsize;      

            p = chunk_at_offset(p, -((long) prevsize));      

            unlink(p, bck, fwd);    

        }

如果当前 free 的 chunk 的前一个相邻 chunk 为空闲状态,与前一个空闲 chunk 合并。计算合并后的 chunk 大小,并将前一个相邻空闲 chunk 从空闲 chunk 链表中删除。

        if (nextchunk != av->top)

        {      

            /* get and clear inuse bit */

           nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

如果与当前 free 的 chunk 相邻的下一个 chunk 不是分配区的 top chunk,查看与当前 chunk 相邻的下一个 chunk 是否处于 inuse 状态。

            /* consolidate forward */

            if (!nextinuse)

            {        

                unlink(nextchunk, bck, fwd);        

                size += nextsize;      

            }

            else        

                clear_inuse_bit_at_offset(nextchunk, 0);

如果与当前 free 的 chunk 相邻的下一个 chunk 处于 inuse 状态,清除当前 chunk 的 inuse 状态,则当前 chunk 空闲了。否则,将相邻的下一个空闲 chunk 从空闲链表中删除,并计算当前 chunk 与下一个 chunk 合并后的 chunk 大小。

            bck = unsorted_chunks(av);      

            fwd = bck->fd;      

            if (__builtin_expect (fwd->bk != bck, 0))        

            {          

                errstr = "free(): corrupted unsorted chunks";          

                goto errout;        

            }      

            p->fd = fwd;      

            p->bk = bck;      

            if (!in_smallbin_range(size))        

            {          

                p->fd_nextsize = NULL;          

                p->bk_nextsize = NULL;        

            }      

            bck->fd = p;      

            fwd->bk = p;

将合并后的 chunk 加入 unsorted bin 的双向循环链表中。如果合并后的 chunk 属于 large bins,将 chunk 的 fd_nextsize 和 bk_nextsize 设置为 NULL。

            set_head(p, size | PREV_INUSE);      

            set_foot(p, size);

设置合并后的空闲 chunk 大小,并标识前一个 chunk 处于 inuse 状态,因为必须保证不能有两个相邻的 chunk 都处于空闲状态。然后将合并后的 chunk 加入 unsorted bin 的双向循环链表中。最后设置合并后的空闲 chunk 的 foot,chunk 空闲时必须设置 foot,该 foot 处于下一个 chunk 的 prev_size 中,只有 chunk 空闲是 foot 才是有效的。

            check_free_chunk(av, p);    

        }

 

            /*

           If the chunk borders the current high end of memory,

           consolidate into top

           */

        else

        {      

            size += nextsize;      

            set_head(p, size | PREV_INUSE);      

            av->top = p;      

            check_chunk(av, p);    

        }

如果当前 free 的 chunk 下一个相邻的 chunk 为 top chunk,则将当前 chunk 合并入 top chunk,修改 top chunk 的大小。

        if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD)

        {      

            if (have_fastchunks(av))        

                malloc_consolidate(av);

如果合并后的 chunk 大小大于 64KB,并且 fast bins 中存在空闲 chunk,调用 malloc_consolidate()函数合并 fast bins 中的空闲 chunk 到 unsorted bin 中。

            if (av == &main_arena)

            {

#ifndef MORECORE_CANNOT_TRIM        

            if ((unsigned long)(chunksize(av->top)) >=            

                (unsigned long)(mp_.trim_threshold))          

                sYSTRIm(mp_.top_pad, av);

#endif

如果当前分配区为主分配区,并且top chunk的大小大于heap的收缩阈值,调用sYSTRIm() 函数首先 heap。

            else

            {        

            /* Always try heap_trim(), even if the top chunk is not

               large, because the correspo nding heap might go away.  */

                heap_info *heap = heap_for_ptr(top(av));

                assert(heap->ar_ptr == av);        

                heap_trim(heap, mp_.top_pad);

            }

        }

如果为非主分配区,调用 heap_trim()函数收缩非主分配区的 sub_heap。

#ifdef ATOMIC_FASTBINS    

        if (! have_lock)

        {      

            assert (locked);      

            (void)mutex_unlock(&av->mutex);    

        }

    }

#endif

如果开启了 ATOMIC_FASTBINS 优化并获得分配区的锁,则对分配区解锁。

    else

    {

#if HAVE_MMAP

        munmap_chunk (p);

#endif

    }

}

如果当前 free 的 chunk 是通过 mmap()分配的,调用 munma_chunk()释放内存。

check

1.释放chunk的时候,chunk必须按2size_sz对齐,chunk1的大小必须大于等于MINSIZE且指针地址不能溢出。

2.释放fast bin的时候,chunk必须大于2SIZE_SZ且小于分配区所分配的内存总量。

3.释放fast bin时,检查当前free的chunk是否是fast bin中的链表头(double free),以及当前free的chunk的size要与相应的fast bin一致。

4.释放chunk的时候,chunk不能为top chunk,next chunk的地址不能超过当前分配区结束的地址,以及next chunk中chunk的inuse标志位需置1。

5.当前 free 的 chunk 的下一个相邻 chunk 的大小需要大于 2*SIZE_SZ 且小于分配区所分配区的内存总量。

6.释放的chunk通过unlink脱链,注意unlink的检查。

7.将free掉的chunk放入unsorted bin中时,有unsorted_chunks(av)->fd->bk == unsorted_chunks(av)的检查。

总结

free大致步骤:

1.判断是否有_free_hook,有则执行hook函数。

2.判断是否是mmap chunk,是则调用munmap_chunk释放(同时可能做调整阈值操作),否则调用_int_free()。

3.判断是否是fast bin,是则插入fast bin链表中(inuse值不置0)。

4.如果不是mmap chunk,判断相邻chunk是否为空闲,是就合并(top chunk除外),将合并后的chunk插入unsorted bin链表中。

5.如果跟top chunk相邻,则合并入top chunk。

6.依据heap的情况可能执行合并fast bin、收缩阈值以及收缩sub_heap等操作。

7.判断是否还存在mmap chunk,是则调用munmap_chunk释放。

内容来源

《glibc内存管理ptmalloc源码分析》

给我留言

留言无头像?