dlmalloc 2.8.6 源码详解(6)

本文章由vector03原创, 转载至[vector03](https://blog.csdn.net/vector03/article/details/41205355).

3.4 sys_alloc

sys_alloc是dlmalloc中向系统获取内存的主要接口. 由于涉及到mmap, top-most segment, top chunk的交互, 相对要更复杂. 我们同样先介绍主要分配算法, 再详细分析子函数.

3.4.1 核心算法

基本上sys_alloc分为四个步骤,

1. 首先检查请求大小nb是否超出mmap_threshold的阈值. 如果是, 则放弃由分配器管理, 直接在mmap区开辟, 原因前面说过, 不再赘述.

2. 根据mspace设定及当前top space的使用情况, 向系统申请一块适当的内存.

Dlmalloc按照下面的顺序由主到次开辟,

第一, 如果允许MORECORE, 则优先通过MORECORE开辟连续内存空间.

连续空间开辟又分为如下几种情况,

若当前mspace处于诞生阶段, 则直接开辟nb + SYS_ALLOC_PADDING大小的空间.

若当前mspace已存在top, 则部分空间可利用top, 剩余nb – m->topsize + SYS_ALLOC_PADDING大小的空间向系统申请.

若MORECORE返回成功, 但空间不连续, 则会尝试扩展空间esize大小以满足分配需求. 如果空间扩展失败, 则反向MORECORE将之前申请的空间归还给系统.

第二, 如果上一步申请失败, 或不允许MORECORE, 则通过MMAP申请. 注意, 这里的MMAP同sys alloc步骤1的mmap是两码事. 这一步申请的结果是要归入mspace空间的.

第三, 倘若前两步都失败, 且允许非连续(noncontiguous)MORECORE, 则尝试直接在system heap上分配非连续空间. 这有些类似第一步中的扩展空间, 区别是此时已经明确top space不连续, 直接申请目标大小.

3. 根据申请成功的地址与原top space的关系, 对连续空间合并. 如果不能合并, 则新开区段. 申请的地址和大小保存在tbase和tsize临时变量里.

其中, 区段合并又分为两种. 若tbase与top-most区段末尾相毗邻, 则从后面合并. 这种情况适用于大部分MORECORE以及小部分MMAP申请到的空间.

若tbase与top-most区段开始相毗邻, 则从前面合并. 这种情况出现的比较少, 多在MMAP时产生, MORECORE虽然也可能出现该情况, 相对就更少.

4. 最后从已扩展的top space中划分chunk返回给用户. 这样就完成了sys_alloc的全部流程.

详细代码注释如下,

这个代码基本上就是本小节开始时介绍的流程, 相信看懂了前面的算法说明, 这里自然没有什么难度. 需要说明的仅有两点,

一个就是在Line4064和Line4065出现的判断, 先对nb做padding计算得到asize, 再判断其是否小于等于nb. 这里就是溢出检测, 对于两个无符号整数的加法, 这是比较便捷的检验方法. 类似的代码在dlmalloc中到处都是.

另一个参考Line4108, 这里同样是溢出检测. 因为MORECORE的入参在dlmalloc中被认为是有符号的. 而HALF_MAX_SIZE_T是size_t的一半, 以此来判断ssize是否溢出.

3.4.2 mmap_alloc

当nb大于mmap_threshold时, 会调用该函数直接进行mmap分配. 与sys_alloc通过其他途径申请的区别在于, dlmalloc对这类空间倾向于不长期持有, 也不纳入任何分箱或区段中. 可以认为它们是脱离dlmalloc管理的孤立内存区域.

既然是孤立内存, 首尾就不会有毗邻的chunk, 但直接mmap出来的payload地址未必是对齐的, 因此在对齐后会产生内部碎片. dlmalloc就将这些碎片伪装成一个chunk. 这样, 当用户释放这片内存时, 可以根据记录在prev_foot中的size信息找到当初mmap出来的首地址.

上图中, mmap分配的原始地址是mm, 经过对齐后的地址是p. dlmalloc将前面的对齐部分伪装成一个free chunk, 长度记录在p->prev_foot中. 当释放时, 就可以根据payload指针重新计算出mm的地址. 在结尾, 有长度为MMAP_FOOT_PAD的一段区域, 用来放置fake next chunk. 也就是保存magic以及fencepost.

代码注释如下,

3.4.3 prepend_alloc

在3.4.1小节中介绍了从系统申请的扩展内存会根据其首地址和旧区段之间的位置关系做合并. 倘若append到区段后面, 申请内存是比较简单的, 因为扩展地址会直接补充到top中, 只需切割top即可. 但如果prepend到前面情况就相对复杂了, 因为从原区段base到top之间的情况不明, 所以必须分情况讨论. 而prepend_alloc函数就是为此而写的.

该函数会在一开始将分配请求从扩展空间中切割出来, 剩余工作就是根据不同情况对remainder做相应处理,

1. 如果旧区段base与top是同一个地址, 直接移动top指针, 将remainder吸收到top中.

2. 如果旧区段base与dv是同一地址, 则扩充dv的范围.

3. 若旧区段开始是普通的free chunk, 则移动oldfirst指针, 将remainder和free chunk合并.

4. 若旧区段开始是inused chunk, 则将remainder插入回分箱.

代码注释如下,


3.4.4 add_segment

对于无法合并的扩展内存区域, dlmalloc最后会将它们作为新的segment插入.

创建新segment按照如下步骤进行,

1. 首先, 根据top, 查找到当前top-most区段, 并且定位出在其结尾的隐藏chunk.

2. 将top重新初始化为新的segment的基址.

3. 将mstate中保存的旧top-most区段信息push到旧区段的隐藏chunk里. 并将新区段信息记录在mstate中.

4. 旧区段末尾写入一连串fenceposts.

5. 若旧区段剩余的top可用, 则将旧top重新插入分箱系统中.

源码注释如下,

Line4016是top初始化函数, 该函数基本只是简单的信息记录, 并在末尾伪装隐藏chunk, 代码如下,

dlmalloc是目前一个十分流行的内存分配器,其由Doug Lea(主页为http://gee.cs.oswego.edu/)从1987年开始编写,到目前为止,最新版本为2.8.3(可以从ftp://g.oswego.edu/pub/misc/malloc.c获取),由于其高效率等特点被广泛的使用(比如一些linux系统等用的就是dlmalloc或其变形,比如ptmalloc,主页为http://www.malloc.de/en/index.html)和研究(各位可以搜索关键字“GCspy”)。 dlmalloc的实现只有一个源文件(还有一个头文件),大概5000行,其内注释占了大量篇幅,由于有这么多注释存在的情况下,表面上看上去很容易懂,的确如此,在不追求细节的情况,对其大致思想的确很容易了解(没错,就只是了解而已),但是dlmalloc作为一个高品质的佳作,实现上使用了非常多的技巧,在实现细节上不花费一定的精力是没有办法深入理解其为什么这么做,这么做的好处在哪,只有当真正读懂后回味起来才发现它是如此美妙。 lenky0401个人博客将陆续推出对dlmalloc的解析(针对Doug Lea Malloc的最新版Version 2.8.3,未做说明的情况下以32位平台,8字节对齐作为假定平台环境设置考虑),由于个人水平有限,因此也不能完全保证对dlmalloc的所有理解都准备无误, 但是所有内容均出自个人的理解而并非存心妄自揣测来愚人耳目,所以如果读者发现其中有什么错误,请勿见怪,如果可以则请来信告之,并欢迎来信讨论(lenky0401@163.com)。 这一系列文章是lenky0401在看完dlmalloc的大部分代码后的再总结,不能保证对dlmalloc的整体完全把握,贴出这些只是希望可以提前收到对此有研究的网友的指点,以便在最后对这一系列文章整理而形成的PDF文档中错误能少一些。至于对于现在贴出来的内容中包含的错误给大家造成的不便提前说声抱歉。:) 描述的内容不会包含dlmalloc全部代码,但会将这其中涉及到的一些技巧尽量讲出,我相信对dlmalloc源代码不感兴趣的朋友也可以学到这些独立的技巧而使用在自己的编程实践中。:) 最后,转载请保留本博客地址连接[http://lenky0401.cublog.cn],谢谢。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值