Glibc ptmalloc learning

花了一个多月,断断续续的读 glibc 中堆管理的源码,第一遍读的断断续续好多地方没懂,于是又读了第二遍,差不多了解了。本来打算写个总结的东西,但是笔记稍微有点乱,整理起来比较浪费时间,想了想算了,网上这种东西蛮多了,而且这个基本上跟着华庭的 pdf 一遍源码读下来,就懂得差不多了,这里我在读源码的时候加了蛮多注释,给大家分享一下,另外我读的版本是2.27

glibc_ptmalloc_learning

主要也就是malloc.carena.c, 希望对以后学习堆的筒子能够有所帮助。

先贴一下当时看的文章,当时主要是看华庭的 pdf, 然后就是头哥博客,华庭的 pdf 是2.22版本,所以有一些出入,不过大体上没啥变化,也看了一些其他的文章

最后贴一个笔记,一个简单的函数总结

ptmalloc_init

  • 1.调用malloc_init_state初始化主分配区
  • 2.设置参数

malloc_init_state

  • 1.所有的 bin 的链表头指向自身
  • 2.设置 flags 参数,表明分配空间连续与否
  • 3.如果是主分配区,设置 fastbin 的最大值
  • 4.初始化 topchunk

arena_get

  • 1.查看私有实例是否存在分配区
  • 2.调用arena_lock

arena_lock

  • 1.如果私有实例中的分配区可用,对其加锁
  • 2.否则调用arena_get2获取分配区

arena_get2

  • 1.调用get_free_list获取一个空闲的分配区并返回
  • 2.否则如果没到narenas_limit, 则创建一个新的分配区
  • 3.到达了narenas_limit, 则重用

reused_arena

  • 1.从上次记录的分配区的下一个开始 (next_to_use) 挨个尝试加锁
  • 2.如果都失败了,则等待next_to_use的分配区
  • 3.如果加锁成功一个,更新next_to_use以及一些其它值返回

new_heap

  • 1.调整 size 合法(大小 + 对齐)
  • 2.尝试从aligned_heap_area开始映射HEAP_MAX_SIZE大小的空间
  • 3.aligned_heap_area为空或第二步失败了则直接映射2*HEAP_MAX_SIZE, 计算对齐后的起始地址(成功会更新aligned_heap_area)
  • 4.第三步失败了再次尝试映射HEAP_MAX_SIZE大小
  • 5.234 任意一步成功了,将其中 size 大小的空间设置位可读可写返回作为新的模拟堆

grow_heap

  • 1.将要新增的大小按页对齐
  • 2.如果不大于HEAP_MAX_SIZE也不大于mprotect_size就更新 size 代表增长成功

shrink_heap

  • 1.收缩后大小大于堆的结构体大小,则更新 size 代表收缩

delete_heap

  • 1.当前堆如果与aligned_heap_area相同,则更新aligned_heap_area为 NULL, 然后删除掉

heap_trim

  • 1.top_chunk起始地址等于heap_info结束地址,直接释放整个subheap
    • (1) 构建 fencepost
    • (2) 如果前一个 subheap 的空间太小,那就不释放了
    • (3) 调用delete_heap 释放 subheap
    • (4) 使用 unlink 释放倒数第二个空闲 chunk, 然后更新各类信息
  • 2.topchunk太小 or 还没有达到收缩阈值 , 退出
  • 3.否则按页对齐之后调用shrink_heap进行收缩

__libc_malloc

  • 1.调用__malloc_hook
  • 2.寻找分配区加锁
  • 3.调用_int_malloc

_int_malloc

  • 1.当前分配区不可用,调用sysmalloc
  • 2.在fastbin中进行精确匹配,取第一个
  • 3.在smallbin中进行精确匹配,取最后一个
  • 4.smallbinfastbin都无法匹配到,先合并fastbin并放到unsortedbin
  • 5.进入循环
    • (1) 反向遍历unsortedbin, 最多 10000 次
      • 在 smallbin 的范围内且 unsortedbin 里面只剩一个remainder且大小符合要求,则直接分配
      • 当前刚好是合适精确大小的,直接分配
      • 当前 chunk 符合smallbin, 放入对应smallbin第一个位置
      • 当前 chunk 符合largebin, 放入对应位置
    • (2) 尝试从对应大小的largebins中反向遍历寻找最合适的最小的 bin 进行分配,此处调用unlink()
    • (3) 尝试从比需求大小更大的largebins中反向遍历寻找最合适的最小的 bin 进行分配,此处调用unlink()
    • (4) 尝试通过topchunk进行分配空间
    • (5) 否则尝试再次合并fastbins,合并成功则继续循环
    • (6) 再否则调用sysmalloc从系统分配内存

sysmalloc

  • 1.分配区超过了 mmap 的分配阈值,直接用 mmap 分配
  • 2.非主分配区
    • 调用grow_heap增长 topchunk 大小
    • 增长失败则调用new_heap新建模拟堆,这里划分 fencepost(会调用_int_free)
    • 又失败了尝试利用 mmap 来申请
  • 3.主分配区
    • 调用sbrk()函数抬高堆上界,拓展 topchunk
    • 成功则调用 hook 函数,失败则调用 mmap
    • 然后矫正分配后的空间,更新 topchunk, 从新的 topchunk 分配空间

malloc_consolidate

  • 遍历 fastbin
    • 正向遍历当前的 fastbin 的每一个 chunk
      • 该 chunk 的前一个 chunk 空闲则合并,调用unlink摘除
      • 该 chunk 的后一个 chunk 空闲且不为topchunk, 调用unlink摘除,然后加入到 unsortedbin 中的第一个的位置
      • 该 chunk 的后一个 chunk 空闲且为topchunk, 合并到topchunk

__libc_free

  • 1.如果是 mmap 分配就直接调用munmap_chunk还给操作系统,同时动态调整 mmap 的分配阈值和收缩阈值
  • 2.如果不是 mmap, 调用_int_free

_int_free

  • 1.几项检查
    • chunk 的指针是否大小溢出,是否对齐
    • chunk 的大小要大于 MINSIZE, 并且对齐
    • do_check_inuse_chunk的检查
  • 2.chunk 的大小符合fastbin
    • 检查下一个 chunk 的大小要大于2 * SIZE_SZ, 也要小于system_mem
    • 检查double free, 然后插入到对应 bin 的第一个
    • 插入完检查 fastbin 的 index 是否正确
  • 3.不是由 mmap 分配的,将加入unsortedbin
    • 检查不能 free 掉topchunk
    • 检查下一个 chunk 不能超过topchunk的结束地址
    • 检查下一个 chunk 的 prev_inuse 位
    • 检查下一个 chunk 的大小要大于2 * SIZE_SZ, 也要小于system_mem
    • 前一个 chunk 空闲则合并,调用unlink摘除
    • 下一个 chunk 不为topchunk, 为空就合并(调用unlink摘除), 不为空就正常继续进行 free, 放入unsortedbin的第一个
    • check_free_chunk检查
    • 下一个 chunk 是topchunk , 将当前合并到topchunk
    • 最终的 size(各种合并之后) 大于FASTBIN_CONSOLIDATION_THRESHOLD(64KB), 调用malloc_consolidatefastbin合到unsortedbin
  • 4.如果是 mmap 分配的,调用munmap_chunk()释放