本文讨论了:
1.ptmalloc的简单概念
2.各种chunk
3.bin数组以及brk和mmap
1.ptmalloc的简单概念
glibc在开始的时候malloc是不支持多线程的,但是在glibc_2.3x中集成了ptmalloc2,也就是平常使用的malloc。这就实现了对多线程编程的支持。本文将讨论ptmalloc的整个malloc(),free()以及他的初始化等。
为了保证内存的高效分配,在第一次调用malloc函数的时候会对整个ptmalloc初始化。初始化将预先分配一块内存,然后切割成若干块。将大于内存分配请求的一块内存交给当前进程(包含头部尾部,还要有一部分来记录)。当free的时候这块内存也不会立刻返回给操作系统,而是放到一个bin数组下,这个数组采用链表的方式保存着若干内存块,当触发某个条件的时候,内存会合并并且修改地址到另外的元素指针下。尽量避免内存碎片。
对于ptmalloc,他的设计是这样的:
1.具有长生命周期的大内存将使用mmap分配。
2.对于短生命周期的内存分配将使用brk系统调用。
3.对于小内存块的释放将返回到bin数组下,大内存(使用mmap分配的)将直接返回给操作系统。
4.小内存块的合并(切割)仅仅在malloc和free的时候,并且合并(切割)以后也不一定返回给操作系统
5.设置了若干阀值来避免内存暴增的现象。
6.为了支持多线程,多个线程可以从同一个分配区分配内存,但是会使用锁来保证线程安全。同时为了优化算法,在出现内存不够的时候会重新创建一个分配区。
2.chunk
前面提到了bin数组是存放空闲内存块的,内存块的将翻译成chunk,一个使用中的chunk是这样的
chunk指针代表着chunk的头,一个chunk包含了用户请求的内存区域和相关的控制信息,mem指针指向的才是真正返回给用户的内存指针。P代表着前一个内存块是否被使用,p为0时代表前一个chunk为空闲,此时第一个域才有效。p为1时代表前一个chunk使用中,第一个域无效。M代表着分配区,为0时代表从heap区分配,为1时代表从mmap分配。
A代表着分配区,为0时代表主分配区,为1时代表非主分配区。
空闲时的chunk如图。
当chunk空闲时,M状态不存在并且在原本用户区的地方存储了包含后一个空闲chunk,前一个空闲chunk的指针。同时包含了为了加快在large bin中查找最近匹配的空闲chunk的指针(比如在large bin,这个chunk和后一个chunk大小一样,指针将直接指向后两个chunk的位置)。
chunk的空间复用
为了让其最大化利用当前chunk的空间,在chunk被使用的时候,第一个域是没有用的。那么错过你最大化利用处理,一个使用中的chunk的大小应该是(用户请求大小+4)并且与8对齐。
3.Bins
在free内存以后,内存不会马上归还给系统,ptmalloc会统一管理heap和mmap银蛇区域中的空心啊chunk,当用户进行下一次分配请求时,ptmalloc会首先在空闲的chunk挑选一块返回,这些chunk被用一个双向链表连接起来,这个链表叫bin。ptmalloc维护了128个bin并用一个数组来存储(通俗的说法就是一个128大小的数组,里面放着指向双向链表的头结点的指针,每个相连bin大小差为8B)
数组分三层,第一层是unsorted bin,第二层是small bin,第三层是large bin。最低一层是unsorted bin,这一层又分为两个块 :fast bins和unsortedbin。fast bins 这一层放着小于64B的内存块,当一个内存小于64B的被free后,将放入到Fast Bins.如果在接收到小于64B的malloc请求,将首先从fast bins中查找。查找不到将进入unsorted bin中查找。
fast bin的内存在某个特定的时候将合并放入第二层unsorted bin(合并时间后文会讨论)。这也就避免了最小找不到就直接到最大找的情况。unsorted bin同时接受用户释放的chunk大于max_fast的内存块。可以将其理解为一个最小和最大的润滑剂
在bin数组无法满足内存分配需求的时候,ptmalloc将寻找一块大块内存。
大块内存又分为以下几种:Top chunk,mmaped chunk和lase remainder.接下来将讨论前两个。
Top chunk
Top chunk对于主分配区和非主分配区的分配方式是不一样的。对于非主分配区,会预先从mmap区银河一块较大内存模拟sub_heap,通过管理sub_heap来响应用户请求。因为内存是按地址从低向高进行分配的,在空闲内存的最高处一定会存在着一块空闲的chunk,叫做top chunk。当bins不能满足修的时候,ptmalloc会从top chunk中挑一块内存返回。如果top chunk自己也不够大,那么分配程序会从新分配一个sub_heap并且将top chunk迁移到新的sub heap上。新的sub heap将和旧的连接起来,然后在从里面分配内存。Top chunk是永远不会返回进bin数组的,如果chunk被free掉,那么chunk会直接返回到sub_heap中。如果返回的chunk和top相邻,那么这两个chunk就会合成新的top chunk,从而使top chunk变大。如果在free的时候内存大于某个阀值,并且top chunk的大小也超过了收缩阀值,那么ptmalloc会搜索sub_heap.如果top_chunk包含了整个sub_heap ,ptmalloc会调用munmap将整个sub_heap的内存返回给操作系统。
对于主分配区,主分配区是唯一能够映射到进程heap区的分配区,它可以通过sbrk来增大或者搜索进程heap的大小。ptmalloc会在开始的时候预先分配一块较大的空闲内存(作为初始的heap给全局),主分配区的top chunk在第一次调用malloc(超过bin数组上限)时会分配一块(chunk_size+128K)align4KB大小的空间作为初始的heap(具体大小其实是可以调整的)。当返回内存的时候如果恰好和 top chunk相邻就合并成新的top chunk。当单次回收或者top chunk超过阀值,会执行内存收缩。减小top chunk的大小。如果向主分配区的top chunk申请但是top chunk没有足够的内存,会执行sbrk将heap的边界上移,然后调整top chunk的大小。
mmaped chunk
如果需要分配的chunk 足够大,大到连bin甚至top chunk本身也不能满足分配需求的时候,ptmalloc会使用mmap来慧姐使用内存映射来将页映射到进程空间。这样分配的chunk在被free的时候将直接解除映射。于是就将内存返回给操作系统。此时如果在对该内存区的引用将产生段错误(segmentation fault)这样的chunk也不会包含在任何bin中。
brk修改heap区的大小,mmap映射
.bss段之上分配给用户程序的空成为heap,start_brk指向heap的开始,而brk指向heap的顶部。可以使用系统调用brk()和sbrk()来增大标识heap顶部的brk值。从而增大heap空间。在使用malloc之前,brk的值等于start_brk,即heap大小为0,ptmalloc开始的时候如果请求的空间小于mmap分配阀值(默认128K)主分配区会调用sbrk增加一块大小为(128K+chunk_size)lign4K 的空间作为heap.非主分配区会调用mmap映射一块大小为HEAP_MAX_SIZE(32位系统默认是1M,64位默认是64M)的空间作为sub_heap。这就是ptmalloc所维护的分配空间。
当用户的请求超过mmap分配阀值,并且主分配区调用sbrk失败,或者是非主分配区在top chunk中不能分配到需要的内存,ptmalloc会尝试调用mmap直接映射一块内存到进程内存空间,使用mmap直接映射的chunk在释放的时候会直接解除映射。