内存管理(二)ptmalloc的分配回收…

来源:互联网 发布:seo工作难点跟重点 编辑:程序博客网 时间:2024/05/21 19:31

 

1. 本文将讨论:

2. ptmalloc的分配和释放在系统里的操作

3. ptmalloc的初始化

4. 子线程的malloc和free

5. ptmalloc的内存暴增以及解决思路

6. 内存池的优缺点

 

 

 

 

 

ptmalloc的分配和释放在系统里的操作

 

本文以32系统为例。

 

Glibc分配算法如下:
小于等于 64 字节:用 pool 算法分配。
64 到 512 字节之间:在最佳匹配算法分配和 pool 算法分配中取一种合适的。
大于等于 512 字节:用最佳匹配算法分配。
大于等于 mmap 分配阈值(默认值 128KB): 根据设置的 mmap 的分配策略进行分配,
如果没有开启 mmap 分配阈值的动态调整机制,大于等于 128KB 就直接调用 mmap 

 

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,那么就需要操作topchunk来进行分配了。判断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 大小的空间。然后将内存指针返回给用户。判断是否为第一次调malloc若是主分配区,则需要进行一次初始化工作,分配一块大小为(chunk_size 128KB) align 4K大小的空间作为初始的heap。若已经初始化过了,主分配区则调用sbrk()增加heap空间分主分配区则在top chunk中切割出一个chunk,使之满足分配需求,并将内存指针返回给用户。

 

一张图概括malloc过程:

内存管理(二)ptmalloc的分配回收、内存暴增解决思路,内存池讨论

ptmalloc回收算法描述:

    free()函数接受一个指向分配区域的指针作为参数,释放该指针所指向的chunk。而具体的释放方法则看该chunk所处的位置和该chunk的大小。工作步骤如下: 

1. free()函数同样首先需要获取分配区的锁,来保证线程安全。

2. 判断传入的指针是否为0,如果为0,则什么都不做,直接 return。否则转下一步。 

3. 判断所需释放的 chunk 是否为 mmaped chunk,如果是,则调用 munmap()释放
mmaped chunk,解除内存空间映射,该该空间不再有效。如果开启了 mmap 分配
阈值的动态调整机制,并且当前回收的 hunk大小大于mmap分配阈值,将map分配阈值设置为该chunk的大小,将mmap收缩阈值设定为mmap分配阈值的2倍,释放完成,否则跳到下一步 

4. 判断chunk的大小和所处的位置,若 chunk_size <= max_fast,并且chunk并不位于heap 的顶部,也就是说并不与top chunk相邻,则转到下一步,否则跳到第6步。(因为与top chunk相邻的小chunk也和top chunk进行合并,所以这里不仅需要判断大小,还需要判断相邻情况) 

5. chunk放到 fast bins 中,chunk放入到fast bins中时,并不修改该chunk使用状态位 P。也不与相邻的chunk进行合并。只是放进去,如此而已。这一步做完之后释放便结束了,程序从free()函数中返回。

6. 判断前一个chunk是否处在使用中,如果前一个块也是空闲块,则合并。并转下一步。 

7. 判断当前释放chunk的下一个块是否为top chunk,如果是,则转第9步,否则转下一步。 

8. 判断下一个chunk是否处在使用中,如果下一个chunk也是空闲的,则合并,并将合并后的chunk放到unsorted bin中。注意,这里在合并的过程中,要更新chunk的大小,以反映合并后的chunk的大小。并转到第10 

9. 如果执行到这一步,说明释放了一个与top chunk相邻的chunk。则无论它有多大,
都将它与top chunk合并,并更新top chunk的大小等信息。转下一步。 

10. 判断合并后的chunk的大小是否大于FASTBIN_CONSOLIDATION_THRESHOLD(默认
64KB),如果是的话,则会触发进行fast bins的合并操作,fast bins中的chunk将被遍历,并与相邻的空闲chunk进行合并,合并后的chunk会被放到unsorted binfast bins将变为空,操作完成之后转下一步。 

11. 判断 top chunk 的大小是否大于mmap收缩阈值(默认为 128KB),如果是的话, 对于主分配区,则会试图归还top chunk中的一部分给操作系统。但是最先分配的128KB 空间是不会归还的,ptmalloc会一直管理这部分内存,用于响应用户的分配请求;如果为非主分配区,会进行sub-heap 收缩,将top chunk的一部分返回给操作系统,如果top chunk为整个sub-heap,会把整个sub-heap还回给操作系统。做完这一步之后,释放结束,free()函数退出。可以看出,收缩堆的条件是当前freechunk大小加上前后能合并chunk的大小大于64k,并且要top chunk的大小要达到mmap收缩阈值,才有可能收缩堆。 

 

 

 

一张图概括free过程:


内存管理(二)ptmalloc的分配回收、内存暴增解决思路,内存池讨论


ptmalloc的初始化:

 

    ptmalloc的初始化发生在进程的第一个内存分配请求。但是ptmalloc的初始化一般都在用户第一次调用malloc之前。因为操作系统和glibc库为进程的初始化做了不少工作,在用户分配内存之前,glibc已经分配了多次内存。

在ptmalloc中的malloc函数的实际接口是public_mALLOc(),这个函数将执行一个__malloc_hook()函数。在进程初始化的时候__malloc_hook指向的函数为malloc_hook_ini().

 

 

简单来说,ptmalloc的初始化将调用一个函数,函数里会执行创建ptmalloc的函数。这个函数首先检查全局变量,查看是否已经初始化,接着为多线程版本的ptmalloc的pthread做准备。接着初始化全局锁,设置当前进程在fork时处理锁的回调函数,最后从环境变量读取参数值。

 


子线程里的mallocfree


当某一线程需要调用malloc分配内存空间的时候,该线程将先查看线程私有变量里是否已经存在分配区。如果存在,尝试加锁。加锁有可能成功也有可能失败(分配区有很大可能是若干个子线程公用一个分配区)。如果成功,则使用该分配区分配内存。如果失败,则搜索记录分配区的循环链表师徒获得空闲的分配区。如果找到,则分配内存。如果失败,则新创建一个分配区。把该分配区加到循环链表里并且加锁,最后分配内存。(如果线程过多,并且高并发,将导致内存暴增)在回收操作中,如果该分配区被加锁,只能等待解锁了再free。

 

 

ptmalloc的内存暴增以及解决思路

 

在上一篇博文,提到了ptmalloc尽量避免为生命周期长的内存块开辟内存。在此将进行讨论。

1.长时间没有释放内存(对于生命周期长的内存块使用过多),全局内存池回不定期的分配内存,可能下次分配的内存是在top chunk分配的。如果长时间不能得到释放,那么brk会被不断修改,导致top chunk升到了一个更高的虚拟地址空间。从而使ptmalloc中的内存块更多但是又无法返回给操作系统。

2.如果线程过多,在高并发环境下,频繁的分配和释放,会导致锁的竞争更为激烈。同时又会不断创建新的非主分配区。这也将导致内存暴增。

 

解决思路如下:

1.对于长生命周期的内存块,可以使用mmap系统调用来申请。

2.多线程的话就尽量减少线程使用。

 

 

内存池的优缺点

 

优点:

1.应用程序可以更简单的管理内存。

2.内存分配的回收更加快。

3.可以以预先处理分配失败的情况。

4.容易实现

 

缺点:

1.只适用于操作可以分阶段的程序

2.不能和第三方库很好的合作

3.如果程序结构发生变化,则不得不修改内存池,这将导致内存管理的重写

4.如果丢失了指针,就会起到管理的反效果

0 0
原创粉丝点击