【堆学习】

堆(glibc)

堆概述

堆简介

堆是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长,用来提供动态分配的内存(用户申请时启用)。只有虚拟地址被真正使用时才会与物理地址形成映射关系。

堆管理器

管理堆的程序,介于用户与系统内核之间。响应用户请求向系统申请内存,一次会申请很大内存,根据用户需求分配堆,堆空间不够时才会再次与系统交互;用户释放内存后内存先有堆管理器管理,不直接返回系统。

堆的申请与释放

相关函数
malloc和free

malloc(size) : 用户通过malloc函数申请size大小的堆内存,malloc 函数返回对应大小字节的内存块的指针
free(ptr) : 用户通过free函数释放相应指针指向的堆内存

brk()和sbrk()

malloc和free后面真正与系统进行交互的函数时brk()和sbrk(),还有mmap、unmmap等

堆的大小通过brk()和sbrk()函数移动program_break使堆增长。
brk(ptr):参数是一个指针,用于设置program_break指向的位置
sbrk(increment):参数与program_break相加来调整program_break的值
执行成功brk()返回0,sbrk()返回上一次program_break的值

初始时,堆的起始地址 start_brk 以及堆的当前末尾 brk 指向同一地址。

  • 不开启 ASLR 保护时,start_brk指向 data/bss 段的结尾。
  • 开启 ASLR保护时,start_brk 指向data/bss 段结尾后的随机偏移处。

如下图所示:
在这里插入图片描述当用户申请内存过大时,就会使用mmap和unmmap来创建匿名映射段给用户使用和回收

Arena

简介

arena包含一片或数片连续的内存,堆块从中划分。

main_arena
  1. 主线程的arena,包含start_brk和brk之间连续的内存。
  2. 只有堆;堆大小不够用brk()扩展
thread_arena
  1. 其他线程的arena
  2. 可以有数片连续内存;映射段大小固定,不可扩展,不够用就再次调用mmap()分配新内存

相关数据结构

glibc的malloc源码中涉及三种数据结构

  • Arena --> malloc_state
  • Heap --> heap_info
  • Chunk --> malloc_chunk
1、Arena - malloc_state
  1. 每个线程都有一个Arena,但可能多个线程共用一个Arena。
  2. 每个Arena包含一个malloc_state 结构体(arena_header),保存bins, top chunk, Last reminder chunk等信息。
  3. 注意,main_arena的arena_header作为全局变量储存在libc.so的数据段中,其他arena的保存在heap segment中
malloc_state结构体定义
struct malloc_state {
	//该变量用于控制程序串行访问同一个分配区,当一个线程获取了分配区之后,其它线程要想访问该分配区,就必须等待该线程分配完成后才能够使用。
    __libc_lock_define(, mutex);  			/* Serialize access.  */
    //flags 记录了分配区的一些标志,比如 bit0 记录了分配区是否有 fast bin chunk ,bit1 标识分配区是否能返回连续的虚拟地址空间
    int flags;  							/* Flags (formerly in max_fast).  */
    //fastbinsY存放每个 fast chunk 链表头部的指针
    mfastbinptr fastbinsY[ NFASTBINS ]; 	 /* Fastbins */
    //指向分配区的 top chunk
    mchunkptr top; 							 /* Base of the topmost chunk -- not otherwise kept in a bin */
    //最新的 chunk 分割之后剩下的那部分
    mchunkptr last_remainder;   			 /* The remainder from the most recent split of a small request */

    //用于存储 unstored bin,small bins 和 large bins 的 chunk 链表
    mchunkptr bins[ NBINS * 2 - 2 ]; 		 /* Normal bins packed as described above */
    //ptmalloc 用一个 bit 来标识某一个 bin 中是否包含空闲 chunk 
    unsigned int binmap[ BINMAPSIZE ]; 		 /* Bitmap of bins, help to speed up the process of determinating if a given bin is definitely empty.*/
    struct malloc_state *next; 				 /* Linked list, points to the next arena */
    struct malloc_state *next_free; 		 /* Linked list for free arenas.  Access to this field is serialized by free_list_lock in arena.c.  */
    INTERNAL_SIZE_T attached_threads;  		 /* 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 system_mem; 			 /* Memory allocated from the system in this arena.  */
    INTERNAL_SIZE_T max_system_mem;
};
+附加介绍
Last remainder
  1. malloc 请求分配内存时,ptmalloc2 找到的 chunk 可能并不和申请的内存大小一致,这时候就将分割之后的剩余部分称之为 last remainder chunk 。
  2. unsort bin 也会存这一块。
  3. top chunk 分割剩下的部分不会作为 last remainder。
2、Heap - heap_info
  1. 子线程的arena可以有多片连续的内存,这些内存称为heap。
  2. 一个arena开始时只有一个heap,当这个heap空间用尽时,会获取新的heap
  3. 每个heap都有一个heap_info 结构体,通过链表相连,保存了指向其所属arena的指针。
  4. main_arena只有一个heap,所以没有heap_info 结构,当main arena用尽空间后,会扩展当前的heap空间。
heap_info 结构体定义
typedef struct _heap_info
{
  //堆对应的 arena 的地址
  mstate ar_ptr; /* Arena for this heap. */
  //prev 即记录了上一个 heap_info 的地址。每个堆的 heap_info 是通过单向链表进行链接的。
  struct _heap_info *prev; /* Previous heap. */
  //size 表示当前堆的大小
  size_t size;   /* Current size in bytes. */
  size_t mprotect_size; /* Size in bytes that has been mprotected PROT_READ|PROT_WRITE.  */
  //确保对齐
  char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];  /* Make sure the following data is properly aligned, particularly that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of MALLOC_ALIGNMENT. */
} heap_info;
3、Chunk - malloc_chunk
  1. Top chunk

    概念:程序第一次进行 malloc 的时候,heap 会被分为两块,一块给用户,剩下的那块就是 top chunk,是处于当前堆的物理地址最高的 chunk。
    作用:当所有的 bin都无法满足用户请求的大小时,如果其大小不小于指定的大小,就进行分配,并将剩下的部分作为新的 top chunk。否则,就对 heap进行扩展后再进行分配。在 main arena中通过 sbrk 扩展 heap,而在 thread arena 中通过 mmap分配新的 heap。
    ③ 注意:top chunk 的 prev_inuse 比特位始终为 1,否则其前面的 chunk 就会被合并到top chunk 中。
    ④ 初始情况下,我们可以将 unsorted chunk(后面会介绍) 作为 top chunk。

  2. chunk申请与释放过程
    连续申请3个chunkA、B、C,释放chunkB,被放入bin,成为一个free chunk,再次申请一个chunkB大小的chunk时,malloc从bin中取出chunkB,回到一开始的状态。连续释放chunkA、B,他们相邻就会合成一个大chunk放入bin中

malloc_chunk结构体定义
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在64位下是8字节,32位下是4字节

字段解释:

  • prev_size:前一 chunk是空闲的话,记录其的大小;否则作为前一个chunk的data部分
  • size:该 chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。最后3个比特位被用作状态标识,从高到低分别表示:
    • NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1 表示不属于,0 表示属于。
    • IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的。
    • PREV_INUSE,当它为1时,表示上一个chunk是使用状态,否则为释放状态
  • fd,bk:chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时启用,fd、bk分别指向下一个和上一个free chunk
  • fd_nextsize, bk_nextsize:也是只有 chunk 空闲的时候才使用,不过其用于较大的 chunk(large chunk)。
Chunk 具体结构
allocated chunk

处于使用中的chunk分为由prev_size和size组成的chunk header,和后面供用户使用的 user data。每次 malloc 申请返回给用户的内存指针其实指向user data 的起始处

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|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
free chunk

处于释放状态的chunk,fd、bk有效,下一个chunk的P比特位为0并且其prev_size表示这个free chunk的大小。因为bk之后的空间可能为0,所以chunk最小是32字节(64位)或16字节(32位),
包括chunk header和两个指针。

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk, if unallocated (P clear)  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  head: |             Size of chunk, in bytes                     |A|0|P|
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Forward pointer to next chunk in list             |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Back pointer to previous chunk in list            |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Unused space (may be 0 bytes long)                .
        .                                                               .
 next   .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  foot: |             Size of chunk, in bytes                           |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|0|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

对上面相关结构的理解图:
在这里插入图片描述

Bin

bin概述

chunk被释放时glibc会将它们重新组合构成不同的bin链表,用户再次申请时就从中寻找合适的chunk返回给用户。不同大小区间的chunk划分到不同的bin中,一共有四种bin:Fast bin (特殊的bin)、Small bin、Large bin和Unsorted bin。这些bin都记录在malloc_state结构中

  • fastbinsY:一个bin数组,里面有NFASTBINS 个fast bin。
  • bins:一个bin数组,共有126个bin,按顺序分别是:
    • bin 1 — unsorted bin
    • bin 2~63 — small bin
    • bin 64~126 — large bin


fast bin

  1. 程序申请和释放的堆块较小,这类bin用单链表结构,采用LIFO(后进先出)策略
  2. fast bin的chunk不会合并,下一个chunk的PREV_INUSE始终为1
  3. 同一个fast bin的chunk大小相同,在fastbinsY中从小到大排序
  4. 序号0的fast bin容纳chunk大小为4*SIZE_SZ字节,随序号增加,chunk递增2*SIZE_SZ字节

fastbinsY数组示例 - 64位为例
在这里插入图片描述

fastbin相关源码
//定义了fastbinY数组的大小
#define NFASTBINS (fastbin_index(request2size(MAX_FAST_SIZE)) + 1)
//根据序号获取fastbinsY中对应的bin
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[ idx ])
//根据chunk大小获取位置。因为minsize=0x20,前两个index不可索引,要减2
#define fastbin_index(sz) \
    ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
//定义了fastbin的最大chunk值
#define MAX_FAST_SIZE (80 * SIZE_SZ / 4)

fastbinsY数组没有保存头节点,只保存malloc_chunk的fd成员,fd初始值为null,对应bin为空,直到chunk进来才保存chunk地址,如下图所示:
fastbinsY数组
fastbinY数组
FASTBIN_CONSOLIDATION_THRESHOLD

#define FASTBIN_CONSOLIDATION_THRESHOLD (65536UL)

fastbin 范围的 chunk不会和其它被释放的 chunk 合并。但是当释放的 chunk 与该 chunk 相邻的空闲 chunk 合并后的大小大于FASTBIN_CONSOLIDATION_THRESHOLD 时,就会触发malloc_consolidate 函数将fastbin中的chunk与其他free chunk合并,然后移动到unsorted bin中。

global_max_fast

#define set_max_fast(s)                                                        \
    global_max_fast =                                                          \
        (((s) == 0) ? SMALLBIN_WIDTH : ((s + SIZE_SZ) & ~MALLOC_ALIGN_MASK))
#define get_max_fast() global_max_fast

fast bin中最大的chunk由global_max_fast 决定,一般在对初始化时设置,运行时也可以设置。

unsorted bin

  1. 一定大小的chunk被释放时,在进入small bin或者large bin 之前,会先加入unsorted bin
  2. 在实践中,一个被释放的chunk常常很快就会被重新使用,所以将其先加入 unsorted bin 可以加快分配的速度。
  3. unsorted bin使用双链表结构,并采用FIFO(先进先出)的分配策略。与fastbinsY不同,unsorted bin中的chunk大小可能是不同的,并且由于是双链表结构,一个bin会占用bins 的两个元素

bins中unsorted bin示例 - 64位系统为例
在这里插入图片描述

unsorted bin相关源码

获取 unsorted bin

#define unsorted_chunks(M) (bin_at(M, 1))


small bin

  1. 同一个small bin里 chunk的大小相同,采用双链表结构
  2. small bin在bins 里居第2到第63位,共62个。根据排序,每个 small bin的大小为2*SIZE_SZ*idx(idx表示 bins数组的下标)。
  3. 在64位系统下,最小的small chunk为2×8×2=32字节,最大的small chunk为2×8×63=1008字节。
  4. 由于small bin和 fast bin有重合的部分,所以这些chunk在某些情况下会被加入small bin 中。

bins中small bin和large bin示例 - 以64位系统为例
在这里插入图片描述

small bin相关源码
// 根据chunk的大小得到small bin对应的索引。
#define smallbin_index(sz)                                                     \
    ((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4)                          \
                           : (((unsigned) (sz)) >> 3)) +                       \
     SMALLBIN_CORRECTION)


large bin

  1. large bin在 bins里居第64 到第126位,共63个被分成了6组,每组 bin所能容纳的chunk按顺序排成等差数列,公差分别如下。
从bins[64]开始的区间size
32个 bins of size64
16个 bins of size512
8个 bins of size4096
4个 bins of size32768
2个 bins of size262144
1个 bins of sizeleft size
  1. 64位下,第一个large bin的 chunk最小为1024字节,第二个large bin的chunk最小为1024+64个字节(处于[1024,1024+64)之间的chunk都属于第一个 large bin),以此类推,32位也类似。

  2. large bin 也是采用双链表结构,里面的chunk 从头结点的fd指针开始,按大小顺序进行排列。

  3. 为了加快检索速度,fd_nextsize 和bk_nextsize指针用于指向第一个与自己大小不同的chunk,所以也只有在加入了大小不同的 chunk时,这两个指针才会被修改。

large bin相关源码
// 根据chunk的大小得到large bin对应的索引。
#define largebin_index(sz)                                                     \
    (SIZE_SZ == 8 ? largebin_index_64(sz) 
    : MALLOC_ALIGNMENT == 16 ? largebin_index_32_big(sz)    \
    : largebin_index_32(sz))


bins相关源码

//从bins中获取指定序号的bin。注意bin 0不存在
#define bin_at(m, i)                                                           \
    (mbinptr)(((char *) &((m)->bins[ ((i) -1) * 2 ])) -                        \
              offsetof(struct malloc_chunk, fd))

//获得当前bin的上一个bin
#define next_bin(b) ((mbinptr)((char *) (b) + (sizeof(mchunkptr) << 1)))

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

bin_at(m,i)宏定义中减去的offsetof(struct malloc_chunk, fd)就是prev_size和size成员的大小,因为bins数组只保存了双链表头节点的fd和bk指针,如图:
在这里插入图片描述bins[0]和bins[1]是unsorted bin的fd和bk,bin_at(1)返回的是unsorted bin的头指针,这个指针指向的是bins[0]地址减去offsetof(struct malloc_chunk, fd)的位置。

chunk 相关宏

request2size

#define request2size(req)                                                      \
    (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)                           \
         ? MINSIZE                                                             \
         : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

#define MALLOC_ALIGN_MASK   (MALLOC_ALIGNMENT - 1)
#define MALLOC_ALIGNMENT    (2*SIZE_SZ)

这个宏将请求的req转换为chunk头部(prevsize和size)的chunk大小,示例(默认MINSIZE=0x20):

  • req∈[0,0x9),return 0x20
  • req∈[0x9,0x18],return 0x20

对于上面第二条,除了用户申请的req,chunk大小还会加上0x10个字节,总共0x28字节,多出的0x8字节由下一个chunk的prev_size提供

chunk2mem()和mem2chunk

#define chunk2mem(p) ((void *) ((char *) (p) + 2 * SIZE_SZ)) //malloc时
#define mem2chunk(mem) ((mchunkptr)((char *) (mem) -2 * SIZE_SZ))  //free时

chunk指针与user data指针的转换

chunk状态相关

  • 定义PREV_INUSE、 IS_MMAPPED、 NON_MAIN_ARENA,以及对三者镜像或运算
/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
#define PREV_INUSE 0x1
/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
#define IS_MMAPPED 0x2
/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
   from a non-main arena. */
#define NON_MAIN_ARENA 0x4
// Bits to mask off when extracting size
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
  • 通过chunk指针p,对某标志位进行提取、检查、置位和清除
#define prev_inuse(p) ((p)->mchunk_size & PREV_INUSE)
#define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)
#define set_non_main_arena(p) ((p)->mchunk_size |= NON_MAIN_ARENA)

/* extract p's inuse bit */
#define inuse(p)                                                               \
    ((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)
/* set/clear chunk as being inuse without otherwise disturbing */
#define set_inuse(p)                                                           \
    ((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size |= PREV_INUSE
#define clear_inuse(p)                                                         \
    ((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size &= ~(PREV_INUSE)
  • 由于当前chunk的使用状态由下一个chunk的size成员的P比特位决定,通过下面的宏可以获得或修改当前chunk的inuse状态
/* check/set/clear inuse bits in known places */
#define inuse_bit_at_offset(p, s)                                              \
    (((mchunkptr)(((char *) (p)) + (s)))->mchunk_size & PREV_INUSE)

#define set_inuse_bit_at_offset(p, s)                                          \
    (((mchunkptr)(((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)

#define clear_inuse_bit_at_offset(p, s)                                        \
    (((mchunkptr)(((char *) (p)) + (s)))->mchunk_size &= ~(PREV_INUSE))
    1. set_head_size()修改size是不会修改当前chunk的标志位,但set_head()会。
    2. set_foot()修改一个chunk的prev_size时当前chunk一定要是释放状态,否则下一个chunk的prev_size没有意义
// SIZE_BITS = 7
#define set_head_size(p, s)                                                    \
    ((p)->mchunk_size = (((p)->mchunk_size & SIZE_BITS) | (s)))
/* Set size/use field */
#define set_head(p, s) ((p)->mchunk_size = (s))
/* Set size at footer (only when chunk is not in use) */
#define set_foot(p, s)                                                         \
    (((mchunkptr)((char *) (p) + (s)))->mchunk_prev_size = (s))
#define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))
    1. next_chunk()将当前chunk地址加上当前chunk大小获得下一个chunk指针。
    2. prev_chunk()将当前chunk地址减去prev_size值获得上一个chunk指针,前提是上一个chunk属于释放状态。
    3. chunk_at_offset()将当前chunk地址加上s偏移处的位置视为一个chunk。
#define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - ((p)->prev_size)))
#define chunk_at_offset(p, s) ((mchunkptr)(((char *) (p)) + (s)))



malloc_consolidate()函数

fast bin中的chunk无法释放与相邻的free chunk合并,形成大量的内存碎片,此时glibc就会调用这个函数将fast bin中的chunk取出来与相邻free chunk合并后放入unsorted bin或者与top 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;

    //如果max_fast为0,调用malloc_init_state函数初始化av
    if (get_max_fast() != 0) {
        // 清除av->flags有关fast bin的标志位,表示所有fast bin都为空了
        // 因为要合并 fastbin 中的 chunk 了。
        clear_fastchunks(av);
        //
        unsorted_bin = unsorted_chunks(av);

        // 按照 fd 顺序遍历 fastbin 的每一个 bin,将 bin 中的每一个 chunk 合并掉并移除,放入unsorted bin
        maxfb = &fastbin(av, NFASTBINS - 1);
        fb    = &fastbin(av, 0);
        do {  //外层循环遍历fastbinsY的每一个fast bin
            p = atomic_exchange_acq(fb, NULL);  //将p的值换为fb
            if (p != 0) {                     //若p=0(NULL),说明改fastbin为空
                do {  //内层循环遍历fast bin中的每一个chunk
                    check_inuse_chunk(av, p);
                    nextp = p->fd;

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

                    if (!prev_inuse(p)) {  //向后合并
                        prevsize = prev_size(p);
                        size += prevsize;
                        p = chunk_at_offset(p, -((long) prevsize));
                        unlink(av, p, bck, fwd);
                    }
					//如果下一个chunk不是top chunk,向前合并,并加入unsorted bin
                    if (nextchunk != av->top) {
                        // 判断 nextchunk 是否是空闲的。
                        nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

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

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

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

                        set_head(p, size | PREV_INUSE);
                        p->bk = unsorted_bin;
                        p->fd = first_unsorted;
                        set_foot(p, size);
                    }
					//如果下一个chunk是top chunk,和top chunk合并,形成新的top chunk
                    else {
                        size += nextsize;
                        set_head(p, size | PREV_INUSE);
                        av->top = p;
                    }

                } while ((p = nextp) != 0);
            }
        } while (fb++ != maxfb);
    }
    else {
        malloc_init_state(av);  //malloc初始化
        // 在非调试情况下没有什么用,在调试情况下,做一些检测。
        check_malloc_state(av);
    }



malloc 相关源码

__libc_malloc

因为定义了宏定义strong_alias(__libc_malloc,malloc),glibc源码中的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);  //寻找一个合适的 arena 来分配内存。
	victim = _int_malloc(ar_ptr, bytes);  //调用 _int_malloc 函数去申请对应的内存。
	//如果没有找到合适的内存,ptmalloc 会尝试再去寻找一个可用的 arena,并分配内存。
	if (!victim && ar_ptr != NULL) {
        LIBC_PROBE(memory_malloc_retry, 1, bytes);
        ar_ptr = arena_get_retry(ar_ptr, bytes);
        victim = _int_malloc(ar_ptr, bytes);
    }
    //申请到了arena,在退出之前解锁改arena。
	if (ar_ptr != NULL) __libc_lock_unlock(ar_ptr->mutex);
	assert(!victim || chunk_is_mmapped(mem2chunk(victim)) ||
           ar_ptr == arena_for_chunk(mem2chunk(victim)));  //检查
	return victim;
}     


_init_malloc

  1. _init_malloc是内存分配的核心函数,为了以最快的速度找到合适的堆块,glibc更根据申请堆块的大小、各个bin的状态仔细地安排了分配顺序和内存整理的时机
  2. 大概的搜索顺序:
    fast bin -> small bin -> unsorted bin ->large bin -> bins -> top chunk -> 系统函数分配

源码
(1)首先定义一些变量,并将用户请求的byte转化为表示chunk的nb。

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;

    checked_request2size(bytes, nb);

如果没有合适的arena,就调用sysmalloc(),用mmap()分配chunk并返回

	if (__glibc_unlikely(av == NULL)) {
        void *p = sysmalloc(nb, av);
        if (p != NULL) alloc_perturb(p, bytes);
        return p;
    }

(2)其次,检查fast bin中是否有合适的chunk

	//如果nb在fast bin的范围内,就通过fast bin分配,这段代码可以在堆初始化之前运行
    if ((unsigned long) (nb) <= (unsigned long) (get_max_fast())) {
        // 得到对应的fastbin的下标
        idx             = fastbin_index(nb);
        // 得到对应的fastbin的头指针
        mfastbinptr *fb = &fastbin(av, idx);
        mchunkptr    pp = *fb;
        // 利用fd遍历对应的bin内是否有空闲的chunk块,
        do {
            victim = pp;
            if (victim == NULL) break;
        } while ((pp = catomic_compare_and_exchange_val_acq(fb, victim->fd,
                                                            victim)) != victim);
        // 存在可以利用的chunk,检查后返回给用户
        if (victim != 0) {
            // 检查取到的 chunk 大小是否与相应的 fastbin 索引一致。防伪造
            if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0)) {
                errstr = "malloc(): memory corruption (fast)";
            errout:
                malloc_printerr(check_action, errstr, chunk2mem(victim), av);
                return NULL;
            }
            // 细致的检查。。只有在 DEBUG 的时候有用
            check_remalloced_chunk(av, victim, nb);
            // 将获取的到chunk转换为mem模式
            void *p = chunk2mem(victim);
            // 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
            alloc_perturb(p, bytes);
            return p;
        }
    }

(3)检查small bin中是否有合适的chunk

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

(4)之后,整理fast bin,计算large bin的index

	//到这里还不能满足,就调用malloc_consolidate()整理fastbins,或许就会有合适的chunk
    else {
        // 获取large bin的下标。
        idx = largebin_index(nb);
        // 如果存在fastbin的话,会处理 fastbin
        if (have_fastchunks(av)) malloc_consolidate(av);
    }

(5)接下来,函数进入一个大的外层for循环,包含_init_malloc()函数之后的所有过程,紧接着是内层的第一个while,它会遍历unsorted bin里的每一个chunk,如果大小正好合适,就将其取出,否则就放入small bin或large bin。这是这是唯一将chunk放入small bin或large bin的过程

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

(6)在内层第一个循环内部,当请求的chunk属于small bin、unsorted bin 只有一个chunk为 last remainder并且满足拆分条件时,就将其拆分

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

(7)接着,将chunk从unsorted bin中移除,如果大小正好合适,就返回给用户

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

(8)如果chunk大小不合适就将其插入到对应的bin中

            if (in_smallbin_range(size)) {
                victim_index = smallbin_index(size);
                //bck指向头节点,fwd是头节点的fd节点。chunk会被插入头节点和fwd节点之间
                bck          = bin_at(av, victim_index);
                fwd          = bck->fd;
            } else {
                // 否则large bin 范围
                victim_index = largebin_index(size);
                bck          = bin_at(av, victim_index); // 当前 large bin 的头部
                fwd          = bck->fd;

             /* maintain large bins in sorted order */
             //需要对双链表额外操作,从这里我们可以总结出,largebin 以 fd_nextsize 递减排序。
             if (fwd != bck) {  // 如果 large bin 链表不空
                 // 加速比较,应该不仅仅有这个考虑,因为链表里的 chunk 都会设置该位。
                 size |= PREV_INUSE;
                 /* if smaller than smallest, bypass loop below */
                 
                 // 判断 bck->bk 是不是在 main arena。
                 assert(chunk_main_arena(bck->bk));
                 if ((unsigned long) (size) <
                     (unsigned long) chunksize_nomask(bck->bk)) {
                     // bck->bk 存储着相应 large bin 中最小的chunk。
                     // 如果遍历的 chunk 比当前最小的还要小,那就只需要插入到链表尾部。
                     
                     // 令 fwd 指向 large bin 头
                     fwd = bck;
                     // 令 bck 指向 largin bin 尾部 chunk
                     bck = bck->bk;
                     // victim 的 fd_nextsize 指向 largin bin 的第一个 chunk
                     victim->fd_nextsize = fwd->fd;
                     // victim 的 bk_nextsize 指向原来链表的第一个 chunk 指向的 bk_nextsize
                     victim->bk_nextsize = fwd->fd->bk_nextsize;
                     // 原来链表的第一个 chunk 的 bk_nextsize 指向 victim
                     // 原来指向链表第一个 chunk 的 fd_nextsize 指向 victim
                     fwd->fd->bk_nextsize =
                     victim->bk_nextsize->fd_nextsize = victim;
                 } else {  //如果不小于最小chunk,通过fd_nextsize找到不比他大的chunk
                     // 当前要插入的 victim 的大小大于最小的 chunk
                     // 判断 fwd 是否在 main arena
                     assert(chunk_main_arena(fwd));
                     // 从链表头部开始找到不比 victim 大的 chunk
                     while ((unsigned long) size < chunksize_nomask(fwd)) {
                         fwd = fwd->fd_nextsize;
                         assert(chunk_main_arena(fwd));
                     }
                     // 如果找到了一个和 victim 一样大的 chunk,
                     // 那就直接将 chunk 插入到该chunk的后面,并不修改 nextsize 指针。
                     if ((unsigned long) size ==
                         (unsigned long) chunksize_nomask(fwd))
                         /* Always insert in the second position.  */
                         fwd = fwd->fd;
                     else {
                         // 如果找到的chunk和当前victim大小不一样
                         // 那么就需要构造 nextsize 双向链表了
                         victim->fd_nextsize              = fwd;
                         victim->bk_nextsize              = fwd->bk_nextsize;
                         fwd->bk_nextsize                 = victim;
                         victim->bk_nextsize->fd_nextsize = victim;
                     }
                     bck = fwd->bk;
                 }
             } else
                 // 如果largre bin空的话,直接简单使得 fd_nextsize 与 bk_nextsize 构成一个双向链表即可。
                 victim->fd_nextsize = victim->bk_nextsize = victim;
         }
         //根据victim所在bin的序号在对应的binmap比特位上记录1   
         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;
        }  //到此,内层第一个循环结束

(9)如果用户申请的是large bin,在第一个循环结束后搜索large bin

chunk拆分过程
当用户申请较小chunk时会先将一个大的chunk进行拆分,合适的部分返回给用户,剩下的部分称为remainder则加入unsorted bin。
同时malloc_state中的last_remainder会记录最近拆分的remainder,起大小至少为MINSIZE(0x20)否则不能拆。

出现拆分的一种情况是,fast bin和small bin中都没有合适的chunk 同时unsorted bin中有且只有一个可拆分的chunk,并且改chunk是last remainder

        if (!in_smallbin_range(nb)) {
            bin = bin_at(av, idx);
            // 如果对应的 bin 为空或者其中的chunk最大的也很小,那就跳过
            // first(bin)=bin->fd 表示当前链表中最大的chunk
            if ((victim = first(bin)) != bin &&
                (unsigned long) chunksize_nomask(victim) >=
                    (unsigned long) (nb)) {
                // 反向遍历链表,直到找到第一个不小于nb大小的chunk
                victim = victim->bk_nextsize;
                while (((unsigned long) (size = chunksize(victim)) <
                        (unsigned long) (nb)))
                    victim = victim->bk_nextsize;

                // 如果最终取到的chunk不是该bin中的最后一个chunk,并且该chunk与其前面的chunk
                // 的大小相同,那么我们就取其前面的chunk,这样可以避免调整bk_nextsize,fd_nextsize
                //  链表。因为大小相同的chunk只有一个会被串在nextsize链上。
                if (victim != last(bin) &&
                    chunksize_nomask(victim) == chunksize_nomask(victim->fd))
                    victim = victim->fd;
                // 计算分配后剩余的大小
                remainder_size = size - nb;
                // 进行unlink
                unlink(av, victim, bck, fwd);

          
                // 如果改chunk-nb<MINSIZE,就直接返回给用户
                if (remainder_size < MINSIZE) {
                    set_inuse_bit_at_offset(victim, size);
                    if (av != &main_arena) set_non_main_arena(victim);
                }
                //如果大于MINSIZE就将remainder加入到unsorted bin中
                else {
                    // 获取剩下那部分chunk的指针,称为remainder
                    remainder = chunk_at_offset(victim, nb);
                    
                    // 插入unsorted bin中
                    bck = unsorted_chunks(av);
                    fwd = bck->fd;
                    // 判断 unsorted bin 是否被破坏。
                    if (__glibc_unlikely(fwd->bk != bck)) {
                        errstr = "malloc(): corrupted unsorted chunks";
                        goto errout;
                    }
                    remainder->bk = bck;
                    remainder->fd = fwd;
                    bck->fd       = remainder;
                    fwd->bk       = remainder;
                    // 如果不处于small bin范围内,就设置对应的字段
                    if (!in_smallbin_range(remainder_size)) {
                        remainder->fd_nextsize = NULL;
                        remainder->bk_nextsize = NULL;
                    }
                    // 设置分配的chunk的标记
                    set_head(victim,
                             nb | PREV_INUSE |
                                 (av != &main_arena ? NON_MAIN_ARENA : 0));

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

(10)接下来进入内层第二个循环,根据binmap来搜索bin。因为申请的chunk大小所对应的bin没有找到合适的chunk,所以就从下一个bin开始搜索

        ++idx;
        // 获取对应的bin
        bin   = bin_at(av, idx);
        // 获取当前索引在binmap中的block索引
        // #define idx2block(i) ((i) >> BINMAPSHIFT)  ,BINMAPSHIFT=5
        // Binmap按block管理,每个block为一个int,共32个bit,可以表示32个bin中是否有空闲chunk存在
        // 所以这里是右移5
        block = idx2block(idx);
        // 获取当前块大小对应的映射,这里可以得知相应的bin中是否有空闲块
        map   = av->binmap[ block ];
        // #define idx2bit(i) ((1U << ((i) & ((1U << BINMAPSHIFT) - 1))))
        // 将idx对应的比特位设置为1,其它位为0
        bit   = idx2bit(idx);
        for (;;) {

(11)在内层第二个循环内部,寻找第一个不为空的block,再根据比特位找到合适的bin

			/*binmap数组的元素类型为unsigned int,4字节,32比特,
			所以binmap的一个block能检查32个bin*/

            
            if (bit > map || bit == 0) {
                do {
                    // 如果++block>=BINMAPSIZE,说明遍历了所有bin且都为空
                    if (++block >= BINMAPSIZE) /* out of bins */
                        goto use_top;
                } while ((map = av->binmap[ block ]) == 0);
                // 获取其对应的bin,因为该map中的chunk大小都比所需的chunk大,而且
                // map本身不为0,所以必然存在满足需求的chunk。
                bin = bin_at(av, (block << BINMAPSHIFT));
                bit = 1;
            }

           
            // 找到block不为空的最小的bin对应的bit
            while ((bit & map) == 0) {
                bin = next_bin(bin);
                bit <<= 1;
                assert(bit != 0);
            }

(12)然后检查bit对应的bin是否为空,是则清空对应比特位,下一个bin开始再次循环,否则将victim从bin中取出

            // 获取对应的bin
            victim = last(bin);

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

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

                /* unlink */
                unlink(av, victim, bck, fwd);

(13)将取出的victim进行切分并把remainder加入到unsorted bin,如果victim不够切,就直接返回给用户。内层第二个循环到此结束

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

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

                    // 将剩余的chunk插入到unsorted bin中
                    bck = unsorted_chunks(av);
                    fwd = bck->fd;
                    if (__glibc_unlikely(fwd->bk != bck)) {
                        errstr = "malloc(): corrupted unsorted chunks 2";
                        goto errout;
                    }
                    remainder->bk = bck;
                    remainder->fd = fwd;
                    bck->fd       = remainder;
                    fwd->bk       = remainder;

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

(14)如果上面操作还不能满足,就只能从top chunk上进行切分

    use_top:
        // 获取当前的top chunk,并计算其对应的大小
        victim = av->top;
        size   = chunksize(victim);
        // 如果分割之后,top chunk 大小还大于MINSIZE,就进行切分
        if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) {
            remainder_size = size - nb;
            remainder      = chunk_at_offset(victim, nb);
            av->top        = remainder;
            // 这里设置 PREV_INUSE 是因为 top chunk 前面的 chunk 如果不是 fastbin,就必然会和
            // top chunk 合并,所以这里设置了 PREV_INUSE。
            set_head(victim, nb | PREV_INUSE |
                                 (av != &main_arena ? NON_MAIN_ARENA : 0));
            set_head(remainder, remainder_size | PREV_INUSE);

            check_malloced_chunk(av, victim, nb);
            void *p = chunk2mem(victim);
            alloc_perturb(p, bytes);
            return p;
        }
        // 否则如果有fastbins,就进行整理,等待外层循环做第二次尝试
        else if (have_fastchunks(av)) {
            // 先执行一次fast bin的合并
            malloc_consolidate(av);
            // 判断需要的chunk是在small bin范围内还是large bin范围内
            // 并计算对应的索引
            // 等待下次再看看是否可以
            if (in_smallbin_range(nb))
                idx = smallbin_index(nb);
            else
                idx = largebin_index(nb);
        }

        // 否则的话,我们就只能从系统中再次申请一点内存了。
        // 调用sysmalloc()函数进行分配,外层循环及_init_malloc()函数到此结束
        else {
            void *p = sysmalloc(nb, av);
            if (p != NULL) alloc_perturb(p, bytes);
            return p;
        }

主线程下,sysmalloc()的大概流程:

  1. 当申请的大小nb 大于 mp .mmap threshold 时,通过mmap(函数进行分配。其中mp .mmap threshold的默认大小为128×1024字
  2. 尝试用brk()扩展堆内存,形成新的 top chunk,而旧的 top chunk会被释放。然后从新的top chunk中切分出nb 大小的chunk,返回给用户。



free 相关源码

__libc_free

和malloc一样,free()函数实际上是__libc_free(),定义如下:

void __libc_free(void *mem) {
    mstate    ar_ptr;
    mchunkptr p; /* chunk corresponding to mem */
    // 判断是否有钩子函数 __free_hook,有就执行这个函数并返回
    void (*hook)(void *, const void *) = atomic_forced_read(__free_hook);
    if (__builtin_expect(hook != NULL, 0)) {
        (*hook)(mem, RETURN_ADDRESS(0));
        return;
    }
    // free NULL没有作用
    if (mem == 0) /* free(0) has no effect */
        return;
    // 将mem(指向user data的指针)转换为chunk状态(chunk指针)
    p = mem2chunk(mem);
    // 如果该块内存是mmap得到的,就用unmmap_chunk()函数释放
    if (chunk_is_mmapped(p)) 
    {

        if (!mp_.no_dyn_threshold && chunksize_nomask(p) > mp_.mmap_threshold &&
            chunksize_nomask(p) <= DEFAULT_MMAP_THRESHOLD_MAX &&
            !DUMPED_MAIN_ARENA_CHUNK(p)) {
            mp_.mmap_threshold = chunksize(p);
            mp_.trim_threshold = 2 * mp_.mmap_threshold;
            LIBC_PROBE(memory_mallopt_free_dyn_thresholds, 2,
                       mp_.mmap_threshold, mp_.trim_threshold);
        }
        munmap_chunk(p);
        return;
    }
    // 根据chunk获得分配区arena的指针
    ar_ptr = arena_for_chunk(p);
    // 执行释放
    _int_free(ar_ptr, p, 0);
}


_int_free

开头定义了一些所需变量

static void _int_free(mstate av, mchunkptr p, int have_lock) {
    INTERNAL_SIZE_T size;      /* its size */
    mfastbinptr *   fb;        /* associated fastbin */
    mchunkptr       nextchunk; /* next contiguous chunk */
    INTERNAL_SIZE_T nextsize;  /* its size */
    int             nextinuse; /* true if nextchunk is used */
    INTERNAL_SIZE_T prevsize;  /* size of previous contiguous chunk */
    mchunkptr       bck;       /* misc temp for linking */
    mchunkptr       fwd;       /* misc temp for linking */

    const char *errstr = NULL;
    int         locked = 0;

    size = chunksize(p);

获得想要释放的chunk大小,并对chunk做一些检查

	//第一个条件筛掉一些特别大的size;第二个条件检查chunk是否对齐
    if (__builtin_expect((uintptr_t) p > (uintptr_t) -size, 0) ||
        __builtin_expect(misaligned_chunk(p), 0)) {
        errstr = "free(): invalid pointer";
    errout:
        if (!have_lock && locked) __libc_lock_unlock(av->mutex);
        malloc_printerr(check_action, errstr, chunk2mem(p), av);
        return;
    }

    // 检查size是否过小以及size是否对齐
    if (__glibc_unlikely(size < MINSIZE || !aligned_OK(size))) {
        errstr = "free(): invalid size";
        goto errout;
    }
    // 检查该chunk是否处于使用状态,非调试状态下没有作用
    check_inuse_chunk(av, p);

p判断chunk是否在fast bin范围,是就插入fast bin

	//如果size<global_max_fast,这将之加入fast bin中
    if ((unsigned long) (size) <= (unsigned long) (get_max_fast())

#if TRIM_FASTBINS

       //默认 #define TRIM_FASTBINS 0,因此默认情况下下面的语句不会执行
       // 如果当前chunk是fast chunk,并且下一个chunk是top chunk,则不能插入
        && (chunk_at_offset(p, size) != av->top)
#endif
            ) {
        // 下一个chunk的大小不能小于两倍的SIZE_SZ,并且
        // 下一个chunk的大小不能大于system_mem, 一般为132k
        // 如果出现这样的情况,就报错。
        if (__builtin_expect(
                chunksize_nomask(chunk_at_offset(p, size)) <= 2 * SIZE_SZ, 0) ||
            __builtin_expect(
                chunksize(chunk_at_offset(p, size)) >= av->system_mem, 0)) {

            if (have_lock || ({
                    assert(locked == 0);
                    __libc_lock_lock(av->mutex);
                    locked = 1;
                    chunksize_nomask(chunk_at_offset(p, size)) <= 2 * SIZE_SZ ||
                        chunksize(chunk_at_offset(p, size)) >= av->system_mem;
                })) {
                errstr = "free(): invalid next size (fast)";
                goto errout;
            }
            if (!have_lock) {
                __libc_lock_unlock(av->mutex);
                locked = 0;
            }
        }
        // 将chunk的mem部分全部设置为perturb_byte
        free_perturb(chunk2mem(p), size - 2 * SIZE_SZ);
        // 设置fast chunk的标记位
        set_fastchunks(av);
        // 根据大小获取fast bin的索引
        unsigned int idx = fastbin_index(size);
        // 获取对应fastbin的头指针,被初始化后为NULL。
        fb               = &fastbin(av, idx);

        /* Atomically link P to its fastbin: P->FD = *FB; *FB = P;  */
        // 使用原子操作将P插入到链表中
        mchunkptr    old     = *fb, old2;
        unsigned int old_idx = ~0u;
        do {
           
            // 简单检查fast bin中第一个chunk是不是当前释放chunk,防止对 fast bin double free
            if (__builtin_expect(old == p, 0)) {
                errstr = "double free or corruption (fasttop)";
                goto errout;
            }
          
            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);
        // 确保fast bin的加入前与加入后相同
        if (have_lock && old != NULL && __builtin_expect(old_idx != idx, 0)) {
            errstr = "invalid fastbin entry (free)";
            goto errout;
        }
    }

chunk合并过程
当一个非fast bin的chunk释放:

  • 顺序先向后(上)合并再向前(下)合并
  • 向前合并合并的是top chunk就形成新的top chunk
  • 向前合并的不是top chunk就加入unsorted bin中
	else if (!chunk_is_mmapped(p)) {
        if (!have_lock) {
            __libc_lock_lock(av->mutex);
            locked = 1;
        }
        nextchunk = chunk_at_offset(p, size);
..........
	//向后合并,新chunk地址与上一个相同
	if (!prev_inuse(p)) {
            prevsize = prev_size(p);
            size += prevsize;
            p = chunk_at_offset(p, -((long) prevsize));
            //到这里就形成了一个新的free chunk,使用unlink()将其从链表中删除
            unlink(av, p, bck, fwd);
        }
	// 如果下一个chunk不是top chunk
        if (nextchunk != av->top) {
            // 获取下一个 chunk 的使用状态
            nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
            // 如果不在使用,合并,否则清空当前chunk的使用状态。
            if (!nextinuse) {
                unlink(av, nextchunk, bck, fwd);
                size += nextsize;
            } else
                clear_inuse_bit_at_offset(nextchunk, 0);
            // 把 chunk 放在 unsorted chunk 链表的头部
            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;
            // 如果是 large chunk,那就设置nextsize指针字段为NULL。
            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);
        }
        // 如果要释放的chunk的下一个chunk是top chunk,那就合并到 top chunk
        else {
            size += nextsize;
            set_head(p, size | PREV_INUSE);
            av->top = p;
            check_chunk(av, p);
        }

如果chunk不是mmap()生成的,就需要合并,先向后合并再向前合并。如果合并后的chunk超过了FASTBIN_CONSOLIDATION_THRESHOLD,就会整理fast bin 并向系统返回内存。_int_free()到此结束

        if ((unsigned long) (size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
            if (have_fastchunks(av)) 
            malloc_consolidate(av);  // 尝试整理fast bin
            // 以下代码用来向系统返回内存,比如返回brk()申请的堆内存
            if (av == &main_arena) {
#ifndef MORECORE_CANNOT_TRIM
                // top chunk 大于当前的收缩阙值
                if ((unsigned long) (chunksize(av->top)) >=
                    (unsigned long) (mp_.trim_threshold))
                    systrim(mp_.top_pad, av);
#endif      // 非主分配区,则直接收缩heap
            } else {

                heap_info *heap = heap_for_ptr(top(av));

                assert(heap->ar_ptr == av);
                heap_trim(heap, mp_.top_pad);
            }
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值