整体介绍:
在glibc malloc中针对堆管理,主要涉及到以下3种数据结构:
heap_info:
即Heap Header,因为一个thread arena(注意:不包含mainthread)可以包含多个heaps,所以为了便于管理,就给每个heap分配一个heap header。那么在什么情况下一个thread arena会包含多个heaps呢?在当前heap不够用的时候,malloc会通过系统调用mmap申请新的堆空间,新的堆空间会被添加到当前thread arena中,便于管理。
malloc_state:
即Arena Header,每个thread只含有一个Arena Header。Arena Header包含bins的信息、top chunk以及最后一个remainder chunk等。
malloc_chunk:
即Chunk Header,一个heap被分为多个chunk,至于每个chunk的大小,这是根据用户的请求决定的,也就是说用户调用malloc(size)传递的size参数“就是”chunk的大小。每个chunk都由一个结构体malloc_chunk表示。
Ptmalloc2
Ptmalloc2通过几种数据结构来进行管理,主要有arena,heap,chunk三种层级。Chunk为分配给用户的内存的一个单位。其实arena和heap我感觉都是对chunk的一种组织方式,方便之后的分配,arena又是对heap的组织。
arena
对于32位系统,数量最多为核心数量2倍,64位则最多为核心数量8倍,可以用来保证多线程的堆空间分配的高效性。主要存储了较高层次的一些信息。有一个main_arena,是由主线程创建的,thread_arena则为各线程创建的,当arena满了之后就不再创建而是与其他arena共享一个arena,方法为依次给各个arena上锁(查看是否有其他线程正在使用该arena),如果上锁成功(没有其他线程正在使用),则使用该arena,之后一直使用这个arena,如果无法使用则阻塞等待。
heap
heap的等级就比arena要低一些了,一个arena可以有多个heap,也是存储了堆相关的信息。可以看成是次于arena的内存管理结构。在首次创建arena的时候会相应的生成一个heap segment,而在单个heap segment容量不够的时候,这时候系统就要扩大堆空间。很显然,由于主进程通过sbrk来扩充,因此不会产生多个heap segment。相反的,其他进程通过mmap来扩充空间的时候,由于mmap开辟空间的不连续性,因此需要链表来联系多个heap空间,此时,每个链节便构成了一个heap segment。但是,为了方便管理,一个arena中不管有多少个heap segment,都只能存在一个malloc_state。
chunk
chunk为分配给用户的内存的一个单位,每当我们分配一段内存的时候其实就是分配得到了一个chunk,我们就可以在chunk当中进行一定的操作了。不过为了进行动态分配,chunk本身也有一些数据(元数据),是用来指示其分配等等的数据。
在glibc malloc中将整个堆内存空间分成了连续的、大小不一的chunk,即对于堆内存管理而言chunk就是最小操作单位。Chunk总共分为4类:
1)allocated chunk;
2)free chunk;
3)top chunk;
4)Last remainder chunk。
从本质上来说,所有类型的chunk都是内存中一块连续的区域,只是通过该区域中特定位置的某些标识符加以区分。为了简便,我们先将这4类chunk简化为2类:allocated chunk以及free chunk,前者表示已经分配给用户使用的chunk,后者表示未使用的chunk。
allcated chunk
以分配的chunk包含下述结构:a part of pre_chunk;chunk_size(flag included);user_data;padding.
NOTE:由于allcated chunk不需考虑内存合并的问题,因此无需关注a part of pre_chunk.padding是对齐字节。在chunk_size末尾的flag有三位,分别代表:pre_chunk的使用情况;mmap;非主进程。
free chunk
未分配的chunk结构和allcated chunk不相同,其将a part of pre_chunk替换成了pre_size,同时会储存一个前置后向指针,和一个后置前向指针。prev_size在前面的chunk是空闲的时候才是可用的。如果前面的chunk是正在被使用的,那么这个prev_size的空间则被前面的chunk所征用。
堆内存中要求每个chunk的大小必须为8的整数倍,因此chunk size的后3位是无效的,为了充分利用内存,堆管理器将这3个比特位用作chunk的标志位,典型的就是将第0比特位用于标记该chunk是否已经被分配。这样的设计很巧妙,因为我们只要获取了一个指向chunk size的指针,就能知道该chunk的大小,即确定了此chunk的边界,且利用chunk size的第0比特位还能知道该chunk是否已经分配,这样就成功地将各个chunk区分开来。注意在allocated chunk中padding部分主要是用于地址对齐的(也可用于对付外部碎片),即让整个chunk的大小为8的整数倍。
bin
在主线程调用free之后:从内存布局可以看出程序的堆空间并没有被释放掉,原来调用free函数释放已经分配了的空间并非直接“返还”给系统,而是由glibc 的malloc库函数加以管理。它会将释放的chunk添加到main arenas的bin(这是一种用于存储同类型free chunk的双链表数据结构)中。在这里,记录空闲空间的freelist数据结构称之为bins。之后当用户再次调用malloc申请堆空间的时候,glibc malloc会先尝试从bins中找到一个满足要求的chunk,如果没有才会向操作系统申请新的堆空间
显示链表就是我们在数据结构中常用的链表,而链表本质上就是将一些属性相同的“结点”串联起来,方便管理。在glibc malloc中这些链表统称为bin,链表中的“结点”就是各个chunk, 结点的共同属性就是: 1)均为free chunk; 2)同一个链表中各个chunk的大小相等。bin是一种记录free chunk的链表数据结构。系统针对不同大小的free chunk, 将bin分为了4类: 1) Fast bin; 2) Unsorted bin; 3) Small bin; 4) Large bin。
在glibc中用于记录bin的数据结构有两种,分别如下所示:
fastbinsY: 这是一个数组,用于记录所有的fast bins;
bins: 这也是一个数组,用于记录除fast bins之外的所有bins。事实上,一共有126个bins,分别是:
bin 1 为unsorted bin;
bin 2 到63为small bin;
bin 64到126为large bin。
Fast bin
既然有fast bin,那就肯定有fast chunk——chunk size为16到80字节的chunk就叫做fast chunk。为了便于后文描述,这里对chunk大小做如下约定:
1)只要说到chunk size,那么就表示该malloc_chunk的实际整体大小;
2)而说到chunk unused size,就表示该malloc_chunk中刨除诸如prev_size, size,fd和bk这类辅助成员之后的实际可用的大小。因此,对free chunk而言,其实际可用大小总是比实际整体大小少16字节。
在内存分配和释放过程中,fast bin是所有bin中操作速度最快的。
Unsorted bin
当释放较小或较大的chunk的时候,如果系统没有将它们添加到对应的bins中,系统就将这些chunk添加到unsorted bin中。这主要是为了让“glibc malloc机制”能够有第二次机会重新利用最近释放的chunk(第一次机会就是fast bin机制)。利用unsorted bin,可以加快内存的分配和释放操作,因为整个操作都不再需要花费额外的时间去查找合适的bin了。
Small bin
小于512字节的chunk称之为small chunk,small bin就是用于管理small chunk的。就内存的分配和释放速度而言,small bin比larger bin快,但比fast bin慢。
Large bin
大于512字节的chunk称之为large chunk,large bin就是用于管理这些large chunk的。
内存合并
fastbin下的chunk不会采取内存合并,这是为了保证fast bin下的chunk大小能尽可能地碎片化。
针对除fast bin以外的其他bin表,若是其中某个chunk刚好处于将要释放的chunk的相邻位置,那么会采取解链操作将该chunk从原来的bin中解除,然后和将释放的chunk合并,再将新的chunk(大于max_fast)放置在unsorted bin中。
向后合并
首先检测前一个chunk是否为free,这可以通过检测当前free chunk的PREV_INUSE(P)比特位知晓。在默认情况下,堆内存中的第一个chunk总是被设置为allocated的,即使它根本就不存在。如果为free的话,那么就进行向后合并:
1)将前一个chunk占用的内存合并到当前chunk;
2)修改指向当前chunk的指针,改为指向前一个chunk。
3)使用unlink宏,将前一个free chunk从双向循环链表中移除。
向前合并
类似
参考链接:
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/
http://www.cnblogs.com/alisecurity/p/5486458.html#4032991
https://www.cnblogs.com/alisecurity/p/5520847.html
https://blog.csdn.net/qq_40265677/article/details/79313913