Glibc:浅谈 malloc() 函数具体实现

简介

用户主动向系统申请堆空间,可以通过 malloc() 或者 realloc() 等函数来操作,其中最主要的函数就是 malloc() 函数。malloc() 函数的定义位于 glibc 的 malloc.c 文件中。实际上在 glibc 内部 malloc() 函数只是__libc_malloc() 函数的别名,而 __libc_malloc() 函数的工作又主要由 _int_malloc() 完成。因此,分析malloc() 函数,即是分析 __libc_malloc() 以及 _int_malloc() 这两个函数。

本文以 glibc 2.24 版本中的 malloc() 函数为讲述对象,从源代码的角度简要地分析 malloc() 函数的具体实现,注意因为完整的 malloc 分配流程过于复杂,所以这里并不打算对 malloc 进行一次完全的剖析,而只是分析 malloc 在处理小空间分配申请时的一个基本的流程。注意,要读懂此文需要读者对 Linux 平台的堆分配有基本了解,明白 binchunk 等基本要素的含义。

源代码

首先,malloc__libc_malloc 的别名:

strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)

__libc_malloc()

void *
__libc_malloc (size_t bytes)
{
  mstate ar_ptr;
  void *victim;

  void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));

  arena_get (ar_ptr, bytes);

  victim = _int_malloc (ar_ptr, bytes);
  /* 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);
    }

  if (ar_ptr != NULL)
    (void) mutex_unlock (&ar_ptr->mutex);

  assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
          ar_ptr == arena_for_chunk (mem2chunk (victim)));
  return victim;
}
libc_hidden_def (__libc_malloc)

_int_malloc()

/*
   ------------------------------ malloc ------------------------------
 */

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);

  /* 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);
      if (p != NULL)
    alloc_perturb (p, bytes);
      return p;
    }

  /*
     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 ()))
    {
      idx = fastbin_index (nb);
      mfastbinptr *fb = &fastbin (av, idx);
      mchunkptr pp = *fb;
      do
        {
          victim = pp;
          if (victim == NULL)
            break;
        }
      while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
             != victim);
      if (victim != 0)
        {
          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;
            }
          check_remalloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }

  /*
     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))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

      if ((victim = last (bin)) != bin)
        {
          if (victim == 0) /* initialization check */
            malloc_consolidate (av);
          else
            {
              bck = victim->bk;
    if (__glibc_unlikely (bck->fd != victim))
                {
                  errstr = "malloc(): smallbin double linked list corrupted";
                  goto errout;
                }
              set_inuse_bit_at_offset (victim, nb);
              bin->bk = bck;
              bck->fd = bin;

              if (av != &main_arena)
                victim->size |= NON_MAIN_ARENA;
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }
        }
    }

  /*
     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
    {
      idx = largebin_index (nb);
      if (have_fastchunks (av))
        malloc_consolidate (av);
    }

  /*
     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 (;; )
    {
      int iters = 0;
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          bck = victim->bk;
          if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
              || __builtin_expect (victim->size > av->system_mem, 0))
            malloc_printerr (check_action, "malloc(): memory corruption",
                             chunk2mem (victim), av);
          size = chunksize (victim);

          /*
             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_size = size - nb;
              remainder = chunk_at_offset (victim, nb);
              unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
              av->last_remainder = remainder;
              remainder->bk = remainder->fd = unsorted_chunks (av);
              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;
            }

          /* remove from unsorted list */
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);

          /* Take now instead of binning if exact fit */

          if (size == nb)
            {
              set_inuse_bit_at_offset (victim, size);
              if (av != &main_arena)
                victim->size |= NON_MAIN_ARENA;
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

          /* place chunk in bin */

          if (in_smallbin_range (size))
            {
              victim_index = smallbin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
            }
          else
            {
              victim_index = largebin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;

              /* maintain large bins in sorted order */
              if (fwd != bck)
                {
                  /* Or with inuse bit to speed comparisons */
                  size |= PREV_INUSE;
                  /* if smaller than smallest, bypass loop below */
                  assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
                  if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
                    {
                      fwd = bck;
                      bck = bck->bk;

                      victim->fd_nextsize = fwd->fd;
                      victim->bk_nextsize = fwd->fd->bk_nextsize;
                      fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
                    }
                  else
                    {
                      assert ((fwd->size & NON_MAIN_ARENA) == 0);
                      while ((unsigned long) size < fwd->size)
                        {
                          fwd = fwd->fd_nextsize;
                          assert ((fwd->size & NON_MAIN_ARENA) == 0);
                        }

                      if ((unsigned long) size == (unsigned long) fwd->size)
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else
                        {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;
                    }
                }
              else
                victim->fd_nextsize = victim->bk_nextsize = victim;
            }

          mark_bin (av, victim_index);
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;

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

      /*
         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))
        {
          bin = bin_at (av, idx);

          /* skip scan if empty or largest chunk is too small */
          if ((victim = first (bin)) != bin &&
              (unsigned long) (victim->size) >= (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) && victim->size == victim->fd->size)
                victim = victim->fd;

              remainder_size = size - nb;
              unlink (av, victim, bck, fwd);

              /* Exhaust */
              if (remainder_size < MINSIZE)
                {
                  set_inuse_bit_at_offset (victim, size);
                  if (av != &main_arena)
                    victim->size |= NON_MAIN_ARENA;
                }
              /* 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))
                    {
                      errstr = "malloc(): corrupted unsorted chunks";
                      goto errout;
                    }
                  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;
            }
        }

      /*
         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;
      bin = bin_at (av, idx);
      block = idx2block (idx);
      map = av->binmap[block];
      bit = idx2bit (idx);

      for (;; )
        {
          /* Skip rest of block if there are no more set bits in this block.  */
          if (bit > map || bit == 0)
            {
              do
                {
                  if (++block >= BINMAPSIZE) /* out of bins */
                    goto use_top;
                }
              while ((map = av->binmap[block]) == 0);

              bin = bin_at (av, (block << BINMAPSHIFT));
              bit = 1;
            }

          /* Advance to bin with set bit. There must be one. */
          while ((bit & map) == 0)
            {
              bin = next_bin (bin);
              bit <<= 1;
              assert (bit != 0);
            }

          /* Inspect the bin. It is likely to be non-empty */
          victim = last (bin);

          /*  If a false alarm (empty bin), clear the bit. */
          if (victim == bin)
            {
              av->binmap[block] = map &= ~bit; /* Write through */
              bin = next_bin (bin);
              bit <<= 1;
            }

          else
            {
              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);

              /* Exhaust */
              if (remainder_size < MINSIZE)
                {
                  set_inuse_bit_at_offset (victim, size);
                  if (av != &main_arena)
                    victim->size |= NON_MAIN_ARENA;
                }

              /* 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))
                    {
                      errstr = "malloc(): corrupted unsorted chunks 2";
                      goto errout;
                    }
                  remainder->bk = bck;
                  remainder->fd = fwd;
                  bck->fd = remainder;
                  fwd->bk = remainder;

                  /* advertise as last remainder */
                  if (in_smallbin_range (nb))
                    av->last_remainder = 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;
            }
        }

    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.)
       */

      victim = av->top;
      size = chunksize (victim);

      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
        {
          remainder_size = size - nb;
          remainder = chunk_at_offset (victim, nb);
          av->top = remainder;
          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;
        }

      /* When we are using atomic ops to free fast chunks we can get
         here for all block sizes.  */
      else if (have_fastchunks (av))
        {
          malloc_consolidate (av);
          /* restore original bin index */
          if (in_smallbin_range (nb))
            idx = smallbin_index (nb);
          else
            idx = largebin_index (nb);
        }

      /*
         Otherwise, relay to handle system-dependent cases
       */
      else
        {
          void *p = sysmalloc (nb, av);
          if (p != NULL)
            alloc_perturb (p, bytes);
          return p;
        }
    }
}

__libc_malloc() 分析

参数

首先看 __libc_malloc() 的参数:

void * __libc_malloc (size_t bytes)

这里的 bytes 即是用户申请分配的空间的大小,并且注意这是用户提出申请的原始大小,比如说 malloc(12),那么这里 bytes 就是 12,而非经过 request2size 宏计算得到的对应 chunk 的大小。

__malloc_hook 全局钩子

ptmalloc 定义了一个全局钩子 __malloc_hook,这个钩子会被赋值为 malloc_hook_ini 函数:

void *weak_variable (*__malloc_hook)
  (size_t __size, const void *) = malloc_hook_ini;

如果我们需要自定义堆分配函数,那么就可以把 __malloc_hook 重新设置成我们自定义的函数,在 __libc_malloc 的最开始处会判断是否调用 __malloc_hook。也就是说 ptmalloc 给我们提供了一个机会去使用自己定义的堆分配函数来完成对堆空间的申请,申请完成后直接返回,如下:

  void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));

如果我们没有自定义堆分配函数,而是选择默认的 ptmalloc 来帮我们完成申请,那么在用户在第一次调用 malloc 函数时会首先转入 malloc_hook_ini 函数里面,这个函数的定义在 hook.c 文件,如下:

static void *
malloc_hook_ini (size_t sz, const void *caller)
{
  __malloc_hook = NULL;
  ptmalloc_init ();
  return __libc_malloc (sz);
}

可见在 malloc_hook_ini 会把 __malloc_hook 设置为空,然后调用 ptmalloc_init 函数,这个函数的工作是完成对 ptmalloc 的初始化,最后又重复调用 __libc_malloc 函数。

因此可知,在我们第一次调用 malloc 申请堆空间的时候,首先会进入 malloc_hook_ini 函数里面进行对 ptmalloc 的初始化工作,然后再次进入 __libc_malloc 的时候,此时钩子 __malloc_hook 已经被置空了,从而继续执行剩余的代码,即转入 _int_malloc 函数。

换个说法,第一次调用 malloc 函数时函数调用路径如下:

malloc -> __libc_malloc -> __malloc_hook(即malloc_hook_ini) -> ptmalloc_init -> __libc_malloc -> _int_malloc

以后用户再调用 malloc 函数的时候,路径将是这样的:

malloc -> __libc_malloc -> _int_malloc

ptmalloc_init

这里简单说一下 ptmalloc_init 函数,ptmalloc_init 的定义在 arena.c 文件里面,它里面有这样的一些操作:

static void
ptmalloc_init (void)
{
  if (__malloc_initialized >= 0)
    return;

  __malloc_initialized = 0;

  // 初始化 ptmalloc 操作
  ...
  ...

  __malloc_initialized = 1;
}

进入 ptmalloc_init,首先判断 __malloc_initialized 的值,__malloc_initialized 是一个全局变量,它标记着 ptmalloc 的初始化状态,如下:

  • >0 –> 初始化完成
  • =0 –> 正在初始化
  • <0 –> 尚未初始化

在 ptmalloc_init 中完成对 ptmalloc 的初始化工作后,置 __malloc_initialized 为 1,表示 ptmalloc 已经被初始化,之后再次进入 ptmalloc_init 时就会直接退出,不会重复初始化。

转入 _int_malloc()

经过全局钩子 __malloc_hook 的折腾,我们就准备进入 _int_malloc 了:

void *
__libc_malloc (size_t bytes)
{
  mstate ar_ptr;
  void *victim;

  void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));

  arena_get (ar_ptr, bytes);

  victim = _int_malloc (ar_ptr, bytes);

  ...

  if (ar_ptr != NULL)
    (void) mutex_unlock (&ar_ptr->mutex);

  ...

  return victim;
}

由以上代码可知,首先调用 arena_get 宏获取到分配区 ar_ptr,分配区管理着一大片空闲的空间,我们申请的堆空间就位于这片空间里面。arena_get 不仅负责分配区的获取,还负责分配区的加锁操作,因为 ptmalloc 是支持多线程的,但是一个分配区在同一时间只能被一个线程操作,所以需要加锁。如果当前分配区链上都没有空闲的分配区,那么 arena_get 还负责创建一个新的分配区并返回,bytes 参数就是在创建新的分配区时用于作为新的分配区的空间大小的参考的。

在调用完 _int_malloc 之后还有一小段代码用于检查分配是否成功,如果不成功则会继续寻找可用的分配区进行分配,这里就不细说了。函数的最后调用 mutex_unlock 对分配区解锁并返回申请到的空间 victim。

_int_malloc 分析

0x0 计算 chunk size

上面说到,__libc_malloc 的参数 bytes 是用户提交的最原始的空间大小,但是 ptmalloc 分配时是以 chunk 为单位分配的,由原始的空间大小计算得到 chunk 的空间大小由 checked_request2size 宏完成:

  /*
     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);

nb 即为计算得到的 chunk 的大小,它在我们后面的分析里会一直出现,很重要。

0x1 是否命中 fastbins

从 fastbin 中分配 chunk 相当简单:

  /*
     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 ()))
    {
      idx = fastbin_index (nb);
      mfastbinptr *fb = &fastbin (av, idx);
      mchunkptr pp = *fb;
      do
        {
          victim = pp;
          if (victim == NULL)
            break;
        }
      while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
             != victim);
      if (victim != 0)
        {
          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;
            }
          check_remalloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }

通过 get_max_fast 宏判断如果 nb 属于 fastbins,首先获取到 fastbins 上相应的 fastbin 链表,然后获取链表上第一个 chunk。如果该 chunk 不为空,那么更改链表头指向第二个 chunk,并通过 chunk2mem 宏返回 chunk 中数据区的地址即可。注意这里无需设置当前空闲 chunk 的使用标志位,因为 fastbins 中的空闲 chunk 为了避免被合并,它的使用标志位依旧是 1,即依然在“使用中”。

如果该 chunk 为空,说明当前 fastbin 中没有刚好匹配 nb 大小的空闲 chunk。注意在 fastbins 中分配 chunk 时是精确匹配而非最佳匹配。

0x2 是否命中 smallbins

如果在 fastbins 里面没有找到满足要求的空闲 chunk 或者 nb 不属于 fastbins,并且 nb 属于 smallbins,那么执行以下代码:

/*
     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))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

      if ((victim = last (bin)) != bin)
        {
          if (victim == 0) /* initialization check */
            malloc_consolidate (av);
          else
            {
              bck = victim->bk;
    if (__glibc_unlikely (bck->fd != victim))
                {
                  errstr = "malloc(): smallbin double linked list corrupted";
                  goto errout;
                }
              set_inuse_bit_at_offset (victim, nb);
              bin->bk = bck;
              bck->fd = bin;

              if (av != &main_arena)
                victim->size |= NON_MAIN_ARENA;
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }
        }
    }
    else{
      ...
    }

smallbins 依然是精确匹配。首先获取到所在 smallbin,然后通过 (victim = last (bin)) != bin 判断该 smallbin 是否为空。如果为空则说明获取失败,没有合适的空闲 chunk,直接跳过剩余代码去执行后面的步骤。否则判断获取到的链表尾的 chunk,即代码中的 victim 是否为空,如果为空,说明当前分配区还没有初始化,则 smallbin 还没有被初始化,此时会转至 malloc_consolidate 函数进行初始化后跳过剩余代码。否则获取链表尾部的空闲 chunk 并返回。注意这里还要通过 set_inuse_bit_at_offset (victim, nb); 设置当前返回 chunk 的使用标志位,以及非主分配区标志位。

值得注意的是,如果在 smallbins 已经初始化但没有找到合适的空闲 chunk,那么是不会调用 malloc_consolidate 来清空 fastbins 的。

0x3 非 smallbins 则转入 malloc_consolidate 整理 fastbins

如果 nb 不属于 smallbins,说明申请的空间为 largebins,这时候不会立即检查 largebins,而是会调用 malloc_consolidate 函数将 fastbins 里面的空闲 chunk 合并整理到 unsortedbin 中,如下:

  if (in_smallbin_range (nb))
    {
    ...
    }
  /*
     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
    {
      idx = largebin_index (nb);
      if (have_fastchunks (av))
        malloc_consolidate (av);
    }

当然,在调用 malloc_consolidate 之前先通过 have_fastchunks 检查当前 fastbins 是否为空,如果本就已经空了,自然没有整理的必要。标记 fastbins 是否为空的是分配区管理的一个数据成员 flags,在 free 函数中当被释放空间插入到 fastbins 中时这个数据成员被设置,在 malloc_consolidate 函数中整理 fastbins 时这个数据成员被清除。

0x4 整理 unsortedbin 到 smallbins 以及 largebins

如果经过以上步骤都没有找到合适的空闲 chunk,此时将开始检查并整理 unsortedbin,注意这个过程是一边检查一边整理的,即如果有合适的就返回退出,如果没有就把 unsortedbin 中的每一个空闲 chunk 整理到 smallbins 或者 largebins 中。

首先进来是一个非常大的 for 循环,我把这个循环整理如下:

    for (;; )
    {
      int iters = 0;
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          ...
          ...
        }

      if (!in_smallbin_range (nb))
        {
          ...
          ...
        }

      for (;; )
        {
          ...
          ...
        }

    use_top:
      ...
      ...
    }

可见这个循环主要由四部分组成,我们先关注第一部分,也即是整理 unsortedbin,如下:

        while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          bck = victim->bk;
          if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
              || __builtin_expect (victim->size > av->system_mem, 0))
            malloc_printerr (check_action, "malloc(): memory corruption",
                             chunk2mem (victim), av);
          size = chunksize (victim);
      ...
        }

首先获取 unsortedbin 的第一个 chunk,即 victim,并且获取到它后面一个 chunk 以及它的大小,这里还进行了一些检查。

0x40 是否切割 last_remainder

接下来是一个判断,这个判断用到了 last_remainder 这个 chunk,它是分配区的一个特殊成员,和 top chunk 一样是一个特殊的 chunk:

           /*
             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_size = size - nb;
              remainder = chunk_at_offset (victim, nb);
              unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
              av->last_remainder = remainder;
              remainder->bk = remainder->fd = unsorted_chunks (av);
              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;
            }

具体的判断条件如下:

  • 申请空间 nb 在 smallbins 范围内
  • unsortedbin 仅有唯一一个空闲 chunk
  • 唯一的一个空闲 chunk 是 last_remainder
  • 唯一一个空闲 chunk 的大小可以进行切割

如果这四个条件同时满足,那么就将该唯一一个空闲 chunk 切割之后返回,剩余的空间作为新的 last_remainder 并插入到 unsortedbin 表头。

这里注意到,如果切割之后新的 last_remainder 不属于 smallbins,那么要把 fd_nextsize 以及 bk_nextsize 这两个成员给置空。

0x41 精确匹配则返回

unsortedbin 本来就是作为 smallbins 和 largebins 的缓冲区的存在,它里面存放着许多 free 时插入的空闲空间或者整理 fastbins 之后插入的空闲空间,很有可能这些空间里就有一个刚好能够精确匹配我们需要申请的空间大小。如果找到这样的空闲 chunk,自然是直接返回即可:

          /* Take now instead of binning if exact fit */

          if (size == nb)
            {
              set_inuse_bit_at_offset (victim, size);
              if (av != &main_arena)
                victim->size |= NON_MAIN_ARENA;
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

0x42 插入到 smallbins

如果当前空闲 chunk 不能精确匹配并且属于 smallbins,那么就将其插入到 smallbins 合适的 smallbin 中,注意是插入到链表头部。

          if (in_smallbin_range (size))
            {
              victim_index = smallbin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
            }
          else
            {
              ...
            }
          mark_bin (av, victim_index);
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;

0x43 插入到 largebins

插入到 largebins 要麻烦很多,因为 largebins 每一个链表上的 chunk 大小并不是相等的,而是处于一个范围内即可,而且每一个 largebin 并不是一组双向循环链表,而是两组,即 fd 和 bk 构成一个双向循环链表,而 fd_nextsize 和 bk_nextsize 组合构成另一个双向循环链表。因此在插入时不仅寻找合适的插入位置更加复杂,修复链表也多了许多工作。

0x430 插入到链表头

首先获取相应 largebin 的链表头 bck 以及第一个空闲 chunk 为 fwd,然后有一个判断:

          if (in_smallbin_range (size))
            {
              ...
            }
          else
            {
              victim_index = largebin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;

              /* maintain large bins in sorted order */
              if (fwd != bck)
                {
                  ...
                }
              else
                victim->fd_nextsize = victim->bk_nextsize = victim;
          }
          mark_bin (av, victim_index);
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;

当 fwd == bck 时,说明此时 largebin 为空,直接插入到链表头即可。

0x431 插入到链表尾

通过 bck->bk 即可找到当前 largebin 的最后一个 chunk,因为在 largebin 中 chunk 按大小从大到小排序,因此最后一个 cunk 也即是最小的一个 chunk。如果待插入 chunk 小于该最小的空闲 chunk,则插入到链表尾:

              if (fwd != bck)
                {
                  /* if smaller than smallest, bypass loop below */
                  assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
                  if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
                    {
                      fwd = bck;
                      bck = bck->bk;

                      victim->fd_nextsize = fwd->fd;
                      victim->bk_nextsize = fwd->fd->bk_nextsize;
                      fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
                    }                 
                }
0x432 插入到链表中间
                  if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
                    {
                      ...
                    }
                  else
                    {
                      assert ((fwd->size & NON_MAIN_ARENA) == 0);
                      while ((unsigned long) size < fwd->size)
                        {
                          fwd = fwd->fd_nextsize;
                          assert ((fwd->size & NON_MAIN_ARENA) == 0);
                        }

                      if ((unsigned long) size == (unsigned long) fwd->size)
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else
                        {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;
                    }

插入到链表中间时,首先通过一个 while 循环找到合适的插入位置,注意到这里用的是 fwd = fwd->fd_nextsize,这里就体现了第二组双向循环链表的作用,即可以用于加快寻找合适的插入位置的速度。

找到位置之后判断,当前待插入 chunk 的大小是否和当前位置所在一组相同尺寸的 chunks 的大小相同,如果相同,则插入到该组 chunks 的第二位。注意这里是插入到第二位,因为如果插入成为第一个,就要修改第二组双向循环链表,无疑耗费更多时间。

否则说明当前待插入 chunk 的大小和 largebin 中任何一组相同尺寸的 chunks 的大小都不相同,只能自己插入并成为一组新的相同尺寸的 chunks 的第一个 chunk。

0x44 中断整理 unsortedbin

中断整理 unsortedbin 有两种情况,一种是已经遍历整理完 unsortedbin 中所有空闲 chunk,在 while 判断处即可自然终止。另一种情况如下所示:

    while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {

          ...
          ...

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

可见 while 循环维护着一个计数器,当整理 unsortedbin 中的空闲 chunks 总数达到 10000 个的时候,就会 break 中断循环,避免因为整理 unsortedbin 而耗费太多时间。

0x5 从 smallbins 或 largebins 分配

来到这一步,unsortedbin 已经空了,我们继续从 smallbins 或者 largebins 分配。但对于 smallbins 以及 largebins 有一点不同的是,此时 smallbins 已经确定无法精确匹配了,只能使用最佳匹配,因为刚开始时经过了一轮对 smallbins 的扫描,即 nb 所对应的那一个 smallbin 已经确定不可能存在合适的空闲 chunk 了,而 largebin 则还存在精确匹配的可能。

0x50 从所在 largebin 精确分配或最佳匹配

首先判断 nb 是否属于 largebins,如果属于则检查 nb 对应的那一个 largebin 上是否有满足要求的空闲 chunk。注意在这个 largebin 上,也仅仅在这个 largebin 上还存在着精确匹配的可能。

进来先通过 (victim = first (bin)) != bin 判断当前 largebin 是否非空,并且最大的一个空闲 chunk 是否比 nb 要大:

       /*
         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))
        {
          bin = bin_at (av, idx);

          /* skip scan if empty or largest chunk is too small */
          if ((victim = first (bin)) != bin &&
              (unsigned long) (victim->size) >= (unsigned long) (nb))
            {
              ...
            }
        }

如果都满足,则通过一个 while 循环找到满足分配要求的那一组相同尺寸的空闲 chunks:

              victim = victim->bk_nextsize;
              while (((unsigned long) (size = chunksize (victim)) <
                      (unsigned long) (nb)))
                victim = victim->bk_nextsize;

如果这一组 chunks 的数量超过两个且大小刚好精确匹配,则返回第二个空闲 chunk,不选择第一个返回是因为返回第一个的话需要重新调整第二组双向循环链表,否则就直接返回找到的第一个 chunk。这里使用了 unlink 宏将选择的 chunk 脱链:

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

                remainder_size = size - nb;
              unlink (av, victim, bck, fwd);

这还不行,因为找到的 chunk 如果不是精确匹配分配要求,就会进行切割并产生一个剩余的空间,如果这个空间不足以成为一个新的 chunk,即比最小的 chunk 的大小要小,那么就直接把整个 chunk 返回给用户,否则切割之后把剩余的空间作为一个新 chunk 并插入到 unsortedbin 中:

              /* Exhaust */
              if (remainder_size < MINSIZE)
                {
                  set_inuse_bit_at_offset (victim, size);
                  if (av != &main_arena)
                    victim->size |= NON_MAIN_ARENA;
                }
              /* 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))
                    {
                      errstr = "malloc(): corrupted unsorted chunks";
                      goto errout;
                    }
                  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);
                }

0x51 从最接近的 smallbin 或 largebin 最佳匹配

如果 nb 属于 smallbins,或者不属于 largebins 但经过以上步骤仍然没有找到合适的 chunk,此时就只能从 smallbin 或者 largebin 里面进行最佳匹配了。这里的最佳匹配的意思是说,从 nb 所对应的那一个 smallbin 或者 largebin 开始往上搜索离它最近并且非空的一个 smallbin 或者 largebin,从中取出最小的一个 空闲 chunk 返回,这个空闲 chunk 必定是不会精确匹配并且会产生剩余空间的,但它已经是能找到的离 nb 最接近的一个空闲 chunk 了。

这里 ptmalloc 设计了一个很巧妙的技巧,用来加快寻找的速度。在分配区里有一个数据成员,是一个位图数组,BINMAPSIZE = 4:

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

由注释也可以知道,这是 bins 的位图,标记每一个 bin 是否为空。我们知道,bins 数组长度为 128,而标记每一个 bin 是否非空只需要用 1 bit 即可,因此对于所有 bins,我们用四个 unsigned int,一共 32 个字节 128 位 即可标记所有 bins 的非空状态了。这里每一个 unsigned int 即是一个位图,标记 32 个 bins。

好,接着往下看。这里 idx 对应着可以精确匹配的那个 smallbin 或者 largebin,加一之后即是离精确匹配最接近的一个 bin,这是因为要进行的是最佳匹配,自然是越接近越好。但是要注意到,这一个 bin 不一定非空,我们的目标是最接近并且非空的 bin。因此通过 idx2block 获取到位图数组的下标,然后获取到对应的 map 位图,最后的 bit 是一个 unsigned int 变量,它在被 idx2bit 赋值后只有 1 bit 被置位,代表当前 idx 所对应的那一个 bin。:

      ++idx;
      bin = bin_at (av, idx);
      block = idx2block (idx);
      map = av->binmap[block];
      bit = idx2bit (idx);

首先检查当前 bin 是否为空,如果 bit 比当前位图要大,说明当前位图所指示的 32 个 bins 中没有非空的 bin 可以找到满足分配要求的空闲 chunk。于是通过一个 while 循环判断下一个位图,如果四个位图都检查完了也没有找到,说明当前所有 bin 都没有空闲 chunk 可以满足分配,只能 goto use_top 交由后面的步骤进行处理。

        for (;; )
        {
          /* Skip rest of block if there are no more set bits in this block.  */
          if (bit > map || bit == 0)
            {
              do
                {
                  if (++block >= BINMAPSIZE) /* out of bins */
                    goto use_top;
                }
              while ((map = av->binmap[block]) == 0);

              ...
            }

            ...
        }

如果找到了一个满足要求位图,这个位图指示着 32 个 bins,这 32 个 bins 中一定存在着某一个 bin 有我们需要的空闲 chunk。这时候我们通过下面的 while 循环来寻找这一个 bin。因为 bit 指示当前判断的那一个 bin,如果 bit 和 map 做与运算不为 0,则说明 bit 指示的这个 bin 就是我们需要寻找的那一个 bin。而且因为这是最佳匹配,在这个 bin 中的最小一个空闲 chunk 必定能够满足我们的分配要求。

        for (;; )
        {
          /* Skip rest of block if there are no more set bits in this block.  */
          if (bit > map || bit == 0)
            {
              ...

              bin = bin_at (av, (block << BINMAPSHIFT));
              bit = 1;
            }

          /* Advance to bin with set bit. There must be one. */
          while ((bit & map) == 0)
            {
              bin = next_bin (bin);
              bit <<= 1;
              assert (bit != 0);
            }

接下来就是把 bin 中的最小的空闲 chunk 返回即可,当然还要注意到一个切割的问题。这一段代码和上面精确匹配的那一段代码基本是相同的,就不分析了。

        for (;; )
        {
          ...

          /* Inspect the bin. It is likely to be non-empty */
          victim = last (bin);

          /*  If a false alarm (empty bin), clear the bit. */
          if (victim == bin)
            {
              av->binmap[block] = map &= ~bit; /* Write through */
              bin = next_bin (bin);
              bit <<= 1;
            }

          else
            {
              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);

              /* Exhaust */
              if (remainder_size < MINSIZE)
                {
                  set_inuse_bit_at_offset (victim, size);
                  if (av != &main_arena)
                    victim->size |= NON_MAIN_ARENA;
                }

              /* 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))
                    {
                      errstr = "malloc(): corrupted unsorted chunks 2";
                      goto errout;
                    }
                  remainder->bk = bck;
                  remainder->fd = fwd;
                  bck->fd = remainder;
                  fwd->bk = remainder;

                  /* advertise as last remainder */
                  if (in_smallbin_range (nb))
                    av->last_remainder = 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;
            }
        }

不过这里有一点要注意的是,如果切割之后剩余的空间在 smallbins 范围之内,那么除了把这个剩余 chunk 插入到 unsortedbin 中之外,它还同时成为当前分配区的 last_remainder。这也是仅有的更改 last_remainder 的两处地方之一,一处是在整理 unsortedbin 切割 last_remainder 时剩余的空间将继承成为新的 last_remainder,另一处即是这里。

0x6 切割 top chunk

如果经过以上步骤仍然没有找到合适的空闲 chunk 可以满足分配要求,此时 ptmalloc 将从 top chunk 中切割空间来进行分配。

首先获取到 top chunk 的大小,然后和申请的空间大小做比较,如果 top chunk 在满足分配后还能够形成一个 chunk,即剩余空间大小大于最小的 chunk 的大小,则开始进行切割,并把剩余的 chunk 作为新的 top 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.)
       */

      victim = av->top;
      size = chunksize (victim);

      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
        {
          remainder_size = size - nb;
          remainder = chunk_at_offset (victim, nb);
          av->top = remainder;
          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;
        }

0x7 fastbins 非空则重新整理 fastbins 以及 unsortedbin

如果 top chunk 也没有办法,那么这时候就重新检查 fastbins 是否为空。因为如果 nb 属于 smallbins 并且在第一次扫描 smallbins 时没有找到合适的空闲 chunk 的话,是不会调用 malloc_consolidate 来清空 fastbins 的。因此这里调用 malloc_consolidate 整理 fastbins 后重新更新 idx,这个 idx 指示着能够精确匹配的那一个 smallbin,然后转到最外层的大循环,即整理 unsortedbin 处的大循环,尝试重新分配。

      /* When we are using atomic ops to free fast chunks we can get
         here for all block sizes.  */
      else if (have_fastchunks (av))
        {
          malloc_consolidate (av);
          /* restore original bin index */
          if (in_smallbin_range (nb))
            idx = smallbin_index (nb);
          else
            idx = largebin_index (nb);
        }

0x8 调用 sysmalloc

如果 fastbins 也没有空闲 chunk,那说明当前 ptmalloc 缓存的空闲 chunks 都不能够满足分配要求,只能调用 sysmalloc 直接向系统申请内存了。

/*
         Otherwise, relay to handle system-dependent cases
       */
      else
        {
          void *p = sysmalloc (nb, av);
          if (p != NULL)
            alloc_perturb (p, bytes);
          return p;
        }

总结

上面的分析只是简要地对 malloc 函数进行了一次初步的分析,然而上面的分析处理只是它在分配小空间时的一个基本流程。事实上完整的 malloc 处理流程要更复杂,比如说当申请空间大于 128KB 时 malloc 会进入 sysmalloc 里面调用 mmap 来进行分配,这里的 128KB 我们把它叫做 mmap 分配阈值,它是会动态变化的,128KB 是它的初始值,而 sysmalloc 和 mmap 我们没有做分析。又比如一些细微的操作,比如说链表的调整,chunk 头部尾部的设置等都没有做分析,而是留给了读者自己学习。还有对于分配区的收缩等也没有做分析,因为我也没有完全理解。但无论如何,这是一篇对于 malloc 分配小空间时对 bins 的一个选择处理的过程分析的文章,这也是我所认为的在框架层面的总体的印象,理解了这一层选择处理的流程,在接下来的深入分析时就会游刃有余,希望对读者有所帮助。因为我本人能力有限,难免会有错误的地方,希望读者多多谅解,一起讨论共同进步。

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在编译glibc时,出现"[install-symbolic-link] Segmentation fault (core dumped)"的错误通常是由于系统缺少必要的依赖或者编译过程中出现了错误。为了解决这个问题,你可以尝试以下几个步骤: 1. 检查GLIBC版本:运行命令"strings /usr/lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC"来查看系统中已安装的GLIBC版本。确保你使用的GLIBC版本与编译glibc时所需的版本匹配。 2. 安装编译glibc所需的依赖:根据你的操作系统版本和发行版,安装编译glibc所需的依赖库。例如,在CentOS 7.6下,你可以使用以下命令安装一些必要的依赖: ``` sudo yum install gcc gcc-c++ make ``` 3. 检查编译选项:确保你在编译glibc时使用了正确的选项。检查编译命令中的参数,如"--enable-checking=release"和"--enable-languages=c,c",确保它们符合你的需求。 4. 检查编译环境:确保你在编译glibc之前设置了正确的编译环境。这包括设置正确的路径、库和头文件等。 5. 检查系统限制:有时,系统可能会限制编译过程的某些资源,比如内存或文件描述符。你可以在系统日志中查看是否有与此错误相关的任何提示。 如果上述步骤都没有解决问题,你可以尝试搜索相关的错误信息或咨询社区或论坛以获取更多帮助。请注意,编译glibc是一个复杂的过程,可能需要深入的系统知识和经验。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [linux查看glibc版本](https://blog.csdn.net/ternence_hsu/article/details/107593800)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值