GLIBC内存管理源码分析

本文深入探讨了glibc内存管理的细节,包括公共接口定义、内存管理最小单元malloc_chunk的结构,以及malloc_state如何管理内存分配。文章详细解释了fastbins、smallbins、largebins和unsortedbins的工作原理,以及malloc和free过程中的具体实现。

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

1. glibc公共接口
#define SIZE_SZ sizeof(size_t) //32位机下位4个字节,64位机下位8个字节

#define MALLOC_ALIGNMENT 16 //不管是32位还是64位,均为16

#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)

#define MIN_CHUNK_SIZE  (offsetof(struct malloc_chunk, fd_nextsize)) //在32位机下大小为16字节,64位机位32字节

/*! 32位为16,64位为32 */
#define MINSIZE  \
  (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

/*! request2size要求16字节对齐,即大小能被16整除;在32位机下最小值为16,64位机为32 */
#define request2size(req)                                         \
  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
   MINSIZE :                                                      \
   ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

#define NBINS 128

------------------------------------------------------------------------------------------------------------------------------
2. glibc内存管理最小单元,即每个被分配给用户的内存,在glibc内部都具有一个对应的malloc_chunk块由glibc进行统一的管理。
struct malloc_chunk {
    INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk (if free).  */
    INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */

    struct malloc_chunk* fd;         /* double links -- used only if free. */
    struct malloc_chunk* bk;

    struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
    struct malloc_chunk* bk_nextsize;
};

------------------------------------------------------------------------------------------------------------------------------
2.1. nchunk_size
该字段由两部分组成,后3bit位分别代表A、M和P,A表示是否是主arena,M表示是否是mmap所分配,P代表前一个chunk是否在使用。剩余的其余bit表示当前chunk的大小。
对主arean所分配的内存,都有sbrk系统调用来完成;除主arean外的所有arean为线程arean,线程内存分配均有mmap来完成。

------------------------------------------------------------------------------------------------------------------------------
2.2. mchunk_prev_size
该字段表示前一个chunk的大小,只有当当前P bit位为0时,mchunk_prev_size才有效,当前chunk可通过偏移mchunk_prev_size长度可找到前一个chunk。找到前一个chunk块后,可将当前chunk与上一个chunk进行合并。

------------------------------------------------------------------------------------------------------------------------------
3. struct malloc_state
该结构是用来管理malloc_chunk的主体结构,所有被分配给用户的内存块,在free后都由malloc_state来进行管理。
struct malloc_state {
    /* Serialize access.  */
    __libc_lock_define (, mutex);

    /* Flags (formerly in max_fast).  */
    int flags;

    /* Set if the fastbin chunks contain recently inserted free blocks.  */
    /* Note this is a bool but not all targets support atomics on booleans.  */
    int have_fastchunks;

    /* Fastbins */
    mfastbinptr fastbinsY[NFASTBINS];

    /* Base of the topmost chunk -- not otherwise kept in a bin */
    mchunkptr top;

    /* The remainder from the most recent split of a small request */
    mchunkptr last_remainder;

    /* Normal bins packed as described above */
    mchunkptr bins[NBINS * 2 - 2];

    /* Bitmap of bins */
    unsigned int binmap[BINMAPSIZE];

    /* Linked list */
    struct malloc_state *next;

    /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
    struct malloc_state *next_free;

    /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
    INTERNAL_SIZE_T attached_threads;

    /* Memory allocated from the system in this arena.  */
    INTERNAL_SIZE_T system_mem;
    INTERNAL_SIZE_T max_system_mem;
}

------------------------------------------------------------------------------------------------------------------------------
3.1. fastbinsY[NFASTBINS]
fastbinsY是一个数组,数组大小计算如下:

#define fastbin_index(sz) ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

/* The maximum fastbin request size we support */
#define MAX_FAST_SIZE   (80 * SIZE_SZ / 4) //在32位机下值为80;64位为160

------------------------------------------------------------------------------------------------------------------------------
3.1.1. fastbinsY数组大小
/*!
 * 32位机的情况:
 *     request2size (MAX_FAST_SIZE) = 96
 *     fastbin_index (request2size (MAX_FAST_SIZE)) //将96右移3位得12,减2得10
 *     fastbin_index (request2size (MAX_FAST_SIZE)) + 1 //结果为10 + 1 = 11
 *     综上可知,NFASTBINS = 11,即fastbinsY是一个大小为11的数组
 * 64位机的情况:
 *     request2size (MAX_FAST_SIZE) = 176
 *     fastbin_index (request2size (MAX_FAST_SIZE)) //将176右移4位得11,减2得9
 *     fastbin_index (request2size (MAX_FAST_SIZE)) + 1 //结果为9 + 1 = 10
 *     综上可知,NFASTBINS = 10,即fastbinsY是一个大小为10的数组
 */
#define NFASTBINS  (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)

------------------------------------------------------------------------------------------------------------------------------
3.1.2. fastbinsY chunk范围
/*! 32位为64字节,64位为128字节 */
#define DEFAULT_MXFAST     (64 * SIZE_SZ / 4)

#define SMALLBIN_WIDTH  MALLOC_ALIGNMENT

/*! 32位为64字节,64位为128字节 */
#define set_max_fast(s) \
    global_max_fast = (((s) == 0) \
    ? SMALLBIN_WIDTH : ((s + SIZE_SZ) & ~MALLOC_ALIGN_MASK))

3.1.3. fastbinsY最多能用多少个??
根据fastbin_index可知:32位和64位系统最对能用7个
它们之间的大小情况如下:
    32位                         64位
    fastbinsY[0] = 16            fastbinsY[0] = 32
    fastbinsY[2] = 32            fastbinsY[1] = 48
    fastbinsY[4] = 48            fastbinsY[2] = 64
    fastbinsY[6] = 64            fastbinsY[3] = 80
                                           fastbinsY[4] = 96
                                           fastbinsY[5] = 112
                                           fastbinsY[6] = 128

------------------------------------------------------------------------------------------------------------------------------
4. bins[NBINS * 2 - 2]
bins被分为unsorted、small和large bins,bins[0]是不存在的

  • unsorted_bin[1]:该bin中存放的chunk块是无序的,在free过程中,首先将chunk块统一放到unsorted bin中,待在后续的malloc中将其放入相应的small和large bins中或直接作为用户请求返回
  • smallbin[2-63]:同一个bin中存放的大小都是相同的,两个相邻的bin之间相差2个机器字长,即32位位8字节,64位为16字节
  • largebin[64-128]

------------------------------------------------------------------------------------------------------------------------------
5. 内存管理malloc和free
5.1. 内存分配顺序
fastbinsY——>smallbin——>largebin——>unsortedbin——>top
在首次进行malloc时,由于fastbinsY、smallbin、largebin和unsortedbin均为空,只能从top处获得内存。

------------------------------------------------------------------------------------------------------------------------------
5.2. top内存管理
top作为一个堆的堆顶元素,在首次请求内存时,glibc会向操作提供申请虚拟内存,以达到堆向上增长的要求,glibc默认一次性申请DEFAULT_TOP_PAD(131072,为128KB,且以内存页4KB进行对齐,这样在后续的堆增长中,都以内存页4KB进行对齐增长)大小的堆。
当用户从top请求一定大小的内存后,剩余的部分被称为last_remainder,last_remainder将会是新的top。

------------------------------------------------------------------------------------------------------------------------------
5.3. unsortedbin
在真正的free中,chunk并不会直接放到smallbin中,而是直接unsortedbin中;如果在free过程中有连续相邻的chunk块被释放,就会在free过程中进行合并,然后重新放入unsortedbin中。unsortedbin在内存分配malloc过程中,作为最后一个被选择的bin。在利用unsortedbin分配内存时,首先会对满足smallbin的请求考虑从last_remainder中进行分配内存,然后会判定从unsortedbin中拿出来的是否满足用户需求,满足则直接返回给用否,否则会一次遍历unsortedbin的chunk块,并将其从unsortedbin中移除,依次根据条件放入smallbin和largebin中。

------------------------------------------------------------------------------------------------------------------------------
5.3.1. 对满足smallbin的请求从last_remainder中分配内存
if (in_smallbin_range (nb) &&
    bck == unsorted_chunks (av) &&
    victim == av->last_remainder &&
    (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{}
------------------------------------------------------------------------------------------------------------------------------
5.3.2. 判断unsortedbin中的chunk块是否满足用户需求
if (size == nb) {
    set_inuse_bit_at_offset (victim, size);
    if (av != &main_arena)
        set_non_main_arena (victim);
#if USE_TCACHE
    /* Fill cache first, return to user only if cache fills.
    We may return one of these chunks later.  */
    if (tcache_nb
    && tcache->counts[tc_idx] < mp_.tcache_count)
    {
        tcache_put (victim, tc_idx);
        return_cached = 1;
        continue;
    }
    else
    {
#endif
        check_malloced_chunk (av, victim, nb);
        void *p = chunk2mem (victim);
        alloc_perturb (p, bytes);
        return p;
#if USE_TCACHE
    }
#endif
}

------------------------------------------------------------------------------------------------------------------------------
5.3.3. 放入smallbin中
if (in_smallbin_range (size))
{
    victim_index = smallbin_index (size);
    bck = bin_at (av, victim_index);
    fwd = bck->fd;
}

------------------------------------------------------------------------------------------------------------------------------
5.3.4. 放入largebin中
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);

------------------------------------------------------------------------------------------------------------------------------
5.4. 从smallbin和largebin中分配内存
待smallbin和largebin中成功放入chunk块后,在后续的内存情况中会考虑从smallbin和largebin中进行分配。

------------------------------------------------------------------------------------------------------------------------------
5.4.1. 从smallbin中分配内存
if (in_smallbin_range (nb))
{
    idx = smallbin_index (nb);
    bin = bin_at (av, idx);

    if ((victim = last (bin)) != bin)
    {
        bck = victim->bk;
        if (__glibc_unlikely (bck->fd != victim))
            malloc_printerr ("malloc(): smallbin double linked list corrupted");
        set_inuse_bit_at_offset (victim, nb);
        bin->bk = bck;
        bck->fd = bin;

        if (av != &main_arena)
            set_non_main_arena (victim);
        check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
        /* While we're here, if we see other chunks of the same size,
        stash them in the tcache.  */
        size_t tc_idx = csize2tidx (nb);
        if (tcache && tc_idx < mp_.tcache_bins)
        {
            mchunkptr tc_victim;

            /* While bin not empty and tcache not full, copy chunks over.  */
            while (tcache->counts[tc_idx] < mp_.tcache_count
            && (tc_victim = last (bin)) != bin)
            {
                if (tc_victim != 0)
                {
                    bck = tc_victim->bk;
                    set_inuse_bit_at_offset (tc_victim, nb);
                    if (av != &main_arena)
                    set_non_main_arena (tc_victim);
                    bin->bk = bck;
                    bck->fd = bin;

                    tcache_put (tc_victim, tc_idx);
                }
            }
        }
#endif
        void *p = chunk2mem (victim);

        alloc_perturb (p, bytes);
        return p;
    }
}

------------------------------------------------------------------------------------------------------------------------------
5.4.2. 从smallbin中分配内存
if (!in_smallbin_range (nb))
{
    bin = bin_at (av, idx);

    /* skip scan if empty or largest chunk is too small */
    if ((victim = first (bin)) != bin
        && (unsigned long) chunksize_nomask (victim)
        >= (unsigned long) (nb))
    {
        victim = victim->bk_nextsize;
        while (((unsigned long) (size = chunksize (victim)) <
            (unsigned long) (nb)))
            victim = victim->bk_nextsize;

        /* Avoid removing the first entry for a size so that the skip
        list does not have to be rerouted.  */
        if (victim != last (bin)
            && chunksize_nomask (victim)
            == chunksize_nomask (victim->fd))
            victim = victim->fd;

        remainder_size = size - nb;
        unlink_chunk (av, victim);

        /* Exhaust */
        if (remainder_size < MINSIZE)
        {
            set_inuse_bit_at_offset (victim, size);
            if (av != &main_arena)
            set_non_main_arena (victim);
        }
        /* Split */
        else
        {
            remainder = chunk_at_offset (victim, nb);
            /* We cannot assume the unsorted list is empty and therefore
            have to perform a complete insert here.  */
            bck = unsorted_chunks (av);
            fwd = bck->fd;
            if (__glibc_unlikely (fwd->bk != bck))
                malloc_printerr ("malloc(): corrupted unsorted chunks");
            remainder->bk = bck;
            remainder->fd = fwd;
            bck->fd = remainder;
            fwd->bk = remainder;
            if (!in_smallbin_range (remainder_size))
            {
                remainder->fd_nextsize = NULL;
                remainder->bk_nextsize = NULL;
            }
            set_head (victim, nb | PREV_INUSE |
                (av != &main_arena ? NON_MAIN_ARENA : 0));
            set_head (remainder, remainder_size | PREV_INUSE);
            set_foot (remainder, remainder_size);
        }
        check_malloced_chunk (av, victim, nb);
        void *p = chunk2mem (victim);
        alloc_perturb (p, bytes);
        return p;
    }
}

------------------------------------------------------------------------------------------------------------------------------
5.5. free管理
5.5.1. 将free的chunk块放入fastbinsY中
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
      )
{}

------------------------------------------------------------------------------------------------------------------------------
5.5.2. 放入unsortedbin,前项和后项合并
对free的chunk在放入unsortedbin后,做了如下操作:
A:通过当前free的chunk找到与之相邻的下一个chunk块,即nextchunk,并对nextchunk对应的inuse位进行清0(clear_inuse_bit_at_offset(nextchunk, 0);),设置当前chunk的inuse位为1(set_head(p, size | PREV_INUSE);),设置nextchunk对应的prev_size为当前chunk的值。这样在真正free nextchunk时,可通过inuse为0和prev_size找到与之相应的前一个块进行合并。
clear_inuse_bit_at_offset(nextchunk, 0);

/* consolidate backward */
/*! 后向合并将两个相邻的chunk块合成一个 */
if (!prev_inuse(p)) {
    prevsize = prev_size (p);
    size += prevsize;

    p = chunk_at_offset(p, -((long) prevsize));
    if (__glibc_unlikely (chunksize(p) != prevsize))
        malloc_printerr ("corrupted size vs. prev_size while consolidating");
    unlink_chunk (av, p);
}

if (nextchunk != av->top) {
    /* get and clear inuse bit */
    nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

    /* consolidate forward */
    /*! 前向合并将多个相邻的chunk块合并成一个 */
    if (!nextinuse) {
        unlink_chunk (av, nextchunk);
        size += nextsize;
    } else
        clear_inuse_bit_at_offset(nextchunk, 0); //置nextchunk inuse位为0,为后向合并做准备

    /*
    Place the chunk in unsorted chunk list. Chunks are
    not placed into regular bins until after they have
    been given one chance to be used in malloc.
    */
    bck = unsorted_chunks(av);
    fwd = bck->fd;

    if (__glibc_unlikely (fwd->bk != bck))
        malloc_printerr ("free(): corrupted unsorted chunks");
    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); //设置当前inuse位
    set_foot(p, size); //设置nextchunk的prev_size为当前chunk的大小

    check_free_chunk(av, p);
}
从上可知,free的chunk会直接放入到unsortedbin中,在前向和后向合并后,会将合并后的大chunk块也放入到unsortedbin中。

------------------------------------------------------------------------------------------------------------------------------
5.5.3. 将free的chunk还给操作系统
#define FASTBIN_CONSOLIDATION_THRESHOLD  (65536UL) //64Kb

if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
    if (atomic_load_relaxed (&av->have_fastchunks))
    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 {
        /* Always try heap_trim(), even if the top chunk is not
        large, because the corresponding heap might go away.  */
        heap_info *heap = heap_for_ptr(top(av));

        assert(heap->ar_ptr == av);
        heap_trim(heap, mp_.top_pad);
    }
}
只有当free的chunk size大于FASTBIN_CONSOLIDATION_THRESHOLD(64Kb)时,才会将内存直接还给操作系统。(注:size的大小不仅仅是用户free的chunk的大小,也会是前向和后向合并后的chunk大小)
malloc_consolidate会将fastbinsY中的所有chunk块与unsortedbin进行合并,合并后由systrim或heap_trim将其还给操作系统。

本文通过Glibc的内存暴增问题,主要介绍了系统的内存管理问题,具体如下: 目录 1. 问题 2. 基础知识 2.1 X86平台Linux进程内存布局 2.1.1 32位模式下进程内存经典布局 2.1.2 32位模式下进程默认内存布局 2.1.3 64位模式下进程内存布局 2.2 操作系统内存分配的相关函数 2.2.1 Heap操作相关函数 2.2.2 Mmap映射区域操作相关函数 3. 概述 3.1 内存管理一般性描述 3.1.1 内存管理的方法 3.1.2 内存管理器的设计目标 3.1.3 常见C内存管理程序 3.2 Ptmalloc内存管理概述 3.2.1 简介 3.2.2 内存管理的设计假设 3.2.3 内存管理数据结构概述 3.2.4 内存分配概述 3.2.5 内存回收概述 3.2.6 配置选项概述 3.2.7 使用注意事项 4. 问题分析及解决 5. 源代码分析 5.1 边界标记法 5.2 分箱式内存管理 5.2.1 Small bins 5.2.2 Large bins 5.2.3 Unsorted bin 5.2.4 Fast bins 5.3 核心结构体分析 5.3.1 malloc_state 5.3.2 Malloc_par 5.3.3 分配区的初始化 5.4 配置选项 5.5 Ptmalloc的初始化 5.5.1 Ptmalloc未初始化时分配/释放内存 5.5.2 ptmalloc_init()函数 5.5.3 ptmalloc_lock_all(),ptmalloc_unlock_all(),ptmalloc_unlock_all2() 5.6 多分配区支持 5.6.1 Heap_info 5.6.2 获取分配区 5.6.3 Arena_get2() 5.6.4 _int_new_arena() 5.6.5 New_heap() 5.6.6 get_free_list()和reused_arena() 5.6.7 grow_heap(),shrink_heap(),delete_heap(),heap_trim() 5.7 内存分配malloc 5.7.1 public_mALLOc() 5.7.2 _int_malloc() 5.8 内存释放free 5.8.1 Public_fREe() 5.8.2 _int_free() 5.8.3 sYSTRIm()和munmap_chunk(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值