dlmalloc 2.8.6 源码详解(6)

本文章由vector03原创, 转载请注明出处.

邮箱地址: [email protected], 欢迎来信讨论.

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,
代码如下,

时间: 2024-11-18 17:08:14

dlmalloc 2.8.6 源码详解(6)的相关文章

dlmalloc 2.8.6 源码详解(7)

本文章由vector03原创, 转载请注明出处. 邮箱地址: [email protected], 欢迎来信讨论. 4. 释放与实现 释放过程相对分配就简单多了, 基本着重在chunk合并, top裁剪, segment释放上. dlmalloc中合并是减少外部碎片最有效的方法了. 4.1 dlfree 释放的主要过程就是根据用户传入的payload, 找到chunk指针, 然后分别检查前一个和后一个chunk是否可以合并. 这里唯一需要注意的就是与dv和top这些特殊chunk的交互. 基本流

dlmalloc 2.8.6 源码详解(5)

本文章由vector03原创, 转载请注明出处. 邮箱地址: [email protected], 欢迎来信讨论. 3. 分配及实现 本章节介绍dlmalloc的分配算法和实现.由于存在多mspace的情况, dlmalloc使用了两套API.一套对应默认的mspace,以dl前缀开头,如dlmalloc, dlrealloc等.如果创建了自定义的mspace,则使用mspace开头的API,如mspace_malloc, mspace_realloc等.但两套API在基础算法上是一致的.我们就

Android编程之Fragment动画加载方法源码详解

上次谈到了Fragment动画加载的异常问题,今天再聊聊它的动画加载loadAnimation的实现源代码: Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { 接下来具体看一下里面的源码部分,我将一部分一部分的讲解,首先是: Animation animObj = fragment.onCreateAnimation(transit, enter, fragm

Java concurrent AQS 源码详解

一.引言 AQS(同步阻塞队列)是concurrent包下锁机制实现的基础,相信大家在读完本篇博客后会对AQS框架有一个较为清晰的认识 这篇博客主要针对AbstractQueuedSynchronizer的源码进行分析,大致分为三个部分: 静态内部类Node的解析 重要常量以及字段的解析 重要方法的源码详解. 所有的分析仅基于个人的理解,若有不正之处,请谅解和批评指正,不胜感激!!! 二.Node解析 AQS在内部维护了一个同步阻塞队列,下面简称sync queue,该队列的元素即静态内部类No

深入Java基础(四)--哈希表(1)HashMap应用及源码详解

继续深入Java基础系列.今天是研究下哈希表,毕竟我们很多应用层的查找存储框架都是哈希作为它的根数据结构进行封装的嘛. 本系列: (1)深入Java基础(一)--基本数据类型及其包装类 (2)深入Java基础(二)--字符串家族 (3)深入Java基础(三)–集合(1)集合父类以及父接口源码及理解 (4)深入Java基础(三)–集合(2)ArrayList和其继承树源码解析以及其注意事项 文章结构:(1)哈希概述及HashMap应用:(2)HashMap源码分析:(3)再次总结关键点 一.哈希概

Spring IOC源码详解之容器依赖注入

Spring IOC源码详解之容器依赖注入 上一篇博客中介绍了IOC容器的初始化,通过源码分析大致了解了IOC容器初始化的一些知识,先简单回顾下上篇的内容 载入bean定义文件的过程,这个过程是通过BeanDefinitionReader来完成的,其中通过 loadBeanDefinition()来对定义文件进行解析和根据Spring定义的bean规则进行处理 - 事实上和Spring定义的bean规则相关的处理是在BeanDefinitionParserDelegate中完成的,完成这个处理需

Spring IOC源码详解之容器初始化

Spring IOC源码详解之容器初始化 上篇介绍了Spring IOC的大致体系类图,先来看一段简短的代码,使用IOC比较典型的代码 ClassPathResource res = new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDe

IntentService源码详解

IntentService可以做什么: 如果你有一个任务,分成n个子任务,需要它们按照顺序完成.如果需要放到一个服务中完成,那么IntentService就会使最好的选择. IntentService是什么: IntentService是一个Service(看起来像废话,但是我第一眼看到这个名字,首先注意的是Intent啊.),所以如果自定义一个IntentService的话,一定要在AndroidManifest.xml里面声明. 从上面的"可以做什么"我们大概可以猜测一下Inten

Android View 事件分发机制源码详解(View篇)

前言 在Android View 事件分发机制源码详解(ViewGroup篇)一文中,主要对ViewGroup#dispatchTouchEvent的源码做了相应的解析,其中说到在ViewGroup把事件传递给子View的时候,会调用子View的dispatchTouchEvent,这时分两种情况,如果子View也是一个ViewGroup那么再执行同样的流程继续把事件分发下去,即调用ViewGroup#dispatchTouchEvent:如果子View只是单纯的一个View,那么调用的是Vie