ptmalloc下堆的分配和回收

ptmalloc内存分配

1) 获取分配区的锁,为了防止多个线程同时访问同一个分配区,在进行分配之前需要取得分配区域的锁。线程先查看线程私有实例中是否已经存在一个分配区,如果存在尝试对该分配区加锁,如果加锁成功,使用该分配区分配内存,否则,该线程搜索分配区循环链表试图获得一个空闲(没有加锁)的分配区。如果所有的分配区都已经加锁,那么ptmalloc 会开辟一个新的分配区,把该分配区加入到全局分配区循环链表和线程的私有实例中并加锁,然后使用该分配区进行分配操作。开辟出来的新分配区一定为非主分配区,因为主分配区是从父进程那里继承来的。开辟非主分配区时会调用mmap()创建一个sub-heap,并设置好top chunk 。
2) 将用户的请求大小转换为实际需要分配的chunk 空间大小。
3) 判断所需分配chunk 的大小是否满足chunk_size <= max_fast (max_fast 默认为 64B), 如果是的话,则转下一步,否则跳到第5 步。
4) 首先尝试在fast bins 中取一个所需大小的chunk 分配给用户。如果可以找到,则分配结束。否则转到下一步。
5) 判断所需大小是否处在small bins 中,即判断chunk_size < 512B 是否成立。如果 chunk 大小处在small bins 中,则转下一步,否则转到第6 步。
6) 根据所需分配的chunk 的大小,找到具体所在的某个small bin,从该bin 的尾部摘取一个恰好满足大小的chunk 。若成功,则分配结束,否则,转到下一步。
7) 到了这一步,说明需要分配的是一块大的内存,或者 small bins中找不到合适的chunk。于是,ptmalloc 首先会遍历fast bins 中的chunk,将相邻的chunk 进行合并,并链接到unsorted bin 中,然后遍历unsorted bin 中的chunk,如果unsorted bin 只有一个chunk,并且这个chunk 在上次分配时被使用过,并且所需分配的chunk 大小属于small bins,并且chunk 的大小大于等于需要分配的大小,这种情况下就直接将该chunk 进行切割,分配结束,否则将根据chunk 的空间大小将其放入smallbins 或是large bins 中,遍历完成后,转入下一步。
8) 到了这一步,说明需要分配的是一块大的内存,或者small bins 和unsorted bin 中都找不到合适的 chunk,并且fast bins 和unsorted bin 中所有的chunk 都清除干净了。从large bins 中按照“smallest-first ,best-fit ”原则,找一个合适的 chunk,从中划分一块所需大小的chunk,并将剩下的部分链接回到bins 中。若操作成功,则分配结束,否则转到下一步。
9) 如果搜索fast bins 和bins 都没有找到合适的chunk,那么就需要操作top chunk来
进行分配了。判断top chunk 大小是否满足所需chunk 的大小,如果是,则从top chunk中分出一块来。否则转到下一步。
10) 到了这一步,说明top chunk 也不能满足分配要求,所以,于是就有了两个选择:
如果是主分配区,调用sbrk(),增加top chunk 大小;如果是非主分配区,调用mmap来分配一个新的sub-heap,增加top chunk 大小;或者使用mmap()来直接分配。在这里,需要依靠 chunk的大小来决定到底使用哪种方法。判断所需分配的 chunk大小是否大于等于mmap分配阈值,如果是的话,则转下一步,调用mmap 分配,否则跳到第12 步,增加top chunk的大小。
11) 使用mmap 系统调用为程序的内存空间映射一块chunk_size align 4kB 大小的空间。然后将内存指针返回给用户。
12) 判断是否为第一次调用malloc,若是主分配区,则需要进行一次初始化工作,分配一块大小为(chunk_size+128KB)align4KB大小的空间作为初始的heap。若已经初始化过了,主分配区则调用sbrk()增加heap空间,分主分配区则在topchunk中切割出一个chunk,使之满足分配需求,并将内存指针返回给用户。
总结一下:根据用户请求分配的内存的大小,ptmalloc有可能会在两个地方为用户分配内存空间。在第一次分配内存时,一般情况下只存在一个主分配区,但也有可能从父进程那里继承来了多个非主分配区,在这里主要讨论主分配区的情况,brk值等于start_brk,所以实际上heap大小为0,topchunk大小也是0。这时,如果不增加heap大小,就不能满足任何分配要求。所以,若用户的请求的内存大小小于mmap分配阈值,则ptmalloc会初始heap。然后在heap中分配空间给用户,以后的分配就基于这个heap进行。若第一次用户的请求就大于mmap分配阈值,则ptmalloc直接使用mmap()分配一块内存给用户,而heap也就没有被初始化,直到用户第一次请求小于mmap分配阈值的内存分配。第一次以后的分配就比较复杂了,简单说来,ptmalloc首先会查找fastbins,如果不能找到匹配的chunk,则查找smallbins。若还是不行,合并fastbins,把chunk加入unsortedbin,在unsortedbin中查找,若还是不行,把unsortedbin中的chunk全加入largebins中,并查找largebins。在fastbins和smallbins中的查找都需要精确匹配,而在largebins中查找时,则遵循“smallest-first,best-fit”的原则,不需要精确匹配。若以上方法都失败了,则ptmalloc会考虑使用topchunk。若topchunk也不能满足分配要求。而且所需chunk大小大于mmap分配阈值,则使用mmap进行分配。否则增加heap,增大topchunk。以满足分配要求。

ptmalloc内存回收

free()函数接受一个指向分配区域的指针作为参数,释放该指针所指向的chunk。而具体的释放方法则看该chunk所处的位置和该chunk的大小。free()函数的工作步骤如下:
1) free()函数同样首先需要获取分配区的锁,来保证线程安全。
2) 判断传入的指针是否为0,如果为0,则什么都不做,直接return。否则转下一步。
3) 判断所需释放的chunk是否为mmapedchunk,如果是,则调用munmap()释放mmapedchunk,解除内存空间映射,该该空间不再有效。如果开启了mmap分配阈值的动态调整机制,并且当前回收的chunk大小大于mmap分配阈值,将mmap分配阈值设置为该chunk的大小,将mmap收缩阈值设定为mmap分配阈值的2倍,释放完成,否则跳到下一步。
4) 判断chunk的大小和所处的位置,若chunk_size<=max_fast,并且chunk并不位于heap的顶部,也就是说并不与topchunk相邻,则转到下一步,否则跳到第6步。(因为与topchunk相邻的小chunk也和topchunk进行合并,所以这里不仅需要判断大小,还需要判断相邻情况)
5) 将chunk放到fastbins中,chunk放入到fastbins中时,并不修改该chunk使用状态位P。也不与相邻的chunk进行合并。只是放进去,如此而已。这一步做完之后释放便结束了,程序从free()函数中返回。
6) 判断前一个chunk是否处在使用中,如果前一个块也是空闲块,则合并。并转下一步。
7) 判断当前释放chunk的下一个块是否为topchunk,如果是,则转第9步,否则转下一步。
8) 判断下一个chunk是否处在使用中,如果下一个chunk也是空闲的,则合并,并将合并后的chunk放到unsortedbin中。注意,这里在合并的过程中,要更新chunk的大小,以反映合并后的chunk的大小。并转到第10步。
9) 如果执行到这一步,说明释放了一个与topchunk相邻的chunk。则无论它有多大,都将它与topchunk合并,并更新topchunk的大小等信息。转下一步。
10) 判断合并后的chunk的大小是否大于FASTBIN_CONSOLIDATION_THRESHOLD(默认64KB),如果是的话,则会触发进行fastbins的合并操作,fastbins中的chunk将被遍历,并与相邻的空闲chunk进行合并,合并后的chunk会被放到unsortedbin中。fastbins将变为空,操作完成之后转下一步。
11) 判断topchunk的大小是否大于mmap收缩阈值(默认为128KB),如果是的话,对于主分配区,则会试图归还topchunk中的一部分给操作系统。但是最先分配的128KB空间是不会归还的,ptmalloc会一直管理这部分内存,用于响应用户的分配请求;如果为非主分配区,会进行sub-heap收缩,将topchunk的一部分返回给操作系统,如果topchunk为整个sub-heap,会把整个sub-heap还回给操作系统。做完这一步之后,释放结束,从free()函数退出。可以看出,收缩堆的条件是当前free的chunk大小加上前后能合并chunk的大小大于64k,并且要topchunk的大小要达到mmap收缩阈值,才有可能收缩堆。

堆的ptmalloc机制的更多相关文章

  1. Erlang进程堆垃圾回收机制

    原文:Erlang进程堆垃圾回收机制 作者:http://blog.csdn.net/mycwq 每一个Erlang进程创建之后都会有自己的PCB,栈,私有堆.erlang不知道他创建的进程会用到哪种 ...

  2. Dance In Heap(一):浅析堆的申请释放及相应保护机制

    0×00 前面的话 在内存中,堆是一个很有趣的地方,因为它可以由用户去直接的进行分配与销毁,所以也产生了一些很有趣.奇思妙想的漏洞,像unlink漏洞.House系列漏洞等等.但是在学习的过程中,我们 ...

  3. Glibc堆管理机制基础

    最近正在学习linux下堆的管理机制,收集了书籍和网络上的资料,以自己的理解做了整理,做个记录.如果有什么不对的地方欢迎指出! Memory Allocator 常见的内存管理机制 dlmalloc: ...

  4. 堆溢出---glibc malloc

    成功从来没有捷径.如果你只关注CVE/NVD的动态以及google专家泄露的POC,那你只是一个脚本小子.能够自己写有效POC,那就证明你已经是一名安全专家了.今天我需要复习一下glibc中内存的相关 ...

  5. Linux堆内存管理深入分析(上)

    Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全   0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...

  6. C#基础知识系列二(值类型和引用类型、可空类型、堆和栈、装箱和拆箱)

    前言 之前对几个没什么理解,只是简单的用过可空类型,也是知道怎么用,至于为什么,还真不太清楚,通过整理本文章学到了很多知识,也许对于以后的各种代码优化都有好处. 本文的重点就是:值类型直接存储其值,引 ...

  7. C#堆栈和托管堆

    首先堆栈和堆(托管堆)都在进程的虚拟内存中.(在32位处理器上每个进程的虚拟内存为4GB) 堆栈stack 堆栈中存储值类型. 堆栈实际上是向下填充,即由高内存地址指向低内存地址填充. 堆栈的工作方式 ...

  8. [c++]堆和栈的区别

    堆和栈的区别一.预备知识—程序的内存分配一个由c/C++编译的程序占用的内存分为以下几个部分1.栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构 ...

  9. Linux堆内存管理深入分析

    (上半部) 作者:走位@阿里聚安全 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种.国内关于栈溢出的资料相对较 ...

随机推荐

  1. 【学】第一节 rt-thread的rt_kprintf功能的实现

    @2019-06-20 [主题] 拿到一个stm32f407新板,使用rt-thread源码中的bsp中的stm32f407-discovery工程,操作串口外设实现rt_kprintf功能 [问题] ...

  2. JAVA WEB初接触——简单的MVC架构

    1.概述 之前有过开发web的经验,因此我不会向无头苍蝇一般,心里还是有点数的

  3. python标准库之glob

    在python中,glob模块是用来查找匹配的文件的 在查找的条件中,需要用到Unix shell中的匹配规则: * : 匹配所所有 ? : 匹配一个字符 *.* : 匹配如:[hello.txt,c ...

  4. Java之ExceptionHelper工具类

    import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.e ...

  5. MongoDB——morphia

    禁止保存className:@Entity(noClassnameStored = true) https://stackoverflow.com/questions/17719018/a-field ...

  6. this 的用法 为原始类型扩展方法

    namespace Demo { public static class Extends { // string类型扩展ToJson方法 public static object ToJson(thi ...

  7. pdf缩略图生成上传解决方案

    前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践. ...

  8. 内存拷贝函数 memcpy

    windows下实现: void* __cdecl memcpy(void* dst,const void* src,size_t count) { void*ret=dst; #if defined ...

  9. Jmeter -- 对并发认识的误区

    1. 误区 如下图所示,并发数设置为1000,启动时间设置1s,就是每秒发起1000个请求(错误) 上述的设置,表示在1s内启动1000个线程,之后,jmeter便以最大限度的1000个并发进行压测, ...

  10. [BZOJ2730]:[HNOI2012]矿场搭建(塔尖)

    题目传送门 题目描述 煤矿工地可以看成是由隧道连接挖煤点组成的无向图.为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处.于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个 ...