malloc源码分析

部分转载ctf wiki和华庭的ptmalloc2分析,内含自己的一些理解和思考

Posted by Rigel on February 14, 2020

malloc解析

malloc

一般我们会使用 malloc 函数来申请内存块,可是当仔细看 glibc 的源码实现时,其实并没有 malloc 函数。其实该函数真正调用的是 __libc_malloc 函数。为什么不直接写个 malloc 函数呢,因为有时候我们可能需要不同的名称。此外,__libc_malloc 函数只是用来简单封装 _int_malloc 函数。_int_malloc才是申请内存块的核心下面我们来仔细分析一下具体的实现。

该函数会首先检查是否有内存分配函数的钩子函数(__malloc_hook),这个主要用于用户自定义的堆分配函数,方便用户快速修改堆分配函数并进行测试。这里需要注意的是,用户申请的字节一旦进入申请内存函数中就变成了无符号整数

// wapper for int_malloc
void *__libc_malloc(size_t bytes) {
    mstate ar_ptr;    //指针
    void * victim;
    // 检查是否有内存分配钩子,如果有,调用钩子并返回.
    void *(*hook)(size_t, const void *) = atomic_forced_read(__malloc_hook);  //data段的hook
    if (__builtin_expect(hook != NULL, 0))
        return (*hook)(bytes, RETURN_ADDRESS(0));

接着会寻找一个 arena 来试图分配内存。

    arena_get(ar_ptr, bytes);  //前面已经定义好了变量,在函数内对变量赋值   根据byte找还有这么多空的arena
#define arena_get(ptr, size) do { \   
	arena_lookup(ptr); \   
  	arena_lock(ptr, size); \ 
} while(0) 

#define arena_lookup(ptr) do { \       //找之前是否已经分配了
    Void_t *vptr = NULL; \   
    ptr = (mstate)tsd_getspecific(arena_key, vptr); \ 
} while(0) 
    
#define arena_lock(ptr, size) do { \     //对arena_get出来的arena进行加锁。如果不成功,调用arena_get2
    if(ptr) \     
        (void)mutex_lock(&ptr->mutex); \   
    else \     
        ptr = arena_get2(ptr, (size)); \ 
} while(0)

//arena_get 宏尝试查看线程的私用实例中是否包含一个分配区,如果 不存在分配区或是存在分配区,但对该分配区加锁失败,就会调用 arena_get2()函数获得一 个分配区

//pthread_getspecific(从一个键读取线程私有数据)    可以看一下https://blog.csdn.net/str999_cn/article/details/78705733    https://blog.csdn.net/tuhuolong/article/details/6197876?locationNum=3&fps=1                说明每个线程都会有数据结构去存储自己的arena指针

static mstate arena_get2 (size_t size, mstate avoid_arena)
{
  mstate a;
  static size_t narenas_limit;
  a = get_free_list ();
  if (a == NULL)
    {
      /* Nothing immediately available, so generate a new arena.  */
      if (narenas_limit == 0)
        {
          if (mp_.arena_max != 0)
            narenas_limit = mp_.arena_max;
          else if (narenas > mp_.arena_test)
            {
              int n = __get_nprocs ();
              if (n >= 1)
                narenas_limit = NARENAS_FROM_NCORES (n);
              else
                /* We have no information about the system.  Assume two
                   cores.  */
                narenas_limit = NARENAS_FROM_NCORES (2);
            }
        }
    repeat:;
      size_t n = narenas;
      /* NB: the following depends on the fact that (size_t)0 - 1 is a
         very large number and that the underflow is OK.  If arena_max
         is set the value of arena_test is irrelevant.  If arena_test
         is set but narenas is not yet larger or equal to arena_test
         narenas_limit is 0.  There is no possibility for narenas to
         be too big for the test to always fail since there is not
         enough address space to create that many arenas.  */
      if (__glibc_unlikely (n <= narenas_limit - 1))
        {
          if (catomic_compare_and_exchange_bool_acq (&narenas, n + 1, n))
            goto repeat;
          a = _int_new_arena (size);
          if (__glibc_unlikely (a == NULL))
            catomic_decrement (&narenas);
        }
      else
        a = reused_arena (avoid_arena);
    }
    //首先尝试从分配区的 free list 中获得一个分配区,分配区的 free list 是从父线程(进程)中继承而来,如果 free list 中没有分配区,尝试重用已有的分配区,只有当分配区的数达到限制时才重用分配区,如果仍未获得可重用的分配区,创建一个新的分配区。      每个进程的内核里都会记录该进程的所有arena!!  尽量不重用arena,这样快。      在这里就会取出/创建相应的arena. 
    if(!a_tsd)     
         a = a_tsd = &main_arena;   
    else {     
        a = a_tsd->next;     
        if(!a) {       /* This can only happen while initializing the new arena. */
       (void)mutex_lock(&main_arena.mutex);       
            THREAD_STAT(++(main_arena.stat_lock_wait));       
            return &main_arena;     
        }   
    }
    //如果线程的私有实例中没有分配区,将主分配区作为候选分配区,如果线程私有实例中 存在分配区,但不能获得该分配区的锁,将该分配区的下一个分配区作为候选分配区,如果候选分配区为空,意味着当前线程私用实例中的分配区正在初始化,还没有加入到全局的分 配区链表中,这种情况下,只有主分配区可选了,等待获得主分配区的锁,如果获得住分配区的锁成功,返回主分配区。 
  return a;
}


//_int_new_arena()函数用于创建一个非主分配区,在 arena_get2()函数中被调用
static mstate _int_new_arena (size_t size)
{
  mstate a;
  heap_info *h;
  char *ptr;
  unsigned long misalign;
  h = new_heap (size + (sizeof (*h) + sizeof (*a) + MALLOC_ALIGNMENT),
                mp_.top_pad);
  if (!h)
    {
      /* Maybe size is too large to fit in a single heap.  So, just try
         to create a minimally-sized arena and let _int_malloc() attempt
         to deal with the large request via mmap_chunk().  */
      h = new_heap (sizeof (*h) + sizeof (*a) + MALLOC_ALIGNMENT, mp_.top_pad);  //new heap也是mmap出来的
      if (!h)
        return 0;
    }
  //对于一个新的非主分配区,至少包含一个 sub_heap,每个非主分配区中都有相应的管 理数据结构,每个非主分配区都有一个 heap_info 实例和 malloc_state 的实例,这两个实例 都位于非主分配区的第一个sub_heap的开始部分,malloc_state实例紧接着heap_info实例。 所以在创建非主分配区时,需要为管理数据结构分配额外的内存空间。New_heap()函数创建 一个新的 sub_heap,并返回 sub_heap 的指针。 
  a = h->ar_ptr = (mstate) (h + 1);
    
  malloc_init_state (a);        //会初始化一下state,里面会把top chunk设置成unsorted bin的数组chunk当初值
    
  a->attached_threads = 1;
  /*a->next = NULL;*/
  a->system_mem = a->max_system_mem = h->size;
  /* Set up the top chunk, with proper alignment. */
  ptr = (char *) (a + 1);
  misalign = (unsigned long) chunk2mem (ptr) & MALLOC_ALIGN_MASK;
  if (misalign > 0)
    ptr += MALLOC_ALIGNMENT - misalign;
  top (a) = (mchunkptr) ptr;
  set_head (top (a), (((char *) h + h->size) - ptr) | PREV_INUSE);
  LIBC_PROBE (memory_arena_new, 2, a, size);
  mstate replaced_arena = thread_arena;
  thread_arena = a;
  __libc_lock_init (a->mutex);
  __libc_lock_lock (list_lock);
  /* Add the new arena to the global list.  */
  a->next = main_arena.next;
  /* FIXME: The barrier is an attempt to synchronize with read access
     in reused_arena, which does not acquire list_lock while
     traversing the list.  */
  atomic_write_barrier ();
  main_arena.next = a;
  __libc_lock_unlock (list_lock);
  __libc_lock_lock (free_list_lock);
  detach_arena (replaced_arena);
  __libc_lock_unlock (free_list_lock);
  /* Lock this arena.  NB: Another thread may have been attached to
     this arena because the arena is now accessible from the
     main_arena.next list and could have been picked by reused_arena.
     This can only happen for the last arena created (before the arena
     limit is reached).  At this point, some arena has to be attached
     to two threads.  We could acquire the arena lock before list_lock
     to make it less likely that reused_arena picks this new arena,
     but this could result in a deadlock with
     __malloc_fork_lock_parent.  */
  __libc_lock_lock (a->mutex);
  return a;
}




/* If we don't have the main arena, then maybe the failure is due to running
   out of mmapped areas, so we can try allocating on the main arena.
   Otherwise, it is likely that sbrk() has failed and there is still a chance
   to mmap(), so try one of the other arenas.  */
static mstate
arena_get_retry (mstate ar_ptr, size_t bytes)
{
  LIBC_PROBE (memory_arena_retry, 2, bytes, ar_ptr);
  if (ar_ptr != &main_arena)
    {
      __libc_lock_unlock (ar_ptr->mutex);
      ar_ptr = &main_arena;
      __libc_lock_lock (ar_ptr->mutex);
    }
  else
    {
      __libc_lock_unlock (ar_ptr->mutex);
      ar_ptr = arena_get2 (bytes, ar_ptr);
    }
  return ar_ptr;
}

arena_get 首先调用 arena_lookup 查找本线程的私用实例中是否包含一个分配区的指针, 返回该指针,调用 arena_lock 尝试对该分配区加锁,如果加锁成功,使用该分配区分配内存, 如果对该分配区加锁失败,调用arena_get2获得一个分配区指针。 如果定义了PRE_THREAD, arena_lock 的处理有些不同,如果本线程拥有的私用实例中包含分配区的指针,则直接对该 分配区加锁,否则,调用 arena_get2 获得分配区指针,PRE_THREAD 的优化保证了每个线程 尽量从自己所属的分配区中分配内存,减少与其它线程因共享分配区带来的锁开销,但 PRE_THREAD 的优化并不能保证每个线程都有一个不同的分配区,当系统中的分配区数量达 到配置的最大值时,不能再增加新的分配区,如果再增加新的线程,就会有多个线程共享同 一个分配区。所以 ptmalloc 的 PRE_THREAD 优化,对线程少时可能会提升一些性能,但线程 多时,提升性能并不明显。即使没有线程共享分配区的情况下,任然需要加锁,这是不必要 的开销,每次加锁操作会消耗 100ns 左右的时间。

主线程的话,一开始就有malloc_state,所以arena_get在look up时就能直接返回malloc_state的指针。

(每个thread的arena信息,肯定在thread info之类的地方有指针存储。 所有arena的信息,应该会在该进程的内核空间中储存,方便遍历)

然后调用 _int_malloc 函数去申请对应的内存。

    victim = _int_malloc(ar_ptr, bytes);

如果分配失败的话,ptmalloc 会尝试再去寻找一个可用的 arena,并分配内存。

    /* Retry with another arena only if we were able to find a usable arena
       before.  */
    if (!victim && ar_ptr != NULL) {
        LIBC_PROBE(memory_malloc_retry, 1, bytes);
        ar_ptr = arena_get_retry(ar_ptr, bytes);
        victim = _int_malloc(ar_ptr, bytes);
    }

如果申请到了 arena,那么在退出之前还得解锁。

    if (ar_ptr != NULL) __libc_lock_unlock(ar_ptr->mutex);

判断目前的状态是否满足以下条件

  • 要么没有申请到内存
  • 要么是 mmap 的内存(mmap也是在int_malloc中完成的)
  • 要么申请到的内存必须在其所分配的 arena 中
    assert(!victim || chunk_is_mmapped(mem2chunk(victim)) ||
           ar_ptr == arena_for_chunk(mem2chunk(victim)));  //用assert判断  如果不符合,就退出程序!

最后返回内存。

    return victim;       //malloc失败就返回null
}

_int_malloc

_int_malloc 是内存分配的核心函数,其核心思路有如下

  1. 它根据用户申请的内存块大小以及相应大小 chunk 通常使用的频度(fastbin chunk, small chunk, large chunk),依次实现了不同的分配方法。
  2. 它由小到大依次检查不同的 bin 中是否有相应的空闲块可以满足用户请求的内存。
  3. 当所有的空闲 chunk 都无法满足时,它会考虑 top chunk。
  4. 当 top chunk 也无法满足时,堆分配器才会进行内存块申请。

在进入该函数后,函数立马定义了一系列自己需要的变量,并将用户申请的内存大小转换为内部的 chunk 大小。

static void *_int_malloc(mstate av, size_t bytes) {
    INTERNAL_SIZE_T nb;  /* normalized request size */
    unsigned int    idx; /* associated bin index */
    mbinptr         bin; /* associated bin */

    mchunkptr       victim;       /* inspected/selected chunk */
    INTERNAL_SIZE_T size;         /* its size */
    int             victim_index; /* its bin index */

    mchunkptr     remainder;      /* remainder from a split */
    unsigned long remainder_size; /* its size */

    unsigned int block; /* bit map traverser */
    unsigned int bit;   /* bit map traverser */
    unsigned int map;   /* current word of binmap */

    mchunkptr fwd; /* misc temp for linking */
    mchunkptr bck; /* misc temp for linking */

    const char *errstr = NULL;

    /*
       Convert request size to internal form by adding SIZE_SZ bytes
       overhead plus possibly more to obtain necessary alignment and/or
       to obtain a size of at least MINSIZE, the smallest allocatable
       size. Also, checked_request2size traps (returning 0) request sizes
       that are so large that they wrap around zero when padded and
       aligned.
     */

    checked_request2size(bytes, nb);

arena

    /* There are no usable arenas.  Fall back to sysmalloc to get a chunk from
       mmap.  */
    if (__glibc_unlikely(av == NULL)) {
        void *p = sysmalloc(nb, av);    //申请chunk
        if (p != NULL) alloc_perturb(p, bytes);     //初始化chunk,填满perturb byte。通常为null,即不填
        return p;
    }
static void alloc_perturb(char *p, size_t n) {
    if (__glibc_unlikely(perturb_byte))
        memset(p, perturb_byte ^ 0xff, n);
}

注意,在这里,如果av=0,基本上就是arena都被锁住的情况,概率很小。如果暂时没用arena,那么就sysmalloc. 线程arena的申请,创建和初始化都是在arena_get中完成的。 那main_arena什么时候初始化??

fast bin

如果申请的 chunk 的大小位于 fastbin 范围内,需要注意的是这里比较的是无符号整数此外,是从 fastbin 的头结点开始取 chunk

fastbin数组初始化时都是null!! 这不同于small/large/unsorted bin 因为init malloc_state时,就没有对fastbinsY数组进行处理 因为fastbin chunk只有fd,没有bk,所以不用chunk形式对数组进行索引

分配区的初始化函数默认分配区的实例av中的所有字段 都清 0 了!! 因为mmap匿名文件映射出来的内存是由内核创建的全为进制零文件!! 即匿名文件创建时就初始化为全0!! 自然mmap出来的heap也是全0

catomic_compare_and_exchange_val_acqMEM,NEWVAL,OLDVAL
//如果*MEM等于OLDVAL,则将*MEM存储为NEWVAL,返回OLDVAL;
    /*
       If the size qualifies as a fastbin, first check corresponding bin.
       This code is safe to execute even if av is not yet initialized, so we
       can try it without checking, which saves some time on this fast path.
     */

    if ((unsigned long) (nb) <= (unsigned long) (get_max_fast())) {
        // 得到对应的fastbin的下标
        idx             = fastbin_index(nb);
        // 得到对应的fastbin的头指针
        mfastbinptr *fb = &fastbin(av, idx);
        mchunkptr    pp = *fb;
        // 利用fd遍历对应的bin内是否有空闲的chunk块,
        do {          //正常情况下这里while实际上就循环了1次
            victim = pp;
            if (victim == NULL) break;//因为fastbin数组没有初始化,即内容为0
        } while ((pp = catomic_compare_and_exchange_val_acq(fb, victim->fd,victim)) != victim);    
        // 存在可以利用的chunk
        if (victim != 0) {
            // 检查取到的 chunk 大小是否与相应的 fastbin 索引一致。因为有可能bin中因double free,修改size!!
            // 根据取得的 victim ,利用 chunksize 计算其大小。
            // 利用fastbin_index 计算 chunk 的索引。
            if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0)) {
                errstr = "malloc(): memory corruption (fast)";
            errout:
                malloc_printerr(check_action, errstr, chunk2mem(victim), av);
                return NULL;
            }
            // 细致的检查。。只有在 DEBUG 的时候有用
            check_remalloced_chunk(av, victim, nb);
            // 将获取的到chunk转换为mem模式
            void *p = chunk2mem(victim);
            // 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
            alloc_perturb(p, bytes);
            return p;
        }
    }

small bin

如果获取的内存块的范围处于 small bin 的范围,那么执行如下流程

    /*
       If a small request, check regular bin.  Since these "smallbins"
       hold one size each, no searching within bins is necessary.
       (For a large request, we need to wait until unsorted chunks are
       processed to find best fit. But for small ones, fits are exact
       anyway, so we can check now, which is faster.)
     */

    if (in_smallbin_range(nb)) {
        // 获取 small bin 的索引
        idx = smallbin_index(nb);
        // 获取对应 small bin 中的 chunk 指针
        bin = bin_at(av, idx);
        // 先执行 victim = last(bin),获取 small bin 的最后一个 chunk
        // 如果 victim = bin ,那说明该 bin 为空。
        // 如果不相等,那么会有两种情况
        if ((victim = last(bin)) != bin) {
            // 第一种情况,small bin 还没有初始化。
            if (victim == 0) /* initialization check */
                // 执行初始化,将 fast bins 中的 chunk 进行合并
                malloc_consolidate(av);
            // 第二种情况,small bin 中存在空闲的 chunk
            else {
                // 获取 small bin 中倒数第二个 chunk 。
                bck = victim->bk;
                // 检查 bck->fd 是不是 victim,防止伪造
                if (__glibc_unlikely(bck->fd != victim)) {
                    errstr = "malloc(): smallbin double linked list corrupted";
                    goto errout;
                }
                // 设置 victim 对应的 inuse 位
                set_inuse_bit_at_offset(victim, nb);
                // 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
                bin->bk = bck;
                bck->fd = bin;
                // 如果不是 main_arena,设置对应的标志
                if (av != &main_arena) set_non_main_arena(victim);
                // 细致的检查,非调试状态没有作用
                check_malloced_chunk(av, victim, nb);
                // 将申请到的 chunk 转化为对应的 mem 状态
                void *p = chunk2mem(victim);
                // 如果设置了 perturb_type , 则将获取到的chunk初始化为 perturb_type ^ 0xff
                alloc_perturb(p, bytes);
                return p;
            }
        }
    }

small bin取的是最先入bin的chunk,即small bin图中最下面的chunk

如果是small request,那么就在这里进行初始化!!

large bin

当 fast bin、small bin 中的 chunk 都不能满足用户请求 chunk 大小时,就会考虑是不是 large bin。但是,其实在 large bin 中并没有直接去扫描对应 bin 中的 chunk,而是先利用 malloc_consolidate(参见 malloc_state 相关函数) 函数处理 fast bin 中的 chunk,将有可能能够合并的 chunk 先进行合并后放到 unsorted bin 中,不能够合并的就直接放到 unsorted bin 中,然后再在下面的大循环中进行相应的处理。为什么不直接从相应的 bin 中取出 large chunk 呢?这是 ptmalloc 的机制,它会在分配 large chunk 之前对堆中碎片 chunk 进行合并,以便减少堆中的碎片。

    /*
       If this is a large request, consolidate fastbins before continuing.
       While it might look excessive to kill all fastbins before
       even seeing if there is space available, this avoids
       fragmentation problems normally associated with fastbins.
       Also, in practice, programs tend to have runs of either small or
       large requests, but less often mixtures, so consolidation is not
       invoked all that often in most programs. And the programs that
       it is called frequently in otherwise tend to fragment.
     */

    else {
        // 获取large bin的下标。
        idx = largebin_index(nb);
        // 如果存在fastbin的话,会处理 fastbin
        if (have_fastchunks(av)) malloc_consolidate(av);
    }

large request才会去consolidate fastbin!! 因为large chunk申请的比较少,在这里希望通过集合fastbin的碎片chunk凑出large chunk,从而提高利用率 因为consolidate耗时,small chunk用的比较多,所以不值当的consolidate!!

大循环 - 遍历 unsorted bin

如果程序执行到了这里,那么说明 与 chunk 大小正好一致的 bin (fast bin, small bin) 中没有 chunk 可以直接满足需求 ,但是 large chunk 则是在这个大循环中处理

在接下来的这个循环中,主要做了以下的操作

  • 按照 FIFO 的方式逐个将 unsorted bin 中的 chunk 取出来(从最早入bin的chunk开始,依次往后)
    • 如果是 small request,则再考虑是不是恰好满足,是的话,直接返回,停止遍历。
    • 如果不是的话,放到对应的 bin 中。
  • 尝试从 large bin 中分配用户所需的内存

该部分是一个大循环,这是为了尝试重新分配 small bin chunk,这是因为我们虽然会首先使用 large bin,top chunk 来尝试满足用户的请求,但是如果没有满足的话,由于我们在上面没有分配成功 small bin,我们并没有对 fast bin 中的 chunk 进行合并,所以这里会进行 fast bin chunk 的合并,进而使用一个大循环来尝试再次分配 small bin chunk。

    /*
       Process recently freed or remaindered chunks, taking one only if
       it is exact fit, or, if this a small request, the chunk is remainder from
       the most recent non-exact fit.  Place other traversed chunks in
       bins.  Note that this step is the only place in any routine where
       chunks are placed in bins.

       The outer loop here is needed because we might not realize until
       near the end of malloc that we should have consolidated, so must
       do so and retry. This happens at most once, and only when we would
       otherwise need to expand memory to service a "small" request.
     */

    for (;;) {    //大循环  循环结尾在函数结尾处  该循环目的就是去consolidate后重新看unsorted bin
        int iters = 0;

unsorted bin 遍历

先考虑 unsorted bin,再考虑 last remainder ,但是对于 small bin chunk 的请求会有所例外。

注意 unsorted bin 的遍历顺序为 bk。

        // 如果 unsorted bin 不为空
        // First In First Out
        while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
            // victim 为 unsorted bin 的最后一个 chunk
            // bck 为 unsorted bin 的倒数第二个 chunk
            bck = victim->bk;
            // 判断得到的 chunk 是否满足要求,不能过小,也不能过大
            // 一般 system_mem 的大小为132K
            if (__builtin_expect(chunksize_nomask(victim) <= 2 * SIZE_SZ, 0) ||
                __builtin_expect(chunksize_nomask(victim) > av->system_mem, 0))
                malloc_printerr(check_action, "malloc(): memory corruption",
                                chunk2mem(victim), av);
            // 得到victim对应的chunk大小。
            size = chunksize(victim);
SMALL REQUEST

如果用户的请求为 small bin chunk,那么我们首先考虑 last remainder,如果 last remainder 是 unsorted bin 中的唯一一块的话(这时说明之前就没有free过,自然不用遍历), 并且 last remainder 的大小分割后还可以作为一个 chunk。 这是唯一的从unsorted bin中分配small bin chunk 的情况,这种优化利于 cpu 的高速缓存命中。 这是在遍历unsorted bin的while循环内,也就是说每遍历一个chunk都会判断一下是不是last remainder且是unsorted bin中唯一一个chunk

这里last remainder比nb大就行,会考虑分割,不用size正好相等。这也是last remainder优先的地方。

            /*
               If a small request, try to use last remainder if it is the
               only chunk in unsorted bin.  This helps promote locality for
               runs of consecutive small requests. This is the only
               exception to best-fit, and applies only when there is
               no exact fit for a small chunk.
             */

            if (in_smallbin_range(nb) && bck == unsorted_chunks(av) &&
                victim == av->last_remainder &&
                (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
                /* split and reattach remainder */
                // 获取新的 remainder 的大小
                remainder_size          = size - nb;
                // 获取新的 remainder 的位置
                remainder               = chunk_at_offset(victim, nb);
                // 更新 unsorted bin 的情况
                unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
                // 更新 av 中记录的 last_remainder
                av->last_remainder                                = remainder;
                // 更新last remainder的指针
                remainder->bk = remainder->fd = unsorted_chunks(av);
                if (!in_smallbin_range(remainder_size)) {
                    remainder->fd_nextsize = NULL;
                    remainder->bk_nextsize = NULL;
                }
                // 设置victim的头部,
                set_head(victim, nb | PREV_INUSE |
                                     (av != &main_arena ? NON_MAIN_ARENA : 0));
                // 设置 remainder 的头部
                set_head(remainder, remainder_size | PREV_INUSE);
                // 设置记录 remainder 大小的 prev_size 字段,因为此时 remainder 处于空闲状态。
                set_foot(remainder, remainder_size);
                // 细致的检查,非调试状态下没有作用
                check_malloced_chunk(av, victim, nb);
                // 将 victim 从 chunk 模式转化为mem模式
                void *p = chunk2mem(victim);
                // 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
                alloc_perturb(p, bytes);
                return p;
            }
初始取出 因为遍历unsorted bin就是为了把chunk放到small/large bin中,所以在这里直接释放掉
            /* remove from unsorted list */
            unsorted_chunks(av)->bk = bck;
            bck->fd                 = unsorted_chunks(av);
EXACT FIT

如果从 unsorted bin 中取出来的 chunk 大小正好合适,就直接使用。这里应该已经把合并后恰好合适的 chunk 给分配出去了。

            /* Take now instead of binning if exact fit */
            if (size == nb) {
                set_inuse_bit_at_offset(victim, size);
                if (av != &main_arena) set_non_main_arena(victim);
                check_malloced_chunk(av, victim, nb);
                void *p = chunk2mem(victim);
                alloc_perturb(p, bytes);
                return p;
            }

把取出来的 chunk 放到对应的 small bin 中。

            /* place chunk in bin */

            if (in_smallbin_range(size)) {
                victim_index = smallbin_index(size);
                bck          = bin_at(av, victim_index);
                fwd          = bck->fd;
PLACE CHUNK IN LARGE BIN

把取出来的 chunk 放到对应的 large bin 中。

            } else {
                // large bin 范围
                victim_index = largebin_index(size);
                bck          = bin_at(av, victim_index); // 当前 large bin 的头部
                fwd          = bck->fd;    //该bin中最大size的chunk

                /* maintain large bins in sorted order */
                /* 从这里我们可以总结出,largebin 以 fd_nextsize 递减排序。
                   同样大小的 chunk,后来的只会插入到之前同样大小的 chunk 后,
                   而不会修改之前相同大小的fd/bk_nextsize,这也很容易理解,
                   可以减低开销。此外,bin 头不参与 nextsize 链接。*/
                // 如果 large bin 链表不空
                if (fwd != bck) {
                    /* Or with inuse bit to speed comparisons */
                    // 加速比较,应该不仅仅有这个考虑,因为链表里的 chunk 都会设置该位。
                    size |= PREV_INUSE;
                    /* if smaller than smallest, bypass loop below */
                    // bck->bk 存储着相应 large bin 中最小的chunk。
                    // 如果遍历的 chunk 比当前最小的还要小,那就只需要插入到链表尾部。
                    // 判断 bck->bk 是不是在 main arena。
                    assert(chunk_main_arena(bck->bk));
                    if ((unsigned long) (size) <
                        (unsigned long) chunksize_nomask(bck->bk)) {
                        // 令 fwd 指向 large bin 头
                        fwd = bck;
                        // 令 bck 指向 largin bin 尾部 chunk
                        bck = bck->bk;
                        // victim 的 fd_nextsize 指向 largin bin 的第一个 chunk
                        victim->fd_nextsize = fwd->fd;
                        // victim 的 bk_nextsize 指向原来链表的第一个 chunk 指向的 bk_nextsize
                        victim->bk_nextsize = fwd->fd->bk_nextsize;
                        // 原来链表的第一个 chunk 的 bk_nextsize 指向 victim
                        // 原来指向链表第一个 chunk 的 fd_nextsize 指向 victim
                        fwd->fd->bk_nextsize =
                            victim->bk_nextsize->fd_nextsize = victim;
                    } else {
                        // 当前要插入的 victim 的大小大于最小的 chunk
                        // 判断 fwd 是否在 main arena
                        assert(chunk_main_arena(fwd));
                        // 从链表头部开始找到不比 victim 大的 chunk
                        while ((unsigned long) size < chunksize_nomask(fwd)) {
                            fwd = fwd->fd_nextsize;   //从大往小找
                            assert(chunk_main_arena(fwd));
                        }
                        // 如果找到了一个和 victim 一样大的 chunk,
                        // 那就直接将 chunk 插入到该chunk的后面,并不修改 nextsize 指针。
                        if ((unsigned long) size ==
                            (unsigned long) chunksize_nomask(fwd))
                            /* Always insert in the second position.  */
                            fwd = fwd->fd;
                        else {
                            // 如果找到的chunk和当前victim大小不一样
                            // 那么就需要构造 nextsize 双向链表了
                            victim->fd_nextsize              = fwd;
                            victim->bk_nextsize              = fwd->bk_nextsize;
                            fwd->bk_nextsize                 = victim;
                            victim->bk_nextsize->fd_nextsize = victim;
                        }
                        bck = fwd->bk;
                    }
                } else
                    // 如果空的话,直接简单使得 fd_nextsize 与 bk_nextsize 构成一个双向链表即可。
                    victim->fd_nextsize = victim->bk_nextsize = victim;
            }

先判断特殊位置,即size在nextsize链中最小,那么直接添加;其次遍历nextsize链,如果有相同的size,添加到fd链;如果没有,在nextsize链中插入该chunk

最终取出
            // 放到对应的 bin 中,构成 bck<-->victim<-->fwd。
            mark_bin(av, victim_index);    //标记binmap
            victim->bk = bck;
            victim->fd = fwd;
            fwd->bk    = victim;
            bck->fd    = victim;
while遍历unsorted bin迭代次数

如果 unsorted bin 中的 chunk 超过了 10000 个,最多遍历 10000 个就退出,避免长时间 处理 unsorted bin 影响内存分配的效率。

            // #define MAX_ITERS 10000
            if (++iters >= MAX_ITERS) break;
        }

**注: 或许会很奇怪,为什么这里没有先去看 small chunk 是否满足新需求了呢?这是因为 small bin 在循环之前已经判断过了,这里如果有的话,就是unsorted bin中分配到small bin的chunk。而这些chunk在分配时就判断了size相不相等。 所以接下来应该判断是否为large request. **

如果请求的 chunk 在 large chunk 范围内,就在对应的 bin 中从小到大进行扫描,找到第一个合适的。

        /*
           If a large request, scan through the chunks of current bin in
           sorted order to find smallest that fits.  Use the skip list for this.
         */
        if (!in_smallbin_range(nb)) {
            idx = largebin_index(nb);
            bin = bin_at(av, idx);
            /* skip scan if empty or largest chunk is too small */
            // 如果对应的 bin 为空或者其中的chunk最大的也很小,那就跳过
            // first(bin)=bin->fd 表示当前链表中最大的chunk
            if ((victim = first(bin)) != bin &&
                (unsigned long) chunksize_nomask(victim) >=
                    (unsigned long) (nb)) {
                // 反向遍历链表,直到找到第一个不小于所需chunk大小的chunk
                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.  */
                // 如果最终取到的chunk不是该bin中的最后一个chunk,并且该chunk与其前面的chunk
                // 的大小相同,那么我们就取其前面的chunk,这样可以避免调整bk_nextsize,fd_nextsize
                //  链表。因为大小相同的chunk只有一个会被串在nextsize链上。
                if (victim != last(bin) &&    //说明size比已有的最小chunk还小
                    chunksize_nomask(victim) == chunksize_nomask(victim->fd)) //这里如果这个size的fd链就一个chunk,它的fd是指向fd_nextsize的!!  以此来判断fd链的个数
                    victim = victim->fd;   //即取fd链上的,不动nextsize链 
                // 计算分配后剩余的大小
                remainder_size = size - nb;   //large chunk不求size完全相同,直接split
                // 进行unlink
                unlink(av, victim, bck, fwd);

                /* Exhaust */
                // 剩下的大小不足以当做一个块
                // 很好奇接下来会怎么办?
                if (remainder_size < MINSIZE) {    //这里如果large chunk size恰好=nb,那么remainder_size=0,也不用split
                    set_inuse_bit_at_offset(victim, size);
                    if (av != &main_arena) set_non_main_arena(victim);
                }
                /* Split */
                //  剩下的大小还可以作为一个chunk,进行分割。
                else {
                    // 获取剩下那部分chunk的指针,称为remainder
                    remainder = chunk_at_offset(victim, nb);
                    /* We cannot assume the unsorted list is empty and therefore
                       have to perform a complete insert here.  */
                    // 插入unsorted bin中
                    bck = unsorted_chunks(av);
                    fwd = bck->fd;
                    // 判断 unsorted bin 是否被破坏。
                    if (__glibc_unlikely(fwd->bk != bck)) {
                        errstr = "malloc(): corrupted unsorted chunks";
                        goto errout;
                    }
                    remainder->bk = bck;
                    remainder->fd = fwd;
                    bck->fd       = remainder;
                    fwd->bk       = remainder;
                    // 如果不处于small bin范围内,就设置对应的字段
                    if (!in_smallbin_range(remainder_size)) {
                        remainder->fd_nextsize = NULL;
                        remainder->bk_nextsize = NULL;
                    }
                    // 设置分配的chunk的标记
                    set_head(victim,
                             nb | PREV_INUSE |
                                 (av != &main_arena ? NON_MAIN_ARENA : 0));

                    // 设置remainder的上一个chunk,即分配出去的chunk的使用状态
                    // 其余的不用管,直接从上面继承下来了
                    set_head(remainder, remainder_size | PREV_INUSE);
                    // 设置remainder的大小
                    set_foot(remainder, remainder_size);
                }      //else的结束
                // 检查
                check_malloced_chunk(av, victim, nb);
                // 转换为mem状态
                void *p = chunk2mem(victim);
                // 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
                alloc_perturb(p, bytes);
                return p;    //如果remiander不够minsize,那么就把chunk整个malloc出去,不split
            }
        }

找到对应的bin,size从小到大遍历,找到第一个size>nb的chunk fd链,进行split(如果0<=remainder<=MINSIZE,就不split,把这部分size送给这次malloc) 也就是说large bin malloc不会直接追求size完全相等!! 当然这是该次malloc第一次去找large chunk,只找了对应idx的bin!! 下一步找更大chunk,就是找其他的更大bin了!! split会把remainder放到unsorted bin中!!

即第一轮都是找对应的bin!! large bin有多个size,所以不一定size相同。 即large bin是看bin的,不要求size!

寻找较大 chunk

如果走到了这里,那说明对于用户所需的 chunk,不能直接从其对应的合适的 bin 中获取 chunk,所以我们需要来查找比当前 bin 更大的small bin 或者 large bin不会找更大的fastbin!! 用到了unlink

        /*
           Search for a chunk by scanning bins, starting with next largest
           bin. This search is strictly by best-fit; i.e., the smallest
           (with ties going to approximately the least recently used) chunk
           that fits is selected.

           The bitmap avoids needing to check that most blocks are nonempty.
           The particular case of skipping all bins during warm-up phases
           when no chunks have been returned yet is faster than it might look.
         */

        ++idx;           //沿用前面的idx
        // 获取对应的bin
        bin   = bin_at(av, idx);
        // 获取当前索引在binmap中的block索引
        // #define idx2block(i) ((i) >> BINMAPSHIFT)  ,BINMAPSHIFT=5
        // Binmap按block管理,每个block为一个int,共32个bit,可以表示32个bin中是否有空闲chunk存在
        // 所以这里是右移5
        block = idx2block(idx);
        // 获取当前块大小对应的映射,这里可以得知相应的bin中是否有空闲块
        map   = av->binmap[ block ];
        // #define idx2bit(i) ((1U << ((i) & ((1U << BINMAPSHIFT) - 1))))
        // 将idx对应的比特位设置为1,其它位为0
        bit   = idx2bit(idx);
        for (;;) {
找到一个合适的 MAP
            /* Skip rest of block if there are no more set bits in this block.
             */
            // 如果bit>map,则表示该 map 中没有比当前所需要chunk大的空闲块
            // 如果bit为0,那么说明,上面idx2bit带入的参数为0。
            if (bit > map || bit == 0) {
                do {
                    // 寻找下一个block,直到其对应的map不为0。
                    // 如果已经不存在的话,那就只能使用top chunk了
                    if (++block >= BINMAPSIZE) /* out of bins */
                        goto use_top;
                } while ((map = av->binmap[ block ]) == 0);
                // 获取其对应的bin,因为该map中的chunk大小都比所需的chunk大,而且
                // map本身不为0,所以必然存在满足需求的chunk。
                bin = bin_at(av, (block << BINMAPSHIFT));
                bit = 1;
            }
找到合适的 BIN
            /* Advance to bin with set bit. There must be one. */
            // 从当前map的最小的bin一直找,直到找到合适的bin。
            // 这里是一定存在的
            while ((bit & map) == 0) {
                bin = next_bin(bin);
                bit <<= 1;
                assert(bit != 0);
            }
简单检查 CHUNK
            /* Inspect the bin. It is likely to be non-empty */
            // 获取对应的bin
            victim = last(bin);

            /*  If a false alarm (empty bin), clear the bit. */
            // 如果victim=bin,那么我们就将map对应的位清0,然后获取下一个bin
            // 这种情况发生的概率应该很小。
            if (victim == bin) {
                av->binmap[ block ] = map &= ~bit; /* Write through */
                bin                 = next_bin(bin);
                bit <<= 1;
            }
真正取出 CHUNK
            else {
                // 获取对应victim的大小
                size = chunksize(victim);

                /*  We know the first chunk in this bin is big enough to use. */
                assert((unsigned long) (size) >= (unsigned long) (nb));
                // 计算分割后剩余的大小
                remainder_size = size - nb;

                /* unlink */
                unlink(av, victim, bck, fwd);  //针对small/large bin

                /* Exhaust */
                // 如果分割后不够一个chunk,就送额外的size
                if (remainder_size < MINSIZE) {
                    set_inuse_bit_at_offset(victim, size);
                    if (av != &main_arena) set_non_main_arena(victim);
                }

                /* Split */
                // 如果够,尽管分割
                else {
                    // 计算剩余的chunk的偏移
                    remainder = chunk_at_offset(victim, nb);

                    /* We cannot assume the unsorted list is empty and therefore
                       have to perform a complete insert here.  */
                    // 将剩余的chunk插入到unsorted bin中
                    bck = unsorted_chunks(av);
                    fwd = bck->fd;
                    if (__glibc_unlikely(fwd->bk != bck)) {
                        errstr = "malloc(): corrupted unsorted chunks 2";
                        goto errout;
                    }
                    remainder->bk = bck;
                    remainder->fd = fwd;
                    bck->fd       = remainder;
                    fwd->bk       = remainder;

                    /* advertise as last remainder */
                    // 如果在small bin范围内,就将其标记为remainder
                    if (in_smallbin_range(nb)) av->last_remainder = remainder;
                    if (!in_smallbin_range(remainder_size)) {
                        remainder->fd_nextsize = NULL;
                        remainder->bk_nextsize = NULL;
                    }
                    // 设置victim的使用状态
                    set_head(victim,
                             nb | PREV_INUSE |
                                 (av != &main_arena ? NON_MAIN_ARENA : 0));
                    // 设置remainder的使用状态,这里是为什么呢?
                    set_head(remainder, remainder_size | PREV_INUSE);
                    // 设置remainder的大小
                    set_foot(remainder, remainder_size);
                }
                // 检查
                check_malloced_chunk(av, victim, nb);
                // chunk状态转换到mem状态
                void *p = chunk2mem(victim);
                // 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
                alloc_perturb(p, bytes);
                return p;
            }

只有small request时的split才会把remainder设置成arena的last remainder!! large request的remainder不会设置!! 所以last remainder只在small request遍历unsorted bin时用!! 这样有助于cache命中

​ 另外,找更大的large bin时,直接取最小的chunk。 取nextsize链上的那个chunk。 nextsize链的变化也由unlink处理。

使用 top chunk

如果从所有的 bins 中都没有获得所需的 chunk,可能的情况为 bins 中没有空闲 chunk, 或者所需的 chunk 大小很大,下一步将尝试从 top chunk 中分配所需 chunk。

    use_top:
        /*
           If large enough, split off the chunk bordering the end of memory
           (held in av->top). Note that this is in accord with the best-fit
           search rule.  In effect, av->top is treated as larger (and thus
           less well fitting) than any other available chunk since it can
           be extended to be as large as necessary (up to system
           limitations).

           We require that av->top always exists (i.e., has size >=
           MINSIZE) after initialization, so if it would otherwise be
           exhausted by current request, it is replenished. (The main
           reason for ensuring it exists is that we may need MINSIZE space
           to put in fenceposts in sysmalloc.)
         */
        // 获取当前的top chunk,并计算其对应的大小
        victim = av->top;
        size   = chunksize(victim);
        // 如果分割之后,top chunk 大小仍然满足 chunk 的最小大小,那么就可以直接进行分割。
        if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) {
            remainder_size = size - nb;
            remainder      = chunk_at_offset(victim, nb);
            av->top        = remainder;
            // 这里设置 PREV_INUSE 是因为 top chunk 前面的 chunk 如果不是 fastbin,就必然会和
            // top chunk 合并,所以这里设置了 PREV_INUSE。
            set_head(victim, nb | PREV_INUSE |
                                 (av != &main_arena ? NON_MAIN_ARENA : 0));
            set_head(remainder, remainder_size | PREV_INUSE);

            check_malloced_chunk(av, victim, nb);
            void *p = chunk2mem(victim);
            alloc_perturb(p, bytes);
            return p;
        }
        // 否则,判断是否有 fast chunk
        /* When we are using atomic ops to free fast chunks we can get
           here for all block sizes.  */
        else if (have_fastchunks(av)) {
            // 先执行一次fast bin的合并
            malloc_consolidate(av);   //这里主要是为了考虑small request,若是large request的话前面已经consolidate一次了
            /* restore original bin index */
            // 判断需要的chunk是在small bin范围内还是large bin范围内
            // 并计算对应的索引
            // 等待下次再看看是否可以
            if (in_smallbin_range(nb))
                idx = smallbin_index(nb);
            else
                idx = largebin_index(nb);
        }

​ 如果 top chunk 也不能满足要求,查看 fast bins 中是否有空闲 chunk 存在,由于开启了 ATOMIC_FASTBINS 优化情况下,free 属于 fast bins 的 chunk 时不需要获得分配区的锁,所以在调用_int_malloc()函数时,有可能有其它线程已经向 fast bins 中加入了新的空闲 chunk,也有可能是所需的 chunk 属于 small bins,但通过前面的步骤都没有分配到所需的 chunk,由于分配 small bin chunk 时在前面的步骤都不会调用 malloc_consolidate()函数将 fast bins 中的 chunk 合并加入到 unsorted bin 中。所在这里如果 fast bin 中有 chunk 存在,调用malloc_consolidate()函数,并重新设置当前 bin 的 index。并转到最外层的循环,尝试重新分 配 small bin chunk 或是 large bin chunk。如果开启了 ATOMIC_FASTBINS 优化,有可能在由其它线程加入到 fast bins 中的 chunk 被合并后加入 unsorted bin 中,从 unsorted bin 中就可以分配出所需的 large bin chunk 了,所以对没有成功分配的 large bin chunk 也需要重试。

​ 尽量把consolidate延后,这样尽量减少开销。

堆内存不够

如果堆内存不够,我们就需要使用 sysmalloc 来申请内存了。

        /*
           Otherwise, relay to handle system-dependent cases
         */
        // 否则的话,我们就只能从系统中再次申请一点内存了。
        else {     //接上面的else if (have_fastchunks(av))
            void *p = sysmalloc(nb, av);
            if (p != NULL) alloc_perturb(p, bytes);
            return p;
        }
	}     //这里接for(;;)大循环的结尾    如果第一轮大循环结尾else if判断还有fastbin,那么就consolidate,开始第二轮大循环,再次遍历unsorted bin。  直至再次运行到这,判断是否还有fastbin。  for(;;)大循环之所以设置无数次,就是为了一直判断fastbin是否还有chunk,有chunk就还有机会。  因为与consolidate相比,sysmalloc要用系统调用,开销太大了!!  所以要尽可能通过fastbin consolidate去实现malloc!!     如果最后fastbin没有了,就会进行这个else去sysmalloc,最后return来结束大循环,退出函数。
}

山穷水尽了,只能想系统申请内存了。sYSMALLOc()函数可能分配的 chunk 包括 small bin chunk,large bin chunk 和 large chunk。将在下一节中介绍该函数的实现。

所以并不会找更大的fastbin,因为fastbin的大小在small bin中,并且又有consolidate的情况,所以没必要再找更大的fastbin!fastbin只会用于合并!!

sysmalloc

当_int_malloc()函数尝试从 fast bins,last remainder chunk,small bins,large bins 和 top chunk 都失败之后,就会使用 sYSMALLOc()函数直接向系统申请内存用于分配所需的 chunk。

正如该函数头的注释所言,该函数用于当前堆内存不足时,需要向系统申请更多的内存。

/*
   sysmalloc handles malloc cases requiring more memory from the system.
   On entry, it is assumed that av->top does not have enough
   space to service request for nb bytes, thus requiring that av->top
   be extended or replaced.
 */

基本定义

static void *sysmalloc(INTERNAL_SIZE_T nb, mstate av) {
  mchunkptr old_top;        /* incoming value of av->top */
  INTERNAL_SIZE_T old_size; /* its size */
  char *old_end;            /* its end address */

  long size; /* arg to first MORECORE or mmap call */
  char *brk; /* return value from MORECORE */

  long correction; /* arg to 2nd MORECORE call */
  char *snd_brk;   /* 2nd return val */

  INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */
  INTERNAL_SIZE_T end_misalign;   /* partial page left at end of new space */
  char *aligned_brk;              /* aligned offset into brk */

  mchunkptr p;                  /* the allocated/returned chunk */
  mchunkptr remainder;          /* remainder frOm allocation */
  unsigned long remainder_size; /* its size */

  size_t pagesize = GLRO(dl_pagesize);
  bool tried_mmap = false;

我们可以主要关注一下 pagesize,其

#ifndef EXEC_PAGESIZE
#define EXEC_PAGESIZE   4096
#endif
# define GLRO(name) _##name
size_t _dl_pagesize = EXEC_PAGESIZE;

所以,pagesize=4096=0x1000

考虑 mmap

正如开头注释所言如果满足如下任何一种条件

  1. 没有分配堆。 一般为arena都被锁的情况
  2. 申请的内存大于 mp_.mmap_threshold,并且 mmap 的数量小于最大值,就可以尝试使用 mmap。

默认情况下,临界值为

static struct malloc_par mp_ = {
    .top_pad = DEFAULT_TOP_PAD,
    .n_mmaps_max = DEFAULT_MMAP_MAX,
    .mmap_threshold = DEFAULT_MMAP_THRESHOLD,
    .trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof(long) == 4 ? 2 : 8))
    .arena_test = NARENAS_FROM_NCORES(1)
#if USE_TCACHE
        ,
    .tcache_count = TCACHE_FILL_COUNT,
    .tcache_bins = TCACHE_MAX_BINS,
    .tcache_max_bytes = tidx2usize(TCACHE_MAX_BINS - 1),
    .tcache_unsorted_limit = 0 /* No limit.  */
#endif
};

DEFAULT_MMAP_THRESHOLD128*1024 字节,即 128 K

#ifndef DEFAULT_MMAP_THRESHOLD
#define DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN
#endif
/*
  MMAP_THRESHOLD_MAX and _MIN are the bounds on the dynamically
  adjusted MMAP_THRESHOLD.
*/

#ifndef DEFAULT_MMAP_THRESHOLD_MIN
#define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024)
#endif

#ifndef DEFAULT_MMAP_THRESHOLD_MAX
/* For 32-bit platforms we cannot increase the maximum mmap
   threshold much because it is also the minimum value for the
   maximum heap size and its alignment.  Going above 512k (i.e., 1M
   for new heaps) wastes too much address space.  */
#if __WORDSIZE == 32
#define DEFAULT_MMAP_THRESHOLD_MAX (512 * 1024)
#else
#define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long))
#endif
#endif

下面为这部分代码。

  /*
     If have mmap, and the request size meets the mmap threshold, and
     the system supports mmap, and there are few enough currently
     allocated mmapped regions, try to directly map this request
     rather than expanding top.
   */

  if (av == NULL ||
      ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) &&
       (mp_.n_mmaps < mp_.n_mmaps_max))) {
    char *mm; /* return value from mmap call*/

  try_mmap:
    /*
       Round up size to nearest page.  For mmapped chunks, the overhead
       is one SIZE_SZ unit larger than for normal chunks, because there
       is no following chunk whose prev_size field could be used.

       See the front_misalign handling below, for glibc there is no
       need for further alignments unless we have have high alignment.
     */
    if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
      size = ALIGN_UP(nb + SIZE_SZ, pagesize);
    else
      size = ALIGN_UP(nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
    tried_mmap = true;

    /* Don't try if size wraps around 0 */
    if ((unsigned long)(size) > (unsigned long)(nb)) {
      mm = (char *)(MMAP(0, size, PROT_READ | PROT_WRITE, 0));

      if (mm != MAP_FAILED) {    //mmap成功
        /*
           The offset to the start of the mmapped region is stored
           in the prev_size field of the chunk. This allows us to adjust
           returned start address to meet alignment requirements here
           and in memalign(), and still be able to compute proper
           address argument for later munmap in free() and realloc().
         */

        if (MALLOC_ALIGNMENT == 2 * SIZE_SZ) {
          /* For glibc, chunk2mem increases the address by 2*SIZE_SZ and
             MALLOC_ALIGN_MASK is 2*SIZE_SZ-1.  Each mmap'ed area is page
             aligned and therefore definitely MALLOC_ALIGN_MASK-aligned.  */
          assert(((INTERNAL_SIZE_T)chunk2mem(mm) & MALLOC_ALIGN_MASK) == 0);
          front_misalign = 0;
        } else
          front_misalign = (INTERNAL_SIZE_T)chunk2mem(mm) & MALLOC_ALIGN_MASK;
        if (front_misalign > 0) {
          correction = MALLOC_ALIGNMENT - front_misalign;
          p = (mchunkptr)(mm + correction);
          set_prev_size(p, correction);    //前面用于页对齐的空间,单独成一个chunk,但是是空的!!没有header,只会在真正mmap的chunk记录pre size
          set_head(p, (size - correction) | IS_MMAPPED);   //只用标记IS_MMAPPED
        } else {
          p = (mchunkptr)mm;
          set_prev_size(p, 0);        
          set_head(p, size | IS_MMAPPED);
        }

        /* update statistics */

        int new = atomic_exchange_and_add(&mp_.n_mmaps, 1) + 1;
        atomic_max(&mp_.max_n_mmaps, new);

        unsigned long sum;
        sum = atomic_exchange_and_add(&mp_.mmapped_mem, size) + size;
        atomic_max(&mp_.max_mmapped_mem, sum);

        check_chunk(av, p);

        return chunk2mem(p);
      }
    }
  }
#define ALIGN_DOWN(base, size)        ((base) & -((__typeof__ (base)) (size)))
#define ALIGN_UP(base, size)        ALIGN_DOWN ((base) + (size) - 1, (size))

size = ALIGN_UP(nb + SIZE_SZ, pagesize); //让以0x1000对齐(&使低位为0),即size就是pagesize

​ 如果要页对齐,那么页开头是空的,chunk一直到页末尾!!

mmap 失败且未分配arena

  /* There are no usable arenas and mmap also failed.  */
  if (av == NULL)
    return 0;

如果没分配arena,直接退出sysmalloc(这时malloc函数会返回null,回到程序中,一旦引用这个指针去寻址之类的,就会报错,程序终止。

下面是分配了arena但是mmap失败(包括第一次malloc,及申请的chunk小于mmap threshold的情况):

记录旧堆信息

  /* Record incoming configuration of top */

  old_top = av->top;
  old_size = chunksize(old_top);
  old_end = (char *)(chunk_at_offset(old_top, old_size));

  brk = snd_brk = (char *)(MORECORE_FAILURE);

检查旧堆信息 1

  /*
     If not the first time through, we require old_size to be
     at least MINSIZE and to have prev_inuse set.
   */

  assert((old_top == initial_top(av) && old_size == 0) ||
         ((unsigned long)(old_size) >= MINSIZE && prev_inuse(old_top) &&
          ((unsigned long)old_end & (pagesize - 1)) == 0));
/* Conveniently, the unsorted bin can be used as dummy(=fake) top on first call */
#define initial_top(M) (unsorted_chunks(M))

这个在还没真正分配arena(指main arena,因为thread arena在创建时就把top设好了,并在top header填写数据)时,top会设置一个初始值(选为unsorted bin的数组chunk,作为一个未malloc的标志。

这个检查要求满足其中任何一个条件

  1. old_top == initial_top(av) && old_size == 0,即如果是第一次的话,堆的大小需要是 0
  2. 已经初始化过的堆,那么
    1. (unsigned long)(old_size) >= MINSIZE && prev_inuse(old_top),堆的大小应该不小于 MINSIZE,并且前一个堆块应该处于使用中。
    2. ((unsigned long)old_end & (pagesize - 1)) == 0),堆的结束地址应该是页对齐的,由于页对齐的大小默认是 0x1000,所以低 12 个比特需要为 0。

这里heap=0怎么理解? main arena没有申请堆空间?

检查旧堆信息 2

  /* Precondition: not enough current space to satisfy nb request */
  assert((unsigned long)(old_size) < (unsigned long)(nb + MINSIZE));

根据 malloc 中的定义

static void *_int_malloc(mstate av, size_t bytes) {
    INTERNAL_SIZE_T nb;  /* normalized request size */

nb 应该是已经加上 chunk 头部的字节,为什么还要加上 MINSIZE呢?这是因为 top chunk 的大小应该至少预留 MINSIZE 空间,以便于合并 arena不能没有top chunk!!必须保证top chunk是一个大于等于MINSIZE的chunk

非 main_arena

  if (av != &main_arena) {
    heap_info *old_heap, *heap;
    size_t old_heap_size;

    /* First try to extend the current heap. */
    old_heap = heap_for_ptr(old_top);
    old_heap_size = old_heap->size;
    if ((long)(MINSIZE + nb - old_size) > 0 &&
        grow_heap(old_heap, MINSIZE + nb - old_size) == 0) {  //这里尝试在原heap基础上扩展top
      av->system_mem += old_heap->size - old_heap_size;
      set_head(old_top,
               (((char *)old_heap + old_heap->size) - (char *)old_top) |
                   PREV_INUSE);
    } else if ((heap = new_heap(nb + (MINSIZE + sizeof(*heap)), mp_.top_pad))) { //调用 new_heap()函数创建一个新的 sub_heap,由于这个 sub_heap 中至少需要容下大小 为 nb 的 chunk,大小为 MINSIZE 的 fencepost 和大小为 sizeof(*heap)的 heap_info 实例,所以 传入 new_heap()函数的分配大小为 nb + (MINSIZE + sizeof(*heap))。        申请heap的大小也要与0x1000对齐!!
      /* Use a newly allocated heap.  */
      heap->ar_ptr = av;
      heap->prev = old_heap;
      av->system_mem += heap->size;
      /* Set up the new top.  */
      top(av) = chunk_at_offset(heap, sizeof(*heap));
      set_head(top(av), (heap->size - sizeof(*heap)) | PREV_INUSE);

      /* Setup fencepost and free the old top chunk with a multiple of
         MALLOC_ALIGNMENT in size. */
      /* The fencepost takes at least MINSIZE bytes, because it might
         become the top chunk again later.  Note that a footer is set
         up, too, although the chunk is marked in use. */
      old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
      set_head(chunk_at_offset(old_top, old_size + 2 * SIZE_SZ),
               0 | PREV_INUSE);
      if (old_size >= MINSIZE) {
        set_head(chunk_at_offset(old_top, old_size),
                 (2 * SIZE_SZ) | PREV_INUSE);
        set_foot(chunk_at_offset(old_top, old_size), (2 * SIZE_SZ));
        set_head(old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
        _int_free(av, old_top, 1);    //把原top chunk作为普通chunk释放掉!!
      } else {
        set_head(old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
        set_foot(old_top, (old_size + 2 * SIZE_SZ));
      }
    } else if (!tried_mmap)
      /* We can at least try to use to mmap memory.  */
      goto try_mmap;    //如果增长sub_heap的可读可写区域大小和创建新sub_heap都失败了,尝试使用mmap() 函数直接从系统分配所需 chunk。 
  }
/* find the heap and corresponding arena for a given ptr */
 #define heap_for_ptr(ptr)   
		((heap_info *)((unsigned long)(ptr) & ~(HEAP_MAX_SIZE-1)))   //因为heap info在heap的开头,所以根据指针直接向低处对齐就行
#define arena_for_chunk(ptr)   
		(chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)->ar_ptr : &main_arena)  //线程arena的malloc_state在heap内,在heap info中有记录   main arena malloc_state

对于线程arena,首先尝试扩展原top chunk。如果失败,就尝试申请新的heap,heap大小根据nb+MINSIZE + sizeof(*heap)再跟0x1000对齐。 新heap先只设置除heap info外所有空间为top chunk。 把原top chunk切分出fencepost,前面的部分当成普通chunk释放掉

​ 设置原 top chunk 的 fencepost,fencepost 需要 MINSIZE 大小的内存空间,将该 old_size 减去 MINSIZE 得到原 top chunk 的有效内存空间,首先设置 fencepost 的第二个 chunk 的 size 为 0,并标识前一个 chunk 处于 inuse 状态。接着判断原 top chunk 的有效内存空间上是否大 于等于 MINSIZE,如果是,表示原 top chunk 可以分配出大于等于 MINSIZE 大小的 chunk,于 是将原 top chunk 切分成空闲 chunk 和 fencepost 两部分,先设置 fencepost 的第一个 chunk 的大小为 2*SIZE_SZ,并标识前一个 chunk 处于 inuse 状态,fencepost 的第一个 chunk 还需 要设置 foot,表示该 chunk 处于空闲状态,而 fencepost 的第二个 chunk 却标识第一个 chunk 处于 inuse 状态,因为不能有两个空闲 chunk 相邻,才会出现这么奇怪的 fencepost。另外其实 top chunk 切分出来的 chunk 也是处于空闲状态,但 fencepost 的第一个 chunk 却标识前一 个 chunk 为 inuse 状态,然后强制将该处于 inuse 状态的 chunk 调用_int_free()函数释放掉。 这样做完全是要遵循不能有两个空闲 chunk 相邻的约定。 如果原 top chunk 中有效空间不足 MINSIZE,则将整个原 top chunk 作为 fencepost,并设 置 fencepost 的第一个 chunk 的相关状态。

Main_arena 处理

计算内存

计算可以满足请求的内存大小。

else { /* av == main_arena */

    /* Request enough space for nb + pad + overhead */
    size = nb + mp_.top_pad + MINSIZE;

默认情况下 top_pad定义为

#ifndef DEFAULT_TOP_PAD
# define DEFAULT_TOP_PAD 131072
#endif

即 131072 字节,0x20000 字节。

是否连续

如果我们希望堆的空间连续的话,那么其实可以复用之前的内存。

    /*
       If contiguous, we can subtract out existing space that we hope to
       combine with new space. We add it back later only if
       we don't actually get contiguous space.
     */

    if (contiguous(av))
      size -= old_size;

一般情况下,主分配区使用 sbrk()从 heap 中分配内存,sbrk()返回连续的虚拟内存,这里调整需要分配的 size,减掉 top chunk 中已有空闲内存大小。 如果是第一次malloc,old_size=0,在这里sbrk去申请空间。

对齐页大小

    /*
       Round to a multiple of page size.
       If MORECORE is not contiguous, this ensures that we only call it
       with whole-page arguments.  And if MORECORE is contiguous and
       this is not first time through, this preserves page-alignment of
       previous calls. Otherwise, we correct to page-align below.
     */

    size = ALIGN_UP(size, pagesize);

将 size 按照页对齐,以页为单位分配连续虚拟内存。 sybrk在这里也以页为单位扩展heap!!

申请内存

第一次malloc也和正常一样,先尝试sbrk,不行再mmap 第一次只不过old_size=0而已,可以正常代入下面代码执行 如果是线程arena的第一次malloc,会在一开始的arena_get时就申请好堆,也就是初始化好了top chunk。

    /*
       Don't try to call MORECORE if argument is so big as to appear
       negative. Note that since mmap takes size_t arg, it may succeed
       below even if we cannot call MORECORE.
     */

    if (size > 0) {         //这个size是要申请的内存大小,不是old_size
      brk = (char *)(MORECORE(size));    //使用 sbrk()从 heap 中分配 size 大小的虚拟内存块
      LIBC_PROBE(memory_sbrk_more, 2, brk, size);
    }
可能成功
    if (brk != (char *)(MORECORE_FAILURE)) {
      /* Call the `morecore' hook if necessary.  */
      void (*hook)(void) = atomic_forced_read(__after_morecore_hook);
      if (__builtin_expect(hook != NULL, 0))
        (*hook)();  //如果 sbrk()分配成功,并且 morecore 的 hook 函数存在,调用 morecore 的 hook 函数。 
    }

这里竟然调用了一个 hook,有点意思。

失败

sbrk失败,考虑 mmap去扩展main arena。

else {
      /*
         If have mmap, try using it as a backup when MORECORE fails or
         cannot be used. This is worth doing on systems that have "holes" in
         address space, so sbrk cannot extend to give contiguous space, but
         space is available elsewhere.  
         Note that we ignore mmap max count and threshold limits, since the space will 			 not be used as a segregated mmap region.
       */

      /* Cannot merge with old top, so add its size back in */
      if (contiguous(av))
        size = ALIGN_UP(size + old_size, pagesize);   //在这里把old size加回来了

      /* If we are relying on mmap as backup, then use larger units */
      if ((unsigned long)(size) < (unsigned long)(MMAP_AS_MORECORE_SIZE))
        size = MMAP_AS_MORECORE_SIZE;    //1M

      /* Don't try if size wraps around 0 */
      if ((unsigned long)(size) > (unsigned long)(nb)) {
        char *mbrk = (char *)(MMAP(0, size, PROT_READ | PROT_WRITE, 0));

        if (mbrk != MAP_FAILED) {
          /* We do not need, and cannot use, another sbrk call to find end */
          brk = mbrk;
          snd_brk = brk + size;

          /*
             Record that we no longer have a contiguous sbrk region.
             After the first time mmap is used as backup, we do not
             ever rely on contiguous space since this could incorrectly
             bridge regions.
           */
          set_noncontiguous(av);  //如果所需分配的内存大小合法,使用 mmap()函数分配内存。如果分配成功,更新 brk 和 snd_brk,并将当前分配区属性设置为可分配不连续虚拟内存块。 
        }
      }
    }

可见,main arena的mmap heap没有heap info!! 由brk记录位置??

内存可能申请成功 这里是前面申请内存部分结束后

    if (brk != (char *)(MORECORE_FAILURE)) {
      if (mp_.sbrk_base == 0)
        mp_.sbrk_base = brk;
      av->system_mem += size;
情况 1 sbrk成功了,将old top更改成新的size
      /*
         If MORECORE extends previous space, we can likewise extend top size.
       */

      if (brk == old_end && snd_brk == (char *)(MORECORE_FAILURE))
        set_head(old_top, (size + old_size) | PREV_INUSE);
情况 2 - 意外内存耗尽
      else if (contiguous(av) && old_size && brk < old_end)
        /* Oops!  Someone else killed our space..  Can't touch anything.  */
        malloc_printerr("break adjusted to free malloc space");
处理其他意外情况
      /*
         Otherwise, make adjustments:

       * If the first time through or noncontiguous, we need to call sbrk
          just to find out where the end of memory lies.

       * We need to ensure that all returned chunks from malloc will meet
          MALLOC_ALIGNMENT

       * If there was an intervening foreign sbrk, we need to adjust sbrk
          request size to account for fact that we will not be able to
          combine new space with existing space in old_top.

       * Almost all systems internally allocate whole pages at a time, in
          which case we might as well use the whole last page of request.
          So we allocate enough more memory to hit a page boundary now,
          which in turn causes future contiguous calls to page-align.
       */

      else {
        front_misalign = 0;
        end_misalign = 0;
        correction = 0;
        aligned_brk = brk;

执行到这个分支,意味着 sbrk()返回的 brk 值大于原 top chunk 的结束地址,那么新的地 址与原 top chunk 的地址不连续,可能是由于外部其它地方调用 sbrk()函数,这里需要处理地址的重新对齐问题。

​ 因为如果外部调用sbrk,而之前malloc指向sbrk时是根据 old brk+ nb去对齐的。而真实brk比原来多,有可能现在虽然对齐但扩展的空间不够用,所以现在要重新对齐。

处理连续内存
        /* handle contiguous cases */
        if (contiguous(av)) {
          /* Count foreign sbrk as system_mem.  */
          if (old_size)
            av->system_mem += brk - old_end;  //如果本分配区可分配连续虚拟内存,并且有外部调用了 sbrk()函数,将外部调用 sbrk() 分配的内存计入当前分配区所分配内存统计中。 

          /* Guarantee alignment of first new chunk made from this space */

          front_misalign = (INTERNAL_SIZE_T)chunk2mem(brk) & MALLOC_ALIGN_MASK;
          if (front_misalign > 0) {
            /*
               Skip over some bytes to arrive at an aligned position.
               We don't need to specially mark these wasted front bytes.
               They will never be accessed anyway because
               prev_inuse of av->top (and any chunk created from its start)
               is always true after initialization.
             */

            correction = MALLOC_ALIGNMENT - front_misalign;
            aligned_brk += correction;
          }			//计算当前的 brk 要矫正的字节数据,保证 brk 地址按 MALLOC_ALIGNMENT 对齐。 

          /*
             If this isn't adjacent to existing space, then we will not
             be able to merge with old_top space, so must add to 2nd request.
           */

          correction += old_size;

          /* Extend the end address to hit a page boundary */
          end_misalign = (INTERNAL_SIZE_T)(brk + size + correction);
          correction += (ALIGN_UP(end_misalign, pagesize)) - end_misalign;

          assert(correction >= 0);
          snd_brk = (char *)(MORECORE(correction));
		//由于原 top chunk 的地址与当前 brk 不相邻,也就不能再使用原 top chunk 的内存了,需要重新为所需 chunk 分配足够的内存,将原 top chunk 的大小加到矫正值中,从当前 brk 中 分配所需 chunk,计算出未对齐的 chunk 结束地址 end_misalign,然后将 end_misalign 按照 页对齐计算出需要矫正的字节数加到矫正值上。然后再调用 sbrk()分配矫正值大小的内存, 如果 sbrk()分配成功,则当前的 top chunk 中可以分配出所需的连续内存的 chunk。 
          /*
             If can't allocate correction, try to at least find out current
             brk.  It might be enough to proceed without failing.

             Note that if second sbrk did NOT fail, we assume that space
             is contiguous with first sbrk. This is a safe assumption unless
             program is multithreaded but doesn't use locks and a foreign sbrk
             occurred between our first and second calls.
           */

          if (snd_brk == (char *)(MORECORE_FAILURE)) {
            correction = 0;
            snd_brk = (char *)(MORECORE(0));   //如果 sbrk()执行失败,更新当前 brk 的结束地址。 
          } else {
            /* Call the `morecore' hook if necessary.  */
            void (*hook)(void) = atomic_forced_read(__after_morecore_hook);
            if (__builtin_expect(hook != NULL, 0))
              (*hook)();
          }
        }
处理不连续内存
        /* handle non-contiguous cases */
        else {
          if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
            /* MORECORE/mmap must correctly align */
            assert(((unsigned long)chunk2mem(brk) & MALLOC_ALIGN_MASK) == 0);
          else {
            front_misalign =
                (INTERNAL_SIZE_T)chunk2mem(brk) & MALLOC_ALIGN_MASK;
            if (front_misalign > 0) {
              /*
                 Skip over some bytes to arrive at an aligned position.
                 We don't need to specially mark these wasted front bytes.
                 They will never be accessed anyway because
                 prev_inuse of av->top (and any chunk created from its start)
                 is always true after initialization.
               */

              aligned_brk += MALLOC_ALIGNMENT - front_misalign;
            }
          }

          /* Find out current end of memory */
          if (snd_brk == (char *)(MORECORE_FAILURE)) {
            snd_brk = (char *)(MORECORE(0));
          }
        }

执行到这里,意味着 brk 是用 mmap()分配的,断言 brk 一定是按 MALLOC_ALIGNMENT 对齐的,因为 mmap()返回的地址按页对齐。如果 brk 的结束地址非法,使用 morecore 获得 当前 brk 的结束地址。

调整

第一次malloc时的top chunk在这里真正分配出来。

        /* Adjust top based on results of second sbrk */
        if (snd_brk != (char *)(MORECORE_FAILURE)) {
          av->top = (mchunkptr)aligned_brk;
          set_head(av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
          av->system_mem += correction;
		//如果 brk 的结束地址合法,设置当前分配区的 top chunk 为 brk,设置 top chunk 的大小, 并更新分配区的总分配内存量。 
          /*
             If not the first time through, we either have a
             gap due to foreign sbrk or a non-contiguous region.  Insert a
             double fencepost at old_top to prevent consolidation with space
             we don't own. These fenceposts are artificial chunks that are
             marked as inuse and are in any case too small to use.  We need
             two to make sizes and alignments work out.
           */

          if (old_size != 0) {    //不是第一次malloc的话,要把old top chunk分割出fencepost并将空闲部分释放掉
            /*
               Shrink old_top to insert fenceposts, keeping size a
               multiple of MALLOC_ALIGNMENT. We know there is at least
               enough space in old_top to do this.
             */
            old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
            set_head(old_top, old_size | PREV_INUSE);

            /*
               Note that the following assignments completely overwrite
               old_top when old_size was previously MINSIZE.  This is
               intentional. We need the fencepost, even if old_top otherwise
               gets lost.
             */
            set_head(chunk_at_offset(old_top, old_size),
                     (2 * SIZE_SZ) | PREV_INUSE);
            set_head(chunk_at_offset(old_top, old_size + 2 * SIZE_SZ),
                     (2 * SIZE_SZ) | PREV_INUSE);

            /* If possible, release the rest. */
            if (old_size >= MINSIZE) {
              _int_free(av, old_top, 1);
            }
          }
        }
      }

需要注意的是,在这里程序将旧的 top chunk 进行了释放,那么其会根据大小进入不同的 bin 或 tcache 中

设置原 top chunk 的 fencepost,fencepost 需要 MINSIZE 大小的内存空间,将该 old_size 减去 MINSIZE 得到原 top chunk 的有效内存空间,我们可以确信原 top chunk 的有效内存空间 一定大于 MINSIZE,将原 top chunk 切分成空闲 chunk 和 fencepost 两部分,首先设置切分出 来的 chunk 的大小为 old_size,并标识前一个 chunk 处于 inuse 状态,原 top chunk 切分出来 的chunk本应处于空闲状态,但fencepost的第一个chunk却标识前一个chunk为inuse状态, 然后强制将该处于 inuse 状态的 chunk 调用_int_free()函数释放掉。然后设置 fencepost 的第 一个 chunk 的大小为 2SIZE_SZ,并标识前一个 chunk 处于 inuse 状态,然后设置 fencepost 的第二个 chunk 的 size 为 2SIZE_SZ,并标识前一个 chunk 处于 inuse 状态。这里的主分配区 的 fencepost 与非主分配区的 fencepost 不同,主分配区 fencepost 的第二个 chunk 的大小设 置为 2*SIZE_SZ,而非主分配区的 fencepost 的第二个 chunk 的大小设置为 0。

更新最大内存 从这里开始,线程arena和main arena的if就结束了,统一切割top chunk去分配chunk 因为之前都是设置的top chunk

  if ((unsigned long)av->system_mem > (unsigned long)(av->max_system_mem))
    av->max_system_mem = av->system_mem;
  check_malloc_state(av);

分配内存块

获取大小
  /* finally, do the allocation */
  p = av->top;
  size = chunksize(p);
切分 TOP
  /* check that one of the above allocation paths succeeded */
  if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) {
    remainder_size = size - nb;
    remainder = chunk_at_offset(p, nb);
    av->top = remainder;
    set_head(p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
    set_head(remainder, remainder_size | PREV_INUSE);
    check_malloced_chunk(av, p, nb);
    return chunk2mem(p);
  }

捕捉所有错误

  /* catch all failure paths */
  __set_errno(ENOMEM);
  return 0;

malloc_init_state

/*
   Initialize a malloc_state struct.
   This is called only from within malloc_consolidate, which needs
   be called in the same contexts anyway.  It is never called directly
   outside of malloc_consolidate because some optimizing compilers try
   to inline it at all call points, which turns out not to be an
   optimization at all. (Inlining it in malloc_consolidate is fine though.)
 */

static void malloc_init_state(mstate av) {
    int     i;
    mbinptr bin;

    /* Establish circular links for normal bins */
    for (i = 1; i < NBINS; ++i) {
        bin     = bin_at(av, i);
        bin->fd = bin->bk = bin;
    }

#if MORECORE_CONTIGUOUS
    if (av != &main_arena)
#endif
        set_noncontiguous(av);
    if (av == &main_arena) set_max_fast(DEFAULT_MXFAST);
    // 设置 flags 标记目前没有fast chunk
    av->flags |= FASTCHUNKS_BIT;
    // 初始值就是 unsorted bin
    av->top = initial_top(av);
}

malloc_consolidate

该函数主要有两个功能

  1. 若 fastbin 未初始化,即 global_max_fast 为 0,那就初始化 malloc_state。
  2. 如果已经初始化的话,就合并 fastbin 中的 chunk。

基本的流程如下

初始

static void malloc_consolidate(mstate av) {
    mfastbinptr *fb;             /* current fastbin being consolidated */
    mfastbinptr *maxfb;          /* last fastbin (for loop control) */
    mchunkptr    p;              /* current chunk being consolidated */
    mchunkptr    nextp;          /* next chunk to consolidate */
    mchunkptr    unsorted_bin;   /* bin header */
    mchunkptr    first_unsorted; /* chunk to link to */

    /* These have same use as in free() */
    mchunkptr       nextchunk;
    INTERNAL_SIZE_T size;
    INTERNAL_SIZE_T nextsize;
    INTERNAL_SIZE_T prevsize;
    int             nextinuse;
    mchunkptr       bck;
    mchunkptr       fwd;

合并 chunk

    /*
      If max_fast is 0, we know that av hasn't
      yet been initialized, in which case do so below
    */
    // 说明 fastbin 已经初始化
    if (get_max_fast() != 0) {
        // 清空 fastbin 标记
        // 因为要合并 fastbin 中的 chunk 了。
        clear_fastchunks(av);
        //
        unsorted_bin = unsorted_chunks(av);

        /*
          Remove each chunk from fast bin and consolidate it, placing it
          then in unsorted bin. Among other reasons for doing this,
          placing in unsorted bin avoids needing to calculate actual bins
          until malloc is sure that chunks aren't immediately going to be
          reused anyway.
        */
        // 按照 fd 顺序遍历 fastbin 的每一个 bin,将 bin 中的每一个 chunk 合并掉。
        maxfb = &fastbin(av, NFASTBINS - 1);
        fb    = &fastbin(av, 0);
        do {
            p = atomic_exchange_acq(fb, NULL);
            if (p != 0) {
                do {
                    check_inuse_chunk(av, p);
                    nextp = p->fd;

                    /* Slightly streamlined version of consolidation code in
                     * free() */
                    size      = chunksize(p);
                    nextchunk = chunk_at_offset(p, size);
                    nextsize  = chunksize(nextchunk);

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

                    if (nextchunk != av->top) {
                        // 判断 nextchunk 是否是空闲的。
                        nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

                        if (!nextinuse) {
                            size += nextsize;
                            unlink(av, nextchunk, bck, fwd);
                        } else
                         // 设置 nextchunk 的 prev inuse 为0,以表明可以合并当前 fast chunk。
                            clear_inuse_bit_at_offset(nextchunk, 0);

                        first_unsorted     = unsorted_bin->fd;
                        unsorted_bin->fd   = p;
                        first_unsorted->bk = p;

                        if (!in_smallbin_range(size)) {
                            p->fd_nextsize = NULL;
                            p->bk_nextsize = NULL;
                        }

                        set_head(p, size | PREV_INUSE);
                        p->bk = unsorted_bin;
                        p->fd = first_unsorted;
                        set_foot(p, size);
                    }

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

                } while ((p = nextp) != 0);
            }
        } while (fb++ != maxfb);

初始化

说明 fastbin 还没有初始化。

    } else {
        malloc_init_state(av);
        // 在非调试情况下没有什么用,在调试情况下,做一些检测。
        check_malloc_state(av);
    }

注意点:

  • hook在data/bss段!! 注意,libc中的malloc/free等hook都是弱符号,方便用户自己在源代码中自定义hook

image-20200922234036123

image-20200827133742794

  • 可见,**largebin数组chunk只是单向记录fd和bk,large chunk链 依然像small bin一样,是循环链表。 **

image-20200920154704890

  • consolidate有两种情况:1.large request会先考虑consolidate,合并碎片,凑出large chunk 2.top chunk不够且是small request时,会重新consolidate一遍,然后遍历unsorted bin,开始。

  • main_arena什么时候初始化?? —-一开始state未初始化,全0,那么fastbin判断不通过;如果是small request,在small bin过程中进行consolidate去初始化state;如果是large request,也会consolidate去初始化state; 之后就到了判断top chunk,取top chunk的size与nb+minsize比较。因为to’p chunk的初始值为unsorted bin数组chunk,对应size是last remainder的位置,一开始=0,那么也就判断出不能使用top chunk。所以最后去sysmalloc通过sbrk去申请内存。

  • 如何找到各thread arena的malloc_state位置? —线程的tsd,内核会记录线程的相关信息

  • 如何找到heap的heap info –在heap segment的开头 有对应的函数去找
  • mmap出来的chunk,会直接munmap,释放掉内存,不入bin mmap的块就不归arena管了 is_mapped标志位开启后,NON_MAIN_ARENA就不管了 mmapped chunks are neither in an arena, nor adjacent to a freed chunk

  • heap info记录的是malloc_state的地址
  • top chunk初始值是unsorted bin的数组chunk
  • arena_for_chunk函数 如何确定一个chunk的arena? –如果是thread arena,看所在heap的开头的heap info指向的arena 如果是主线程,看该chunk的NOT_IN_MAIN_ARENA标志位
  • void *p = sysmalloc(nb, av) 函数怎么申请chunk的? –首先尝试mmap,不符要求就sbrk或new heap,还不行就mmap扩展原heap
  • 线程的arena都是在arena_get中取到/创建的,返回的都是初始化好的arena;main arena因为state在libc中,所以直接取,但是第一次时并没有分配内存。