0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

内存释放free步骤

科技绿洲 来源:Linux开发架构之路 作者:Linux开发架构之路 2023-11-09 11:31 次阅读

内存释放free

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的大小必须是按是按 2*SIZE_SZ 对齐的且大于等于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错误,即free的chunk不能是表头的chunk。同时校验表头不为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必须按2 size_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释放。

二次分析后总结

1.有_free_hook运行_free_hook,没有则通过用户指针-2获得chunk的指针。 2.如果内存是通过mmap()分配的,调用munmap_chunk()函数unmap chunk,同时动态调整阈值。 3.通过size位获得chunk的大小,size的大小必须是按 2 SIZE_SZ 对齐并且大于等于MINSIZE的。4.如果size在fastbin范围内,通过chunk的size得到nextchunk的指针,并通过nextchunk的size域判断nextchunk的size不能小于等于2SIZE_SZ,不能大于等于分配区所分配的内存总量。 5.根据chunk的size获得fastbin对应的idx,根据idx获得fastbin对应的表头,这里要保证free的chunk不能是fastbin表头的chunk,同时保证表头chunk对应的idx与free chunk对应的idx相同。这时就将free chunk插入到表头,通过fd指针链接原来的表头chunk。 6.如果chunk不是mmap分配的,通过size获得nextchunk的指针,这里保证free chunk不能是top chunk,nextchunk不能超过当前分配区的地址,并且nextchunk中的prev_size位不能为0。 7.通过nextchunk的size位获得nextchunk的大小,保证nextchunk的大小在2*SIZE_SZ和分配区所分配的内存总量之间。 8.通过prev_size位判断前一个chunk为空闲,则合并,通过prev_size获得新的chunk的指针,并将前一个chunk从空闲链表中脱链。 9.通过下下个chunk的inuse位判断nextchunk是否处于空闲状态,如果处于是使用状态,则将nextchunk里的inuse置0,即表明当前chunk空闲,如果nextchunk处于空闲状态,且nextchunk不是topchunk,则将nextchunk脱链,合并到chunk中。 10.将chunk加入到unsortedbin双向循环链表的头部,并设置对应指针,注意这里在chunk加入之前必须保证unsorted bin成环的。 11.设置合并后的空闲chunk大小,并标识前一个chunk处于inuse状态,再设置设置合并后的空闲chunk的foot。 12.如果nextchunk为top chunk,合并到top chunk中,并修改相应的size和标志位 13.依据heap的情况可能执行合并fast bin、收缩阈值以及收缩sub_heap等操作。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内存
    +关注

    关注

    8

    文章

    3001

    浏览量

    73883
  • Free
    +关注

    关注

    0

    文章

    16

    浏览量

    11077
  • 函数
    +关注

    关注

    3

    文章

    4306

    浏览量

    62430
  • 代码
    +关注

    关注

    30

    文章

    4748

    浏览量

    68349
收藏 人收藏

    评论

    相关推荐

    ESP32CE MQTT断线重连后内存释放怎么解决?

    idf v5.2.1 开启mqtt 连接手机wifi 然后关闭数据流量和打开数据流量(模拟网络超时)esp_get_free_heap_size 获取到的内存越来越小 ,只有wifi重连才会释放
    发表于 07-19 07:12

    关于内存释放

    刚看到坛子里有个朋友说两个while循环就让内存占用达到100%,而一个就没问题,我怀疑是死循环或者内存释放,大家有兴趣的也来讨论下,关于labview内存
    发表于 06-15 09:05

    使用malloc()和 free()函数动态的分配/释放内存的危害

    前言本文会从以下几个方面阐述使用malloc()和 free()函数动态的分配/释放内存的危害。存在的问题在嵌入式中无法很难实现对内存的动态映射(虚拟
    发表于 12-14 07:56

    可以使用malloc()和free()这两个函数动态分配内存释放内存

    在ANSI C中,可以使用malloc()和free()这两个函数动态分配内存释放内存,但是,在嵌入式操作系统中,调用malloc()和free
    发表于 12-17 08:26

    请问该如何释放slab的内存

    1046A的内存不断增长,最后会死机。请问该如何释放slab的内存??实际进程使用的内存只有500M左右。root@localhost:~# ps aux|awk '{sum+=$6}
    发表于 01-05 07:33

    单片机中的动态内存释放指针相关资料下载

    **注:**这只是工作中遇到的问题记录**注:**以下只代表个人观点,不一定完全正确。**注:**以下内容为通过程序确定,但不排除会与其他文章或者记录雷同,如有侵权free函数释放后,释放掉的
    发表于 01-13 07:47

    使用rt_free释放内存时出现to free a bad data block:的错误是为什么?

    最近在使用rt_free释放内存时会出现to free a bad data block:的错误。期初怀疑重复释放
    发表于 03-17 09:24

    请教内存释放是否是在rt_free后就完成呢

    问题描述:1.在线程中调用了一个函数A,该函数会申请内存函数结束前会再释放。测试中发现这个函数在线程中调用几次后就提示异常,申请不到内存。2:请教:内存
    发表于 08-29 11:41

    如何释放电脑内存

    如何释放电脑内存 电脑在用久以后,大家是不是感觉到速度越来越慢?想释放内存还必须重启计算机,是不是很麻烦?本人介绍一种
    发表于 03-01 10:23 2605次阅读

    基于SLUB的DEBUG功能,如何帮忙检测内存越界和访问已经释放内存

    SLAB内存分配器-SLUB的DEBUG功能,如何帮忙检测内存越界(out-of-bounds)和访问已经释放内存(use-after-free
    的头像 发表于 02-08 14:11 9601次阅读
    基于SLUB的DEBUG功能,如何帮忙检测<b class='flag-5'>内存</b>越界和访问已经<b class='flag-5'>释放</b>的<b class='flag-5'>内存</b>

    STM32内存管理

    要实现 2 个函数: malloc 和 free; malloc 函数用于内存申请, free 函数用于内存释放。介绍一种简单的实现方法,分
    发表于 12-24 19:37 13次下载
    STM32<b class='flag-5'>内存</b>管理

    malloc和free简介及实现方式说明

    malloc 分配指定大小的内存空间,返回一个指向该空间的指针。大小以字节为单位。返回 void* 指针,需要强制类型转换后才能引用其中的值。 free 释放一个由 malloc 所分配的内存
    的头像 发表于 05-14 09:56 4520次阅读
    malloc和<b class='flag-5'>free</b>简介及实现方式说明

    free释放内存的时候,为什么不需要指定内存的大小?

    malloc在申请内存的时候,需要指定内存的大小,申请成功则返回这块内存的地址,但是free的时候,只需要指定释放
    的头像 发表于 09-15 17:05 1605次阅读
    <b class='flag-5'>free</b>在<b class='flag-5'>释放</b><b class='flag-5'>内存</b>的时候,为什么不需要指定<b class='flag-5'>内存</b>的大小?

    linux free命令原理

    和分析系统内核的内存信息来显示内存使用情况。在Linux系统中,内核维护了一个内存统计信息表(或称为"proc"文件),其中包含了关于系统内存使用的详细信息。
    的头像 发表于 11-08 11:23 536次阅读

    常用的解决内存错误的方法

    :同一块内存释放两次 • use after free内存释放后使用 • wild free
    的头像 发表于 11-10 15:29 1453次阅读
    常用的解决<b class='flag-5'>内存</b>错误的方法