堆笔记-2.23源码分析

badmonkey 2021年09月10日 201次浏览

Glibc-2.23源码分析

仅作为回顾和复习使用

前菜

内存分布示意图

mem_laylout

系统调用

sbrkmmap,其中sbrk通过修改brk的指向扩张堆,而mmap则是通过申请位于黄色部分的匿名映射段。

线程相关

主线程申请的一大块内存为main_arena,子线程申请的一大块内存为arena,即使线程释放堆块后,arena也不会立即释放,而是由ptmalloc2进行管理。

大佬博客

数据结构

malloc申请的chunk使用struct malloc_chunk定义在malloc.c

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

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

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

这里的INTERNAL_SIZE_T和系统的位数有关,32位为4字节,64位为8字节。这里的size代表的是整个chunk(包括prev_size和size字段)的大小。例如:x86-64位下,chunk的数据部分大小为0x70,头大小为0x10,对应的size为0x80

prev_size保存的是物理相邻的前一个chunk的size,比如chunk2的pre_size保存的是chunk1的size(前提是chunk1是free chunk),prev_size可以被复用。

heap_grow

size字段的最低3bit含义

AMP

fd指针

待完善

用户使用malloc返回的指针指向mem,而debug时指向的是chunk

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk, if unallocated (P clear)  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of chunk, in bytes                     |A|M|P|
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             User data starts here...                          .
        .                                                               .
        .             (malloc_usable_size() bytes)                      .
next    .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             (size of chunk, but used for application data)    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|1|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

bins以及mutex等定义在malloc_state中,同时定义了一个静态变量main_arena

struct malloc_state
{
  /* Serialize access.  */
  mutex_t mutex;

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

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

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

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

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

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

  /* Linked list */
  struct malloc_state *next;

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

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

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

因此常常通过泄露栈上有关main_arena的地址(如small bin的地址),进而泄露libc的基址

常用宏定义

获取chunk的大小

/* Get size, ignoring use bits */
#define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))

/* Like chunksize, but do not mask SIZE_BITS.  */
#define chunksize_nomask(p) ((p)->mchunk_size)

前一个chunk

/* Size of the chunk below P.  Only valid if !prev_inuse (P).  */
#define prev_size(p) ((p)->mchunk_prev_size)

/* Set the size of the chunk below P.  Only valid if !prev_inuse (P).  */
#define set_prev_size(p, sz) ((p)->mchunk_prev_size = (sz))

/* Ptr to previous physical malloc_chunk.  Only valid if !prev_inuse (P).  */
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))

下一个chunk

/* Ptr to next physical malloc_chunk. */
#define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))

bin相关

malloc_state结构体中的BIN数组每一项包含两个机器字长,分别保存第一个chunk的fdbk,可以看到bin_at宏获取到bin数组的下标后,减去了fd在malloc_chunk中的偏移(2个机器字长)。

typedef struct malloc_chunk *mbinptr;

/* addressing -- note that bin_at(0) does not exist */
#define bin_at(m, i)                                                           \
    (mbinptr)(((char *) &((m)->bins[ ((i) -1) * 2 ])) -                        \
              offsetof(struct malloc_chunk, fd))

/* analog of ++bin */
//获取下一个bin的地址
#define next_bin(b) ((mbinptr)((char *) (b) + (sizeof(mchunkptr) << 1)))

/* Reminders about list directionality within bins */
// 这两个宏可以用来遍历bin
// 获取 bin 的位于链表头的 chunk
#define first(b) ((b)->fd)
// 获取 bin 的位于链表尾的 chunk
#define last(b) ((b)->bk)

BIN

fastbin

单链表,LIFO,相邻的fastbin大小差距为两个机器字长(32位8字节,64位0x10字节),共有10个fastbin

部分宏,这里的sz是chunk_size(包括chunk head)而不是数据空间的size

#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[ idx ])

/* offset 2 to use otherwise unindexable first 2 bins */
// chunk size=2*size_sz*(2+idx)
// 这里要减2,否则的话,前两个bin没有办法索引到。
#define fastbin_index(sz)                                                      \
    ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

samll bin

双向循环链表,共62个small bin,采用FIFO原则

small bins 中每个 chunk 的大小与其所在的 bin 的 index 的关系为:chunk_size = 2 * SIZE_SZ *index

large bin

共63个large bin

待完善

unsorted bin

一个unsorted bin ,FIFO原则

待完善

#define bin_index(sz)                                                          \
    ((in_smallbin_range(sz)) ? smallbin_index(sz) : largebin_index(sz))

ptmalloc2机制

堆管理中free的检查比较多,因此了解free和malloc的机制,是绕过检查机制的第一步。

malloc

首先尝试分配fastbin,按照LIFO的策略,先确定fastbin数组的idx,然后从head遍历此fastbin,同时还会检查分配出去的chunk的大小是否属于当前的fastbin,因此fastbin attack时,需要寻找或者构造一个合法的chunk,同时分配出去的chunk,数据空间部分会被清零,因此原有的fd,bk字段会被清空。

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

然后尝试分配smallbin,此处不会分割small bin的chunk,根据请求的大小,计算得到small bin的下标,然后根据FIFO策略,从双向链表的尾部遍历small bin匹配后,会检查前一个chunk的bk指针是否指向待分配的chunk

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

如果申请的是large bin的size,首先会合并fastbin(malloc_consolidate),并计算对应largebin的下标。

然后开始一层循环,在此循环中会尝试在unsorted bin中分配chunk(只有一个reminder chunk在unsorted bin上),如果reminder chunk的size大于申请的size+MINSIZE(分配完申请的chunk后还能分配一个最小的chunk),会进行分割并更新reminder chunk。

// 仅有一个unsorted chunk 而且是满足大小的 reminder 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;
}

如果当前unsorted bin的chunk大小恰好等于申请的size,则将这个unsorted chunk分配出去。

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

如果当前的unsorted chunk的size 小于申请的size,那么根据此unsorted chunk的size,放置到对应的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;

这里关于fd_nextsizebk_nextsize的维护比较复杂,large bin中的按照从大到小排序,链首是最大的chunk,链尾是最小的chunk,其中通过fd_nextsize找到小chunk,通过bk_nextsize找到大chunk。流程示意图

largebin_flow

处理完unsorted_bin中的chunk,后进入large bin的分配

if (!in_smallbin_range (nb))
{
    bin = bin_at (av, idx);

    /* skip scan if empty or largest chunk is too small */
    // large bin最大的chunk要大于申请的size,不然直接skip
    if ((victim = first (bin)) != bin &&
        (unsigned long) (victim->size) >= (unsigned long) (nb))
    {
        //最小的chunk
        victim = victim->bk_nextsize;
        // 找一个比申请的size大的large chunk
        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.  */
        // 如果同样size的large bin有好几个那么先取第二个,因为第一个包含了fd_nextsize和bk_nextsize的信息,取第二个的话,方便维护
        if (victim != last (bin) && victim->size == victim->fd->size)
            victim = victim->fd;

        remainder_size = size - nb;
        // 将large bin从bin链中摘下来。
        unlink (av, victim, bck, fwd);
        // 如果剩下的空间比MINSIZE大,那么将其放到unsorted_bin中,直接将其分配给用户
        /* 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;
    }

主要的逻辑是遍历当前的bin链,然后寻找到大小合适的chunk后将其unlink,同时尝试将剩余的部分作为reminder chunk加入unsorted bin。如果在对应idx的largebin中找不到合适的chunk,那么会通过binmap遍历剩下的largebin,寻找可分配的chunk。分析一下binmap的结构。

binmap

定义了一些宏

#define BINMAPSHIFT      5
#define BITSPERMAP       (1U << BINMAPSHIFT)
#define BINMAPSIZE       (NBINS / BITSPERMAP)

#define idx2block(i)     ((i) >> BINMAPSHIFT)
#define idx2bit(i)       ((1U << ((i) & ((1U << BINMAPSHIFT) - 1))))

#define mark_bin(m, i)    ((m)->binmap[idx2block (i)] |= idx2bit (i))
#define unmark_bin(m, i)  ((m)->binmap[idx2block (i)] &= ~(idx2bit (i)))
#define get_binmap(m, i)  ((m)->binmap[idx2block (i)] & idx2bit (i))

将128个bin,通过位映射来管理,一共有4个block,每个block 32bits,通过idx2block确定指定下标的block序号,mark_bin和unmark_bin用于标记bin链是否为空。

++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.  */
    // 当前的block中没有更大的bin链了
    if (bit > map || bit == 0)
    {
        // 访问下一个block
        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链
        bin = next_bin (bin);
        bit <<= 1;
        assert (bit != 0);
    }

    /* Inspect the bin. It is likely to be non-empty */

    victim = last (bin);
    //写回传,发现当前的bin链实际上是空的,那么需要更新binmap的信息
    /*  If a false alarm (empty bin), clear the bit. */
    if (victim == bin)
    {
        av->binmap[block] = map &= ~bit; /* Write through */
        // 同时进入下一个largebin
        bin = next_bin (bin);
        bit <<= 1;
    }

    else
    {
        size = chunksize (victim);
        // 下一个bin链的最小的chunk也要比申请的chunk要大
        /*  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);
        // 对剩下的部分空间,进行分配,够大则作为last_reminder,不够大则分配给用户
        /* 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;
    }
}

如果所有的bin链都无法满足需求,那么会从top_chunk进行分配。通常前几次malloc时都是从top_chunk中进行切割,因为此时bin链都是空的。

victim = av->top;
size = chunksize (victim);
// 切割剩下的部分,作为新的top_chunk,不过top_chunk的size需要大于MINSIZE
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.  */
// 如果不够大的话,就先合并fastbin,或者通过sysmalloc分配chunk
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;
}
}

malloc_consolidate

在malloc的时候如果申请的size为large chunk,那么会合并fastbin(或者top_chunk不够大的时候也会合并fastbin),主要有两个循环,大循环遍历所有的fastbin,小循环遍历bin链中的chunk,每个chunk都会进行前向和后向合并,如果后向的chunk是top_chunk会将合并后的chunk加入top_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;

  /*
    If max_fast is 0, we know that av hasn't
    yet been initialized, in which case do so below
  */

  if (get_max_fast () != 0) {
    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.
    */

    maxfb = &fastbin (av, NFASTBINS - 1);
    fb = &fastbin (av, 0);
    do {
      p = atomic_exchange_acq (fb, 0);
      if (p != 0) {
	do {
	  check_inuse_chunk(av, p);
	  nextp = p->fd;

	  /* Slightly streamlined version of consolidation code in free() */
	  size = p->size & ~(PREV_INUSE|NON_MAIN_ARENA);
	  nextchunk = chunk_at_offset(p, size);
	  nextsize = chunksize(nextchunk);
      // 将物理相邻的前一个free chunk合并后unlink
	  if (!prev_inuse(p)) {
	    prevsize = p->prev_size;
	    size += prevsize;
	    p = chunk_at_offset(p, -((long) prevsize));
	    unlink(av, p, bck, fwd);
	  }

	  if (nextchunk != av->top) {
	      // 将物理相邻的后一个free chunk合并后 unlink
	    nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

	    if (!nextinuse) {
	      size += nextsize;
	      unlink(av, nextchunk, bck, fwd);
	    } else
	      clear_inuse_bit_at_offset(nextchunk, 0);// 前一个chunk已经被unlink了,所以当前chunk的inuse位要设为0
        // 插入unsorted_bin
	    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);
	    // 插入unsorted_bin
	    p->bk = unsorted_bin;
	    p->fd = first_unsorted;
	    set_foot(p, size);
	  }
	  else {
	      // 如果和top_chunk相邻,直接将chunk合并到top_chunk中。
	    size += nextsize;
	    set_head(p, size | PREV_INUSE);
	    av->top = p;
	  }

	} while ( (p = nextp) != 0);

      }
    } while (fb++ != maxfb);
  }
  else {
    malloc_init_state(av);
    check_malloc_state(av);
  }
}

需要将free chunk从bin链中摘下来的时候,需要进行unlink,一般都是从small bin或者large bin中摘下chunk。unlink并不是一个函数,而是定义的一个宏。

/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {  
	// 检查chunk的fd和bk是否违法
    FD = P->fd;								      
    BK = P->bk;								      
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  
    else {								      
        FD->bk = BK;							      
        BK->fd = FD;							      
        if (!in_smallbin_range (P->size)				      
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {		      
	    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
	      malloc_printerr (check_action,				      
			       "corrupted double-linked list (not small)",    
			       P, AV);					      
            if (FD->fd_nextsize == NULL) {	//这个size具有多个chunk			      
                if (P->fd_nextsize == P)	//只有一个size的large bin			      
                  FD->fd_nextsize = FD->bk_nextsize = FD;	//当前的large bin只有一个size的chunk,但是这个size的chunk有很多个,如果是把这个size的头部给unlink掉,那么第二个chunk就需要作为头部,因此需要更新其fd_next和bk_next的信息	     
                else {
                    //如果具有多个size的large chunk,而且去掉的是其中一个size的头chunk,那么需要更新该size的第二个chunk的fd_next和bk_next信息,同时还要更新该size的fd_nextsize chunk的bk_next 以及size的bk_nextsize的fd_nextsize
                    FD->fd_nextsize = P->fd_nextsize;			      
                    FD->bk_nextsize = P->bk_nextsize;			      
                    P->fd_nextsize->bk_nextsize = FD;			      
                    P->bk_nextsize->fd_nextsize = FD;			      
                  }							     
              } else {			
                // 如果此size只有一个chunk,那么需要更新其前后chunk的信息
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;		      
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;		      
              }								      
          }								      
      }									      
}

小结

如果申请fastbin size,先遍历指定的fastbin并尝试分配,同时会检查bin链的合法性(分配出去的chunk size是否属于这个fastbin)。

如果申请smallbin size,遍历指定的small bin并分配

如果申请largebin size,首先合并fastbin,然后遍历unsorted bin,采用first fit原则,对于合适的unsorted chunk直接切割并分配,不合适的chunk,会被放到对应的bin链中

如果unsorted bin中仍然无法分配到chunk,那么会遍历对应的largebin,并寻找合适的chunk。

如果对应的largebin找不到合适的chunk,那么会遍历剩下所有的largebin,寻找合适的chunk

如果遍历完binmap也没有找到合适的chunk,那么会从top chunk中进行分配。

如果top chunk不够大,那么会调用sysmalloc进行分配。

free

free的时候会根据chunk的size进行free,首先free fastbin的chunk,首先会检查free掉的chunk物理相邻的下一个chunk的size时候合法(比MINSIZE还小,比system_mem还大)。然后将数据空间清零。并尝试将此chunk放置到指定的fastbin中,不过会检查fastbin的第一个chunk是否和待插入的chunk相等,防止double free,同时还会检查待free chunk的size和待插入的fastbin的size时候相等。

if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
    || __builtin_expect (chunksize (chunk_at_offset (p, size))
                         >= av->system_mem, 0))
{
    /* We might not have a lock at this point and concurrent modifications
	   of system_mem might have let to a false positive.  Redo the test
	   after getting the lock.  */
    if (have_lock
        || ({ assert (locked == 0);
             mutex_lock(&av->mutex);
             locked = 1;
             chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
                 || chunksize (chunk_at_offset (p, size)) >= av->system_mem;
            }))
    {
        errstr = "free(): invalid next size (fast)";
        goto errout;
    }
    if (! have_lock)
    {
        (void)mutex_unlock(&av->mutex);
        locked = 0;
    }
}

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

set_fastchunks(av);
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);

/* Atomically link P to its fastbin: P->FD = *FB; *FB = P;  */
mchunkptr old = *fb, old2;
unsigned int old_idx = ~0u;
do
{
    /* Check that the top of the bin is not the record we are going to add
	   (i.e., double free).  */
    if (__builtin_expect (old == p, 0))
    {
        errstr = "double free or corruption (fasttop)";
        goto errout;
    }
    /* Check that size of fastbin chunk at the top is the same as
	   size of the chunk that we are adding.  We can dereference OLD
	   only if we have the lock, otherwise it might have already been
	   deallocated.  See use of OLD_IDX below for the actual check.  */
    if (have_lock && old != NULL)
        old_idx = fastbin_index(chunksize(old));
    p->fd = old2 = old;
}
while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);

if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0))
{
    errstr = "invalid fastbin entry (free)";
    goto errout;
}
}

如果不是fastbin,那么会检查chunk是否属于top chunk,或者chunk 越过了top chunk,或者此chunk 已经是free过的chunk

/* Lightweight tests: check whether the block is already the
       top block.  */
if (__glibc_unlikely (p == av->top))
{
    // 属于top chunk
    errstr = "double free or corruption (top)";
    goto errout;
}
/* Or whether the next chunk is beyond the boundaries of the arena.  */
if (__builtin_expect (contiguous (av)
                      && (char *) nextchunk
                      >= ((char *) av->top + chunksize(av->top)), 0))
{
    // chunk的尾部已经超出了top chunk
    errstr = "double free or corruption (out)";
    goto errout;
}
/* Or whether the block is actually not marked used.  */
if (__glibc_unlikely (!prev_inuse(nextchunk)))
{
    // 已经被free过的chunk 不能再次free
    errstr = "double free or corruption (!prev)";
    goto errout;
}
nextsize = chunksize(nextchunk);
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
    || __builtin_expect (nextsize >= av->system_mem, 0))
{
    errstr = "free(): invalid next size (normal)";
    goto errout;
}

对于非fastbin size的chunk,同时还会检查nextchunk的size是否合法,然后会尝试前向和并和后向合并,在和高地址的chunk进行合并的时候要判断是否和top chunk相邻,如果相邻那么会合并到top chunk中,反之将合并后的chunk放置到unsorted bin中

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

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

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

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

    /*
	Place the chunk in unsorted chunk list. Chunks are
	not placed into regular bins until after they have
	been given one chance to be used in malloc.
      */

    bck = unsorted_chunks(av);
    fwd = bck->fd;
    if (__glibc_unlikely (fwd->bk != bck))
    {
        errstr = "free(): corrupted unsorted chunks";
        goto errout;
    }
    p->fd = fwd;
    p->bk = bck;
    if (!in_smallbin_range(size))
    {
        p->fd_nextsize = NULL;
        p->bk_nextsize = NULL;
    }
    bck->fd = p;
    fwd->bk = p;

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

    check_free_chunk(av, p);
}

/*
      If the chunk borders the current high end of memory,
      consolidate into top
    */

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

如果free的chunk size大于FASTBIN_CONSOLIDATION_THRESHOLD,那么会进行malloc_consolidate

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

    if (av == &main_arena) {
        #ifndef MORECORE_CANNOT_TRIM
        if ((unsigned long)(chunksize(av->top)) >=
            (unsigned long)(mp_.trim_threshold))
            systrim(mp_.top_pad, av);
        #endif
    } else {
        /* Always try heap_trim(), even if the top chunk is not
	   large, because the corresponding heap might go away.  */
        heap_info *heap = heap_for_ptr(top(av));

        assert(heap->ar_ptr == av);
        heap_trim(heap, mp_.top_pad);
    }
}

if (! have_lock) {
    assert (locked);
    (void)mutex_unlock(&av->mutex);
}
}
/*
    If the chunk was allocated via mmap, release via munmap().
  */

else {
    munmap_chunk (p);
}

小结

整体的流程比较简单,如果是fastbin chunk,检查fastbin的第一个chunk,防止double free,以及nextchunk的size是否过大或者过小。如果不是fastbin chunk,那么会进行前后chunk的合并,并判断和top chunk的关系。然后加入到unsorted bin中,如果free的chunk size过大还会合并fastbin。