chunk可以分成allocated chunk,free chunk,top chunk,last remainter chunk
- allocated chunk:当前chunk是被应用层用户所使用的
- free chunk:当前chunk是空闲的,没有被应用层用户所使用
- top chunk:不属于任何的bins,主要是当我们在free的bins中没有找到我们所需的bins时,就从top bins中划分出我们所需的bins。当top bins不够时,需要使用brk分配
- last remainter chunk
由于在libc2.26之后引入tcachebins,所以堆分配机制和之前不太相同
tcachebins——tcachebins是一个长度为64的字节数组,每个字节数组对应一条链表。所以tcachebins只能存放0x0-0x400大小的堆,且每个链表长度为7。并且类似于fastbins,是一个单链表。在释放大小为0x0-0x400大小的堆的时候,首先会被释放入对应长度tcachebins对应的链表中,当长度超出7后,再放入fastbin或unsortbins中。malloc的时候当发现malloc对应大小的堆,先从tcachebins中取出。注意当如果从fastbin中取出了一个块,那么会把剩余的块放入tcache中直至填满tcache(smallbin中也是一样)。如果进入了unsortedbin,且chunk的size和当前申请的大小精确匹配,那么在tcache未满的情况下会将其放入到tcachebin中
fastbins——fastbins上有7个单链表,是bins数组上的前十位,存放大小为0x20-0x80的堆,存放时不合并
sortbins——当有大于0x400或在0x80-0x400的时候tcachebins以存放满,多余的堆将让放入sortbins。
smallbins——通常是在对sortbins和fastbins整理后放入
内存分配malloc流程
- 获取分配区的锁,防止多线程冲突。
- 计算出实际需要分配的内存的chunk实际大小。
- 先从tcachebins中查找是否有对应大小堆块,如果存在将先从tcachebins中取出。
- 判断chunk的大小,如果小于max_fast(128B),则尝试去fast bins上取适合的chunk,如果有则分配结束,并将剩余的块放入tcachebins直到存满。否则,下一步;
- 判断chunk大小是否小于1024B,如果是,则从small bins上去查找chunk,如果有合适的,则分配结束,并将剩余的块放入tcachebins直到存满。下一步;
- 判断chunk大小是否小于1024B,如果是,如果unsorted bin中有满足chunk大小的,则直接切割返回。并将unsorted bin中剩下的部分整理入small bins和large bins上。否则直接整理。否则操作top chunk。
- 当申请大于1024B,ptmalloc首先会遍历fast bins中的chunk,将相邻的chunk进行合并,并链接到unsorted bin中然后遍历 unsorted bins。如果unsorted bins上只有一个chunk并且大于待分配的chunk,则进行切割,并且剩余的chunk继续扔回unsorted bins;如果unsorted bins上有大小和待分配chunk相等的,则返回,并从unsorted bins删除;如果unsorted bins中的某一chunk大小 属于small bins的范围,则放入small bins的头部;如果unsorted bins中的某一chunk大小 属于large bins的范围,则找到合适的位置放入。若未分配成功,转入下一步;
- 从large bins中查找找到合适的chunk之后,然后进行切割,一部分分配给用户,剩下的放入unsorted bin中。
- 如果搜索fast bins和bins都没有找到合适的chunk,那么就需要操作top chunk来进行分配了
当top chunk大小比用户所请求大小还大的时候,top chunk会分为两个部分:User chunk(用户请求大小)和Remainder chunk(剩余大小)。其中Remainder chunk成为新的top chunk。
当top chunk大小小于用户所请求的大小时,top chunk就通过sbrk(main arena)或mmap(thread arena)系统调用来扩容。 - 到了这一步,说明 top chunk 也不能满足分配要求,所以,于是就有了两个选择: 如 果是主分配区,调用 sbrk(),增加 top chunk 大小;如果是非主分配区,调用 mmap 来分配一个新的 sub-heap,增加 top chunk 大小;或者使用 mmap()来直接分配。在 这里,需要依靠 chunk 的大小来决定到底使用哪种方法。判断所需分配的 chunk 大小是否大于等于 mmap 分配阈值,如果是的话,则转下一步,调用 mmap 分配, 否则跳到第 10 步,增加 top chunk 的大小。
- 使用 mmap 系统调用为程序的内存空间映射一块 chunk_size align 4kB 大小的空间。 然后将内存指针返回给用户。
- 判断是否为第一次调用 malloc,若是主分配区,则需要进行一次初始化工作,分配 一块大小为(chunk_size + 0x250) 大小的空间作为初始的 heap。若已经初 始化过了,主分配区则调用 sbrk()增加 heap 空间,分主分配区则在 top chunk 中切 割出一个 chunk,使之满足分配需求,并将内存指针返回给用户。
简而言之: 获取分配区(arena)并加锁–> fast bin –> unsorted bin –> small bin –> large bin –> top chunk –> 扩展堆
内存回收流程
- 获取分配区的锁,保证线程安全。
- 如果free的是空指针,则返回,什么都不做。
- 判断当前chunk是否是mmap映射区域映射的内存,如果是,则直接munmap()释放这块内存。前面的已使用chunk的数据结构中,我们可以看到有M来标识是否是mmap映射的内存。
- 判断chunk是否在tcachebins范围内且tcachebins对应位置未满,如果满足,加入tcachebins中,否则进入下一步
- 判断chunk是否与top chunk相邻,如果相邻,则直接和top chunk合并(和top chunk相邻相当于和分配区中的空闲内存块相邻)。转到步骤8
- 如果chunk的大小大于max_fast(64b),则放入unsorted bin,并且检查是否有合并,有合并情况并且和top chunk相邻,则转到步骤8;没有合并情况则free。
- 如果chunk的大小小于 max_fast(64b),则直接放入fast bin,fast bin并没有改变chunk的状态。没有合并情况,则free;有合并情况,转到步骤7
- 在fast bin,如果当前chunk的下一个chunk也是空闲的,则将这两个chunk合并,放入unsorted bin上面。合并后的大小如果大于64B,会触发进行fast bins的合并操作,fast bins中的chunk将被遍历,并与相邻的空闲chunk进行合并,合并后的chunk会被放到unsorted bin中,fast bin会变为空。合并后的chunk和topchunk相邻,则会合并到topchunk中。转到步骤8
- 判断top chunk的大小是否大于mmap收缩阈值(默认为128KB),如果是的话,对于主分配区,则会试图归还top chunk中的一部分给操作系统。free结束。