堆基础
内存分配后的系统调用主要是(s)brk与mmap、munmap函数。start_brk指向bss的末尾,brk()与sbrk()来移动program_break使得堆增长;当用户申请内存过大(大于128kb),ptmalloc2会选择mmap函数创建匿名映射段供用户使用,并通过unmmap函数回收。
chunk
1 | chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
对于已经分配的chunk,前两个字段称为chunk header,后面称为user data。每次 malloc 申请得到的内存指针,也就是指向 user data 的起始处。
对于prev_size字段,分配时可被上一chunk复用,空闲时代表前一chunk的size。这里的前一chunk指较低地址的chunk。
对于size字段,该字段的低三个比特位对chunk大小没有影响,从高到低依次为:A:记录当前 chunk 是否不属于主线程,1 表示不属于,0 表示属于;M:记录当前 chunk 是否是由 mmap 分配的;P:记录前一个 chunk 块是否被分配,堆中第一个chunk P位常设置为1。
一般来讲,物理相邻的两个空闲 chunk 会被合并为一个 chunk 。堆管理器会通过 prev_size 字段以及 size 字段合并两个物理相邻的空闲 chunk块。
top chunk
程序第一次进行 malloc 的时候,heap 会被分为两块,一块给用户,剩下的那块就是 top chunk。其实,所谓的 top chunk 就是处于当前堆的物理地址最高的 chunk。这个 chunk 不属于任何一个 bin,它的作用在于当所有的 bin 都无法满足用户请求的大小时,如果其大小不小于指定的大小,就进行分配,并将剩下的部分作为新的 top chunk。否则,就对 heap 进行扩展后再进行分配。在 main arena 中通过 sbrk 扩展 heap,而在 thread arena 中通过 mmap 分配新的 heap。需要注意的是,top chunk 的 prev_inuse 比特位始终为 1,否则其前面的 chunk 就会被合并到 top chunk 中。
remainder
当用户申请的chunk较小时,会先将一个较大的chunk进行拆分,合适的部分交给用户,剩下的部分则加入unsorted bin中(称为remainder)。同时malloc_state中的last_remainder会记录最近拆分出的remainder。
拆分chunk的一种情况是:fast bin与small bin中没有合适的chunk,同时unsorted bin中有且只有一个可拆得分的chunk,并且为last remainder。
bin
bin对应的数据结构在malloc_state中:
1 |
|
bins就是用于存储 unstored bin,small bins 和 large bins 的 chunk 链表。
一个bin对应两个bins,一共有126个bin:
bin1为unsorted bin;
bin2到bin63为small bin;
bin64到bin126为large bin;
fast bin
程序申请与释放的堆块往往比较小,所以glibc采用单链表且LIFO。
FastbinsY也是一个bin数组,一共NFASTBINS个fast bin.
为了加快速度,fast bin里的chunk不会合并。所以下一个chunk的inuse始终为1,使其始终为使用状态。
unsorted bin
使用双链表结构,FIFO。unsorted bin 可以视为空闲 chunk 回归其所属 bin 之前的缓冲区。其chunk大小可以不同,主要两个来源:
1.当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
2.释放一个不属于 fastbin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
small bin
32-1008字节与fastbin有重合的地方
large bin
分为6组,每组能容纳的成等差数列。双链表结构,按大小顺序排列。fd_nextsize与bk_nextsize分别都用来指向第一个与自己不同大小的chunk,为加快检索速度。
malloc_consolidate
由于fastbin中的chunk无法释放,导致相邻chunk无法与之合并,从而造成大量内存碎片,malloc_consolidate函数即用来解决这个问题,该函数可将fastbin中的chunk取出来,与相邻的free chunk合并后放入unsorted bin中或者形成新的top chunk。
malloc
_libc_malloc()
1 | void * __libc_malloc (size_t bytes) |
_int_malloc()
_int_malloc()是内存分配的核心函数,为了以最快速度找到最合适的chunk,glibc根据申请堆块的大小和各个bin的状态安排了分配顺序和内存整理的时机,大致搜索顺序:
1)fast bin(大小完全一样)
2)small bin(大小完全一样)
3)unsorted bin(大小完全一样)
4)large bin(大小完全一样)
5)bins(寻找最小的能满足的)
6)top chunk(切出合适的chunl)
7)系统函数分配