之前写了一篇关于ptmalloc的malloc/free代码解析的博客,有同事看到说,在不熟悉ptmalloc架构的情况,即使注释写的再详细,也不是很好理解,建议再写一些架构介绍的博文,年前后这段时间一直在忙其他事情,打算最近抽出一些时间完成这个任务。
ptmalloc的设计遵循以下原则:
1,较大块的内存,占用周期长得内存使用mmap分配,使用时间短的内存使用brk,如果不了解brk和mmap这两个系统调用,请看这里
2,缓存小的,临时使用的内存,大块内存和长时间使用的内存直接跟kernel交互。
3,malloc和free在必要的时候会合并或者切割以保证能拿到合适的内存块。
4,分配区最大粒度的单位为分配区(arena),可能有多个,包括一个主分配区(main arena)和不定数量的非主分配区(non main arena),非主分配区数目只增不减。
5,每个分配区都配有mutex,在操作分配区时需加锁,属于比较浪费性能的地方,最新的版本已经使用原子操作代替某些锁操作。
6,非主分配区只使用mmap跟OS索取内存,通过使用宏HEAP_MAX_SIZE,32位系统每次1M,64位为64M
宏声明如下:
# if __WORDSIZE == 32
# define DEFAULT_MMAP_THRESHOLD_MAX (512 * 1024)
# else
# define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long))
# endif
# define HEAP_MAX_SIZE (2 * DEFAULT_MMAP_THRESHOLD_MAX)
前面已经说了,内存的最大组织单位为分配区(arena),而内存分配的基本单位为trunk。
ptmalloc将一部分大小差不多(smallbin完全相等,largebin在一定范围)的chunk用双向链表连接起来,这样一个链表被叫做一个bin。
glibc一共维护128个bin,以数组的方式管理每个bin的头结点,是个很典型的hash结构。
1,除index为0外,所有数组是按chunk的大小递增排列的。
2,index为0的bin为unsorted bin,字面意思是没有排序的chunk,这个bin里面是刚刚释放的内存,没有整理到后面的有序chunk链表中。
3,index从1到64的bin被称为small bin,同一个bin中的chunk大小完全相同。
4,每两个相邻的smallbin的chunk大小相差8个bytes。
5,index从64到127的bin被称为largebin,同一个bin中的chunk大小在一个范围内,从头到位按从大到小排序(大的在头,便于查找所需的空间)。
6,为了提高小内存的申请和释放效率(事实上,通常大块内存申请释放的频率不是很高),所以对于应用程序释放的小于某个阀值的内存(默认64B)会被装入一个叫做fastbin的容器中,当应用程序再次申请不大于这个阀值的内存时,会首先从fastbin中找寻。glibc会周期性地遍历fastbin,将空闲chunk合并,归入unsortedbin然后装入bin中
除了双向链表和数组联合表示的bin类trunk结构之外,chunk还有其他的组织方式。
1,Top chunk
主从分配区的Top chunk是不一样的
从分配区通过mmap分配一块较大的子heap区域,因为内存从低到高分配,则高处空闲的一块chunk即为top chunk
主分配区用sbrk改更改break位置来修改进程heap区域,malloc第一次被调用时glibc会给分配一块(size + 128)大小的四字节对齐大小的内存,是为主分配区的top Chunk。
top chunk总是在fastbins和bins之后的第三选择。
2,mapped chunk
当需要的chunk非常大,bin和topchunk都不能满足需求时,glibc会直接使用mmap通过匿名映射得到大块内存,提供给应用程序。
这种chunk在free时会直接通过unmmap解除映射,将内存归还操作系统,所以大块内存操作对于glibc来说属于效率非常低的。
mapped chunk至始至终不会归于任何bin结构管理,free的内存再次操作会导致非法内存访问。
3,last remainder
last remainder同样不会属于bin管理。
如果需要一个比较小的内存,但是smarllbins中找不到合适的chunk,同时last remainder大于所需的内存大小,则从里面切割出来一部分给应用,另外的部分仍然作为last remainder