free源码分析---2

free源码分析—_int_free

根据上一章的分析可知,如果一个chunk不是由mmap分配得到,就会调用_int_free进行释放。下面来看,

void __libc_free(void *mem) {

    ...

    p = mem2chunk(mem);
    if (chunk_is_mmapped(p)){
        ...
    }

    ar_ptr = arena_for_chunk(p);
    _int_free(ar_ptr, p, 0);
}

_int_free第一部分

首先来看_int_free第一部分,为了便于分析,这里省略了一些不关键的代码,

static void _int_free(mstate av, mchunkptr p, int have_lock) {
    INTERNAL_SIZE_T size;
    mfastbinptr *fb;
    mchunkptr nextchunk;
    INTERNAL_SIZE_T nextsize;
    int nextinuse;
    INTERNAL_SIZE_T prevsize;
    mchunkptr bck;
    mchunkptr fwd;

    const char *errstr = NULL;
    int locked = 0;
    size = chunksize(p);

    if (__glibc_unlikely(size < MINSIZE || !aligned_OK (size))) {
        goto errout;
    }

    check_inuse_chunk(av, p);

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

        free_perturb(chunk2mem(p), size - 2 * SIZE_SZ);
        set_fastchunks(av);
        unsigned int idx = fastbin_index(size);
        fb = &fastbin(av, idx);

        mchunkptr old = *fb, old2;
        unsigned int old_idx = ~0u;
        do {
            if (have_lock && old != NULL)
                old_idx = fastbin_index(chunksize(old));
            p->fd = old2 = old;
        } while ((old = catomic_compare_and_exchange_val_rel(fb, p, old2)) != old2);
    }

    ...

}

第一部分首先是检查size变量的合法性,然后比较get_max_fast()判断size是否在fastbin的范围内,如果在fastbin的管理范围内,就通过set_fastchunks设置分配区的标志位表示fastbin有空闲chunk,接下来根据size获得即将添加的chunk在fastbin中的索引idx,并通过该索引获得头指针fb,最后通过CAS操作将该chunk添加到fastbin中。这里需要注意fastbin中存放的chunk是按照单向链表组织的。

_int_free第二部分

继续往下看,为了使整个代码结构清晰,这里保留了上一部分的if,

static void _int_free(mstate av, mchunkptr p, int have_lock) {

    ...

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

        ...

    }
    else if (!chunk_is_mmapped(p)) {

        nextchunk = chunk_at_offset(p, size);
        nextsize = chunksize(nextchunk);

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

        if (!prev_inuse(p)) {
            prevsize = p->prev_size;
            size += prevsize;
            p = chunk_at_offset(p, -((long ) prevsize));
            unlink(av, p, bck, fwd);
        }

        if (nextchunk != av->top) {
            nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

            if (!nextinuse) {
                unlink(av, nextchunk, bck, fwd);
                size += nextsize;
            } else
                clear_inuse_bit_at_offset(nextchunk, 0);

            bck = unsorted_chunks(av);
            fwd = bck->fd;
            if (__glibc_unlikely(fwd->bk != bck)) {
                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;

            set_head(p, size | PREV_INUSE);
            set_foot(p, size);

            check_free_chunk(av, p);
        }

        else {
            size += nextsize;
            set_head(p, size | PREV_INUSE);
            av->top = p;
            check_chunk(av, p);
        }

        ...

    }

    ...

}

如果将要释放的chunk不属于fastbin,且不是由mmap分配的,就首先获得下一个chunk的指针nextchunk和大小nextsize,如果前一个chunk空闲,就和前一个chunk合并,并通过unlink将该chunk从空闲链表中脱离。接下来,如果刚才前面取出的下一个chunk也为空闲,并且该chunk不是top chunk,则继续合并,否则将其设为空闲。再往下,就是取出unsortedbin的头指针,将合并后的chunk插入unsortedbin链表头部,并进行相应的设置。
如果下一个chunk为top chunk,就将要释放的chunk合并到top chunk中。

_int_free第三部分

继续往下看,

static void _int_free(mstate av, mchunkptr p, int have_lock) {

    ...

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

        ...

    }
    else if (!chunk_is_mmapped(p)) {

        ...

        if ((unsigned long) (size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
            if (have_fastchunks(av))
                malloc_consolidate(av);

            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
            } else {
                heap_info *heap = heap_for_ptr(top(av));
                heap_trim(heap, mp_.top_pad);
            }
        }
        if (!have_lock) {
            assert(locked);
            (void) mutex_unlock(&av->mutex);
        }
    }
    else {
        munmap_chunk(p);
    }
}

如果前面释放的chunk比较大,就需要做一些处理了。首先对fastbin中的chunk进行合并并添加到unsortedbin中。然后,如果是主分配区,并且主分配区的top chunk大于一定的值,就通过systrim缩小top chunk。如果是非主分配区,就获得top chunk对应的非主分配区的heap_info指针,调用heap_trim尝试缩小该heap。后面来看systrimheap_trim这两个函数。
最后,说明chunk还是通过mmap分配的,就调用munmap_chunk释放它。munmap_chunk函数已经在上一章介绍了。

systrim

systrim用于缩小主分配区的top chunk大小,下面来看,

static int systrim(size_t pad, mstate av) {
    long top_size;
    long extra;
    long released;
    char *current_brk;
    char *new_brk;
    size_t pagesize;
    long top_area;

    pagesize = GLRO(dl_pagesize);
    top_size = chunksize(av->top);

    top_area = top_size - MINSIZE - 1;
    if (top_area <= pad)
        return 0;

    extra = (top_area - pad) & ~(pagesize - 1);

    if (extra == 0)
        return 0;

    current_brk = (char *) (MORECORE(0));
    if (current_brk == (char *) (av->top) + top_size) {

        MORECORE(-extra);
        void (*hook)(void) = atomic_forced_read (__after_morecore_hook);
        if (__builtin_expect(hook != NULL, 0))
            (*hook)();
        new_brk = (char *) (MORECORE(0));

        LIBC_PROBE (memory_sbrk_less, 2, new_brk, extra);

        if (new_brk != (char *) MORECORE_FAILURE) {
            released = (long) (current_brk - new_brk);

            if (released != 0) {
                av->system_mem -= released;
                set_head(av->top, (top_size - released) | PREV_INUSE);
                check_malloc_state (av);
                return 1;
            }
        }
    }
    return 0;
}

首先,如果主分配区的top chunk本来就没什么空间,就直接返回,否则就将主分配区中可以缩小的大小保存在extra中。下面检查当前堆的brk指针是否和top chunk的结束地址相等,如果相等就可以通过MORECORE降低堆的大小,MORECORE是brk的系统调用,最后也是通过do_munmap释放虚拟内存的。__after_morecore_hook函数指针为空,不管它。再下来,获得释放后的堆指针保存在new_brk中,计算释放的虚拟内存的大小released,并将该信息更新到主分配区中,然后设置新top chunk的size

heap_trim

heap_trim用来缩小非主分配区的heap大小,下面来看,

static int
internal_function heap_trim(heap_info *heap, size_t pad) {
    mstate ar_ptr = heap->ar_ptr;
    unsigned long pagesz = GLRO(dl_pagesize);
    mchunkptr top_chunk = top(ar_ptr), p, bck, fwd;
    heap_info *prev_heap;
    long new_size, top_size, top_area, extra, prev_size, misalign;

    while (top_chunk == chunk_at_offset(heap, sizeof(*heap))) {
        prev_heap = heap->prev;
        prev_size = prev_heap->size - (MINSIZE - 2 * SIZE_SZ);
        p = chunk_at_offset(prev_heap, prev_size);
        misalign = ((long) p) & MALLOC_ALIGN_MASK;
        p = chunk_at_offset(prev_heap, prev_size - misalign);
        p = prev_chunk(p);
        new_size = chunksize(p) + (MINSIZE - 2 * SIZE_SZ) + misalign;
        if (!prev_inuse(p))
            new_size += p->prev_size;
        if (new_size + (HEAP_MAX_SIZE - prev_heap->size)
                < pad + MINSIZE + pagesz)
            break;
        ar_ptr->system_mem -= heap->size;
        arena_mem -= heap->size;
        delete_heap(heap);
        heap = prev_heap;
        if (!prev_inuse(p)){
            p = prev_chunk(p);
            unlink(ar_ptr, p, bck, fwd);
        }
        top (ar_ptr) = top_chunk = p;
        set_head(top_chunk, new_size | PREV_INUSE);
    }

    top_size = chunksize(top_chunk);
    top_area = top_size - MINSIZE - 1;
    if (top_area < 0 || (size_t) top_area <= pad)
        return 0;

    extra = ALIGN_DOWN(top_area - pad, pagesz);
    if ((unsigned long) extra < mp_.trim_threshold)
        return 0;

    if (shrink_heap(heap, extra) != 0)
        return 0;

    ar_ptr->system_mem -= extra;
    arena_mem -= extra;

    set_head(top_chunk, (top_size - extra) | PREV_INUSE);
    return 1;
}

第一个while表示,如果top chunk指针正好在heap_info上,则考虑删掉整个heap。这是因为此时,该heap只有一个top chunk。再删掉该heap之前,需要检查该heap的前一个heap是否有足够的空间,否则删掉该heap后,剩余的空间太小。
经过计算后,newsize保存了前一个heap高地址处的fencepost和前一个空闲chunk(如果存在)的总大小组成,如果newsize加上该heap还未使用的内存(HEAP_MAX_SIZE - prev_heap->size)太小,就break退出循环,取消对整个heap的释放。否则,在更新了相应的信息后,调用delete_heap删除整个heap,delete_heap是一个宏,定义如下

#define delete_heap(heap) \
  do {                                        \
      if ((char *) (heap) + HEAP_MAX_SIZE == aligned_heap_area)           \
        aligned_heap_area = NULL;                         \
      __munmap ((char *) (heap), HEAP_MAX_SIZE);                  \
    } while (0)

delete_heap其最终通过__munmap释放整个heap,大小为HEAP_MAX_SIZE
删除掉整个heap后,如果前一个heap的fencepost的前面有一个空闲chunk,就将该空闲chunk从空闲链表中脱离,然后设置fencepost或者该空闲chunk(如果存在)的地址为新的top chunk,该top chunk的大小为前面计算的new_size
然后返回while继续检查,如果新的top chunk指针又正好在heap_info上,就表示该heap也就只有一个chunk即top chunk,就继续释放该heap。
再往下,如果新的top chunk剩余空间top_area太小,就直接返回了。如果还有足够的空间,且top_area大于收缩阀值,就调用shrink_heap进一步将新的top chunk的大小减少extra。最后设置一些分配区的信息,并设置减少后的top chunk的大小为top_size - extra

static int shrink_heap(heap_info *h, long diff) {
    long new_size;

    new_size = (long) h->size - diff;
    if (new_size < (long) sizeof(*h))
        return -1;

    h->size = new_size;
    return 0;
}

这里其实就是减小heap_infosize变量。

总结

下面对整个_int_free函数做个总结。
首先检查将要释放的chunk是否属于fastbin,如果属于就将其添加到fastbin中。
然后检查该chunk是否是由mmap分配的,如果不是,就根据其下一个chunk的类型添加到unsortedbin或者合并到top chunk中。
接着,如果释放的chunk的大小大于一定的阀值,就需要通过systrim缩小主分配区的大小,或者通过heap_trim缩小非主分配区的大小。
最后如果该chunk是由mmap的分配的,通过munmap_chunk释放。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeCAD 是一款开的三维建模软件,我们可以使用它进行各种复杂的三维设计和建模任务。分析是对软件代码的深入研究,以了解软件的内部工作原理和实现细节。 首先,进行 FreeCAD 分析的主要目的之一是理解软件的整体架构和模块。通过仔细研究代码,我们可以了解软件是如何组织和管理不同的功能模块的,这对于学习软件的整体设计思想和流程控制非常重要。 其次,分析可以帮助我们理解软件内部的数据结构和算法。FreeCAD 使用多种数据结构来表示三维模型和相关数据,例如,顶点列表、边列表和面列表等。通过分析码,我们可以了解这些数据结构如何实现和组织,以及它们在软件中的具体应用。 此外,分析还可以帮助我们理解软件的实现细节和优化技巧。通过研究代码,我们可以了解开发者是如何解决各种问题和挑战的。例如,我们可以看到他们在算法实现、图形渲染和性能优化方面的技巧和策略。 最后,分析促进了开社区的发展。通过深入研究软件的代码,我们可以了解软件的工作原理,并可以为项目做出贡献。我们可以提交 bug 报告、提供改进建议、编写文档或贡献代码等,从而推动软件的进一步发展和改进。 综上所述,对 FreeCAD 码进行分析是了解软件内部工作原理和实现细节的关键步骤。通过分析,我们可以深入学习软件的整体架构、数据结构、算法和优化技巧,并为其发展做出贡献。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值