glibc的回收策略源码解读

内容分享7小时前发布
0 0 0

glibc回收策略源码解读

源码部分名词解释外层无限循环与处理 unsorted bin(代码最前面的 for (;;) 和 while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)))last_remainder 小请求的特殊优化合理的创建标题,有助于目录的生成从 unsorted 中移除(/* remove from unsorted list */)精确匹配(size == nb)与 tcache 处理把 unsorted 中的块放回对应的 bin(“place chunk in bin”)MAX_ITERS & return_cached 限制如果是 large 请求:跳过 unsorted,直接在 large bin 的 skip-list 中寻找(if (!in_smallbin_range(nb)) 这一大段)遍历 binmap(从 idx 开始往上扫 bin) —— 这是 best-fit 的实现核心use_top:从 av->top(堆顶)切分have_fastchunks 和 malloc_consolidate最后手段 sysmalloc:向系统要更多内存关键不变量与安全检查(为什么有那么多 malloc_printerr)关于 large bins 的 skip-list / ordered 插入(更细的说明)关键设计思想总结(要点)常见误解与注意点

源码部分


  for (;; )
    {
      int iters = 0;
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          bck = victim->bk;
          size = chunksize (victim);
          mchunkptr next = chunk_at_offset (victim, size);

          if (__glibc_unlikely (size <= 2 * SIZE_SZ)
              || __glibc_unlikely (size > av->system_mem))
            malloc_printerr ("malloc(): invalid size (unsorted)");
          if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
              || __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
            malloc_printerr ("malloc(): invalid next size (unsorted)");
          if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
            malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
          if (__glibc_unlikely (bck->fd != victim)
              || __glibc_unlikely (victim->fd != unsorted_chunks (av)))
            malloc_printerr ("malloc(): unsorted double linked list corrupted");
          if (__glibc_unlikely (prev_inuse (next)))
            malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

          /*
             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 */
          if (__glibc_unlikely (bck->fd != victim))
            malloc_printerr ("malloc(): corrupted unsorted chunks 3");
          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)
		set_non_main_arena (victim);
#if USE_TCACHE
	      /* Fill cache first, return to user only if cache fills.
		 We may return one of these chunks later.  */
	      if (tcache_nb
		  && tcache->counts[tc_idx] < mp_.tcache_count)
		{
		  tcache_put (victim, tc_idx);
		  return_cached = 1;
		  continue;
		}
	      else
		{
#endif
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
#if USE_TCACHE
		}
#endif
            }

          /* 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 (chunk_main_arena (bck->bk));
                  if ((unsigned long) (size)
		      < (unsigned long) chunksize_nomask (bck->bk))
                    {
                      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 (chunk_main_arena (fwd));
                      while ((unsigned long) size < chunksize_nomask (fwd))
                        {
                          fwd = fwd->fd_nextsize;
			  assert (chunk_main_arena (fwd));
                        }

                      if ((unsigned long) size
			  == (unsigned long) chunksize_nomask (fwd))
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else
                        {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
                            malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;
                      if (bck->fd != fwd)
                        malloc_printerr ("malloc(): largebin double linked list corrupted (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;

#if USE_TCACHE
      /* If we've processed as many chunks as we're allowed while
	 filling the cache, return one of the cached ones.  */
      ++tcache_unsorted_count;
      if (return_cached
	  && mp_.tcache_unsorted_limit > 0
	  && tcache_unsorted_count > mp_.tcache_unsorted_limit)
	{
	  return tcache_get (tc_idx);
	}
#endif

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

#if USE_TCACHE
      /* If all the small chunks we found ended up cached, return one now.  */
      if (return_cached)
	{
	  return tcache_get (tc_idx);
	}
#endif

      /*
         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) chunksize_nomask (victim)
	        >= (unsigned long) (nb))
            {
              victim = victim->bk_nextsize;
              while (((unsigned long) (size = chunksize (victim)) <
                      (unsigned long) (nb)))
                victim = victim->bk_nextsize;

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

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

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

      /*
         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_chunk (av, victim);

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

              /* Split */
              else
                {
                  remainder = chunk_at_offset (victim, nb);

                  /* We cannot assume the unsorted list is empty and therefore
                     have to perform a complete insert here.  */
                  bck = unsorted_chunks (av);
                  fwd = bck->fd;
		  if (__glibc_unlikely (fwd->bk != bck))
		    malloc_printerr ("malloc(): corrupted unsorted chunks 2");
                  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 (__glibc_unlikely (size > av->system_mem))
        malloc_printerr ("malloc(): corrupted top size");

      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 (atomic_load_relaxed (&av->have_fastchunks))
        {
          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;
        }
    }

名词解释

av:arena 指针(内存分配器状态)。main_arena 是主 arena,线程/并发下可能有别的 arena。
nb:请求的“正规化”大小(包含对齐、最小头尾等调整后的大小)。
victim / bck / fwd:表示当前考察的 chunk、其后向/前向链表指针。
unsorted_chunks(av):unsorted bin 的链表头 — 最近 free 的块先放这里。
smallbin_range / in_smallbin_range(size):小块 bin 的范围(按离散尺寸分类,通常是快速查找)。
MINSIZE:chunk 能表示的最小尺寸(包含头尾等元信息)。
PREV_INUSE:标志位,表示前一个 chunk 是否被占用。
chunk_at_offset(victim, offset):从 victim 基址偏移得到另一个 chunk(用于分裂或读 next)。
chunk2mem(victim):把 chunk header 转成用户地址(返回给用户)。
set_head / set_foot / set_inuse_bit_at_offset:更新 chunk 的头/尾及 inuse 标志。
fd_nextsize / bk_nextsize:用于 large-bin 的“跳跃/按尺寸排序”链表(skip list 风格,用于按大小搜索)。
binmap:每一段 bins 的位图,用来快速跳过空的 bin block。
av->top:堆的顶端 chunk(可以通过 sbrk/mmap 扩展),是特殊处理的 chunk。

外层无限循环与处理 unsorted bin(代码最前面的 for (;😉 和 while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)))

这段首先遍历 unsorted 列表(unsorted 存放最近被 free 的 chunk)。为什么先看 unsorted?
这些 chunk 最近刚释放,可能局部性更好(尤其对 small 请求)。
unsorted 是未按大小分类的临时池,分配时优先快试以提升命中率和局部性。
在遍历每个 victim 时,代码先做一堆完整性校验(这些 malloc_printerr):
size 合法性(不能小于 2*SIZE_SZ,或超过 av->system_mem)
next chunk 的 chunksize 合法
next->prev_size 与 size 是否匹配
双向链表的 fd/bk 是否一致
prev_inuse(next):unsorted 上的 victim 的 next 应该是 free(即 prev_inuse==0),否则数据结构不一致
这些校验是为了在内存块损坏时尽早报错并终止(安全性)。

last_remainder 小请求的特殊优化


if (in_smallbin_range (nb) &&
    bck == unsorted_chunks (av) &&
    victim == av->last_remainder &&
    size > nb + MINSIZE)
{
    // split and return remainder
}

如果请求是 small-range,并且当前 victim 是 unsorted 且恰好是 av->last_remainder(上次留下的尾部),并且 victim 比请求要大(且能分裂出合法的 remainder),那就直接把 victim 切割(split),把前面的一部分作为分配返回,把剩余的 remainder 放回 unsorted(作为新的 last_remainder)。

目的是 小请求时优先复用上次留下的尾部,以提升局部性(连续小分配很常见)。

分裂细节:

set_head 设置分配块头(标记 PREV_INUSE 等)

set_head(set_foot) 设置 remainder 的头尾

check_malloced_chunk、alloc_perturb 做调试/安全填充,然后 return chunk2mem(victim)

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用
TOC
语法后生成一个完美的目录。

从 unsorted 中移除(/* remove from unsorted list */)


unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

无论是否最后决定把 victim 直接返回或放回某个 bin,代码先把 victim 从 unsorted 链表里摘出(unlink unsorted)。
若 size == nb(精确匹配),直接把它标记为 inuse 并返回(考虑 tcache 情形)。

精确匹配(size == nb)与 tcache 处理

如果精确匹配,做 set_inuse_bit_at_offset,并给出用户指针返回。
但如果启用了 tcache(线程局部缓存),实现有个特殊逻辑:
tcache_put(victim, tc_idx):把这块放入 thread cache,而不直接返回给用户(为了后续更快的分配)。
return_cached = 1 并 continue:继续处理循环(因为我们还要给用户分配内存 — 可能会在处理若干 cached 后用 tcache_get 返回一个 cached)。
这个行为是为了先填满 tcache(减小并发竞争、提高性能),只在 tcache 填满或超 limit 时才真正把一个 cached chunk 返还用户。
注意:tcache 在这里是一个复杂的性能优化 — 不是功能行为的核心,遇到 USE_TCACHE 分支可以暂时把它视作“先尝试放到线程缓存再决定是否返回”。

把 unsorted 中的块放回对应的 bin(“place chunk in bin”)

如果 victim 不能立即用于分配(既不是上面 small last_remainder 情况,也不是精确匹配),就需要把它放到合适的 bin 中:
小 bin:if (in_smallbin_range(size))
直接定位 victim_index = smallbin_index(size); bck = bin_at(av, victim_index); fwd = bck->fd; 并把 victim 插入到该 list(small bins 通常不按大小排序)。
大 bin(large bin):需要把块按大小排序插入,用 fd_nextsize / bk_nextsize 维护一个按大小跳跃链(skip-list 风格)以便快速找到最小能 fit 的块。
如果这个 bin 里已有元素(fwd != bck),就要在合适位置插入:
先做 size |= PREV_INUSE(与 inuse 位做或,便于比较)
如果小于该 bin 中最小尺寸(bck->bk),直接把它插到最前面(并维护 fd_nextsize / bk_nextsize)
否则沿 fd_nextsize 找到第一个尺寸 >= size 的位置,并插入;若恰好相等,按照实现总是插入“第二个位置”以维持某种 LRU/稳定性(代码注释:Always insert in the second position.)。
如果该 large bin 为空(fwd == bck),把 fd_nextsize = bk_nextsize = victim(自环)。
最后 mark_bin(av, victim_index)(设置 binmap 相应位),并把 victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; 完成插入。

MAX_ITERS & return_cached 限制

在处理 unsorted 时,代码会统计 iters,如果超过 MAX_ITERS(10000) 则 break。防止在异常/损坏情况下死循环。
tcache 相关:如果处理过程中有 return_cached,并且超过 mp_.tcache_unsorted_limit,则会 return tcache_get(tc_idx) 以返回一个缓存中的块。

如果是 large 请求:跳过 unsorted,直接在 large bin 的 skip-list 中寻找(if (!in_smallbin_range(nb)) 这一大段)

这段针对大请求(nb not in smallbin_range):
bin = bin_at(av, idx),如果 first(bin) != bin 并且 chunksize_nomask(first(bin)) >= nb,说明该 bin 有足够大的块。
使用 victim = victim->bk_nextsize 往后遍历 bk_nextsize,直到找到第一个 size >= nb 的块(这是 skip-list 的查找)。
特殊处理:为了避免破坏 skip-list 的路由,尽量不要摘除 bin 中的第一个元素(如果 victim != last(bin) 并且 chunksize_nomask(victim) == chunksize_nomask(victim->fd) 则选择 victim = victim->fd)。
找到之后 remainder_size = size – nb; unlink_chunk(av, victim) 把它从 bin 中摘出,然后:
若 remainder_size < MINSIZE:整块分配(exhaust),标记 inuse。
否则分裂 remainder 并把 remainder 插回 unsorted(注意:分裂出来的 remainder 放回 unsorted,不是直接放回 bin)。如果 nb 是 small-range,还会把 remainder 标记为 av->last_remainder(作为未来的可能分配尾部)。
完成后 check_malloced_chunk / chunk2mem 并返回指针。

遍历 binmap(从 idx 开始往上扫 bin) —— 这是 best-fit 的实现核心

当当前 bin 没有合适的块时,代码会做 ++idx,并通过 binmap 快速找到下一个非空的 bin-block:
block = idx2block(idx),map = av->binmap[block],bit = idx2bit(idx)。
如果当前 bit 对应位没有 set(或超出 map),跳到下一个 block,直到找到非零 map。
在找到有 set 的 block 后,继续把 bit 左移直到 bit & map 成立(找到具体的 bin idx)
再 victim = last(bin)(取最尾部元素,best-fit 策略:最后元素通常最小/最合适)
若是“假警报”(bin 实际空),就把 av->binmap[block] &= ~bit 并继续;
否则,和之前一样 size = chunksize(victim);assert(size >= nb);remainder_size = size – nb;unlink_chunk;exhaust or split;若分裂则把 remainder 插入 unsorted 并可能标记 av->last_remainder,最终返回 chunk。
要点:binmap + 按 bin block 扫描,让搜索空 bin 的步骤很快(不需要遍历所有 bin)

use_top:从 av->top(堆顶)切分

当所有 bins 都没有合适块时,进入 use_top(代码 label):
victim = av->top,size = chunksize(victim)。
检查 size 范围(size <= av->system_mem)。
如果 size >= nb + MINSIZE,说明在顶端可以分出一个合法 remainder:
remainder_size = size – nb,remainder = chunk_at_offset(victim, nb),把 av->top = remainder,并把 victim 返回(设置 head/prev_inuse 等)。
说明:av->top 被视作“可以扩张”的 chunk(可以视为最大且可被扩展的候选),因此逻辑上它是最差 fit 的候选,但可以用来满足请求。保证 av->top 总存在(即至少 MINSIZE)是为了申请时能放置 fenceposts 等。

have_fastchunks 和 malloc_consolidate

如果 top 空间不足(size < nb + MINSIZE),但 atomic_load_relaxed(&av->have_fastchunks) 为真,说明:
有 fastbin(快速 free 缓存)中的块需要合并回常规 bins(通常是另一种延迟 free 的优化,或并发情况下由原子操作标记)。
调用 malloc_consolidate(av) 将 fastbins 中缓冲的块合并(归并)到 unsorted/normal bins,然后恢复 idx,继续尝试查找。
这是并发与性能优化的协调:fastbins 增加了释放/分配速度,但在某些时刻必须把它们合并到通用数据结构中才能用于较大/复杂的分配。

最后手段 sysmalloc:向系统要更多内存

若以上都不能满足分配,并且没有可合并的 fastchunks,则调用 sysmalloc(nb, av),它会尝试通过 sbrk/mmap 向内核申请更多内存;若成功,会返回一个新的内存(通常通过 top 切分或直接返回),否则返回 NULL 表示 OOM。

关键不变量与安全检查(为什么有那么多 malloc_printerr)

在整个过程中很多位置做一致性检查:
bck->fd == victim、victim->fd == unsorted_chunks(av):链表双向指针必须互相一致。
prev_size(next) 与 size 一致:保证 chunk 边界正确。
chunksize_nomask(next) 不小于最小值,且不超过 av->system_mem:防止篡改或损坏导致异常行为。
这些检查可在检测到堆损坏时停止并报告。

关于 large bins 的 skip-list / ordered 插入(更细的说明)

large bin 使用 fd_nextsize / bk_nextsize 来维持按 chunksize_nomask 排序的链表(仅用于尺寸比较;fd/bk 保持 LRU 之类的双向链)。
插入时如果发现相同尺寸,代码会 “always insert in the second position” —— 这样做的好处是维持某种稳定性,避免老的 bin 路由(skip pointers)频繁重链接,从而减少维护成本。
当从 large bin 中选择时,使用 bk_nextsize 方向寻找最小能 fit 的块,保证 best-fit 策略(尽量选择最小能够满足的块,从而减少内存碎片)。

关键设计思想总结(要点)

优先 unsorted:利用最近释放块的局部性,做快速复用与 small last_remainder 优化。

small bins vs large bins:small bins 按固定桶处理(快速),large bins 按尺寸排序(用于 best-fit)。

best-fit 策略:用 skip-list (fd_nextsize) + binmap 实现高效搜索,尽量选择最小合适块以降低碎片。

top chunk:作为最后的可切分源,treated as “least-fitting but expandable”。

tcache & fastbins:多层缓存以提高并发/线程局部性能,但需要时刻能 consolidate 回常规结构。

大量校验:为了发现内存损坏或数据结构异常,避免继续在损坏堆上运行。

安全防护:MAX_ITERS、防止无限循环、以及 fallback 到 sysmalloc。

常见误解与注意点

unsorted 不是长期缓存,它只是最近释放的临时池,分配时会尽快把块放回 small/large bin(或返回给用户)。

large bin 的 fd/bk 与 fd_nextsize/bk_nextsize 是两套不同的链表:一套用于 LRU/双向遍历(fd/bk),一套用于按尺寸快速跳跃(*_nextsize)。

last_remainder 只是一个小的优化,不改变全局分配策略,但能大幅提高连续小分配场景的命中率。

tcache 使得有时分配不直接返回 unsorted 上的精确匹配,而是先塞入线程缓存;因此观察到的行为可能有延迟。

© 版权声明

相关文章

暂无评论

none
暂无评论...