内存分配器dlmalloc 2.8.3源码浅析

2023-05-16

目    录

1. 本文档介绍 1

2.边界标记法 2

3. 分箱式内存管理 6

4. 核心结构体malloc_state 13

5. 内存分配相关函数 16

5.1 函数dlmalloc 16

5.2 函数tmalloc_small 25

5.3 函数tmalloc_large 27

5.4 函数sys_alloc 32

5.5 函数mmap_alloc 39

6. 内存回收相关函数 42

6.1 函数dlfree 42

6.2 函数sys_trim 47

7. 本文档声明 50

1. 本文档介绍

dlmalloc是目前一个十分流行的内存分配器,其由Doug Lea1987年开始编写,到目前为止,最新版本为2.8.3,由于其高效率等特点被广泛的使用和研究(很多linux系统等用的就是dlmalloc或其变形,比如ptmalloc)。

dlmalloc的实现只有一个源文件(还有一个头文件),大概5000行,其内注释占了大量篇幅,由于有这么多注释存在的情况下,表面上看上去很容易懂,的确如此,在不追求细节的情况,对其大致思想的确很容易了解(没错,就只是了解而已),但是dlmalloc作为一个高品质的佳作,实现上使用了非常多的技巧,在实现细节上不花费一定的精力是没有办法深入理解其为什么这么做,这么做的好处在哪,只有当真正读懂后回味起来才发现它是如此美妙。

lenky0401个人博客将陆续推出关于dlmalloc源码(针对Doug Lea Malloc的最新版Version 2.8.3)的初步解析文章,由于lenky0401水平有限,因此也不能完全保证对dlmalloc的所有理解都准备无误,但是所有内容均出自个人的理解而并非存心妄自揣测来愚人耳目,所以如果读者发现其中有什么错误,请勿见怪,如果可以则请来信告之,并欢迎来信讨论(lenky0401@163.com)。

本文档描述的内容不会包含dlmalloc全部代码,但会将这其中涉及到的一些技巧尽量讲出,我相信对dlmalloc源代码不感兴趣的朋友也可以学到这些独立的技巧而使用在自己的编程实践中。另外,本文档对dlmalloc的描述过程中忽略了很多细节(主要是环境和自定义设置)的反复核认,一致约定在未做说明的情况下就以32位平台、8字节对齐、Ubuntu-8.10操作系统作为参考环境设置考虑。

 

2.边界标记法

dlmalloc采用所谓的边界标记法将内存划分成很多块,从而对内存的分配与回收进行管理。在dlmalloc的实现源码中定义了两种结构体malloc_chunk和malloc_tree_chunk来描述这些块,小于256字节的chunk块由结构体malloc_chunk来描述,大于256字节的chunk块由结构体malloc_tree_chunk来管理。

结构体malloc_chunk和malloc_tree_chunk的定义如下:

struct malloc_chunk {

  size_t               prev_foot;  /* Size of previous chunk (if free).  */

  size_t               head;       /* Size and inuse bits. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */

  struct malloc_chunk* bk;

};

typedef struct malloc_chunk  mchunk;

typedef struct malloc_chunk* mchunkptr;

typedef struct malloc_chunk* sbinptr;  /* The type of bins of chunks */

 

struct malloc_tree_chunk {

  /* The first four fields must be compatible with malloc_chunk */

  size_t                    prev_foot;

  size_t                    head;

  struct malloc_tree_chunk* fd;

  struct malloc_tree_chunk* bk;

 

  struct malloc_tree_chunk* child[2];

  struct malloc_tree_chunk* parent;

  bindex_t                  index;

};

 

从它们的定义中可以看到结构体malloc_tree_chunk除了比malloc_chunk多三个字段以外,前四个字段和malloc_chunk完全一样。我们先来看看只考虑使用结构体malloc_chunk管理(结构体malloc_tree_chunk的管理类似)内存的情况,如下图所示:

图1 边界标记法划分内存

 

可以有多个连续的被使用中chunk块,但是不会有多个连续的空闲chunk块,因为连续的多个空闲chunk块可以合并成一个大的空闲chunk块。

按照边界标记法,结构体malloc_chunk通过字段head和prev_foot将内存分割成很多块,从图1中①所示。字段head记录与本块相关的信息,这包括本chunk块大小,本块是否在使用中,前一chunk块是否在使用中。head一个字段就能存储这么多信息是因为dlmalloc在分割内存的时候总是以地址对齐(默认是8字节,可以自由设置,但是8字节是最小值并且设置的值必须是2为底的幂函数值,即是alignment = 2^n,n为整数且n>=3)的方式来进行的,所以用head来存储本chunk块大小字节数的话,其末3bit位总是0,因此这三位可以用来存储其它信息,比如:

以第0位作为标志位,标记前一chunk块是否在使用中,为1表示使用,为0表示空闲。

以第1位作为标志位,标记本chunk块是否在使用中,为1表示使用,为0表示空闲。

  我们来看看它们的各自相关判断代码:

#define SIZE_T_ONE          ((size_t)1)

#define SIZE_T_TWO          ((size_t)2)

#define PINUSE_BIT          (SIZE_T_ONE)

#define CINUSE_BIT          (SIZE_T_TWO)

#define cinuse(p)           ((p)->head & CINUSE_BIT)

#define pinuse(p)           ((p)->head & PINUSE_BIT)

prev_foot字段虽然在当前chunk块结构体内,记录的却是前一个邻接chunk块的信息(有特例存在,马上会讲到),这样做的好处就是我们通过本块chunk结构体就可以直接获取到前一chunk块的信息,从而方便做进一步的处理操作。相对的,当前chunk块的foot信息就存在于下一个邻接chunk块的结构体内。

字段prev_foot记录的什么信息呢?有两种情况:1如果前一个邻接chunk块空闲,那么当前chunk块结构体内的prev_foot字段记录的是前一个邻接chunk块的大小。此时我们可以利用如下代码:

#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) ))

来获得前一个邻接chunk块的malloc_chunk结构体指针。插播一句,利用字段head可以获取下一个邻接chunk块的malloc_chunk结构体指针,如下:

#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~INUSE_BITS)))

2)如果前一个邻接chunk块正在被使用中,那么当前chunk块结构体内的prev_foot字段记录的是它们所在的结构体malloc_state变量信息,具体来说是这样:

/* Set foot of inuse chunk to be xor of mstate and seed */

#define mark_inuse_foot(M,p,s)\

  (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic))

结构体malloc_state在后面几节会讲到,总之记录结构体malloc_state变量信息用于做额外的安全检测,因此同时也会额外降低dlmalloc性能,所以只有当开启了这一功能时(即定义了FOOTERS宏)才有这种情况,默认是不开启的。

和字段head一样,dlmalloc也利用了prev_foot字段的最末bit位(第0位)来记录该chunk块是如何分配的,当该chunk块是直接通过函数mmap()从系统获取内存分配的时,该bit被置为1。虽然该chunk块前没有邻接实际的chunk块(因为它是通过函数mmap()独立分配的),但是此prev_foot字段记录的大小也不一定就是0,因为chunk块需要对齐,因此此chunk块的首地址不一定和mmap()返回的内存段首地址完全一致,未对齐的那部分空间就伪装成一个chunk块,即该chunk块的prev_foot字段记录这段未对齐部分内存的大小,这样再实际调用函数munmap()释放该chunk块时能获取到正确的映射地址。关于这段叙述的具体代码请参考函数mmap_alloc()。

指针fd、bk只有当该chunk块空闲时才存在,其作用是用于将对应的空闲chunk块加入到空闲chunk块链表中统一管理,而如果该chunk块被分配给应用程序使用,那么这两个指针也就没有用(该chunk块已经从空闲链中拆出)了,所以也当作应用程序的使用空间,而不至于浪费,图中②所示。chunk块地址和真正使用地址的相互转换如下所示,TWO_SIZE_T_SIZES是由结构体字段prev_foot和head所占用的空间(这两个字段在空闲chunk块和正在使用chunk块中都存在)。

/* conversion from malloc headers to user pointers, and back */

#define chunk2mem(p)        ((void*)((char*)(p)       + TWO_SIZE_T_SIZES))

#define mem2chunk(mem)      ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES))

  对于结构体malloc_tree_chunk,其实在内存分割上合结构体malloc_chunk完全一致,因为它们的前四个字段完全一样(事实上只有两个字段prev_foot和head起边界标记作用),其它的字段都是为了方便空闲chunk块的管理而已。

3. 分箱式内存管理

上一节提到对于大小在256字节以下的chunk块是通过malloc_chunk组织管理的,256字节以下的chunk块一共有256/8=32类,即大小为8字节(由于实际上申请的chunk块大小最小值为16,因此这个链表事实上为空)、16字节、24字节、32字节,……,256字节(该链表也为空),因此dlmalloc维护了32个双向环形链表(每个链表都具有链表头节点,加头节点的最大作用就是便于对链表内节点的统一处理,即简化编程),每一个链表内的各空闲chunk块的大小一致,因此当应用程序需要某个字节大小(这里的字节大小是考虑了chunk块头和对齐等所占空间了的,即如果应用如果程序调用函数malloc(8),那么到dlmalloc这应该比8大,这种更细节的疑问下面还有,并非我故意不表达,只是转述太多了,倒说不清我自己真正想说的了,阅读时请读者自己注意就好)的内存空间时直接在对应的链表内取就可以了(具体稍有不同,即如果对应链表内没有空闲可用chunk块,则还会查看下一个链表,举个例子:当应用程序申请32个字节,如果32字节大小的链表为空,那么dlmalloc还会在大小为40字节的链表内查找空闲chunk块。),这样既可以很好的满足应用程序的内存空间申请请求而又不会出现太多的内存碎片。我们可以用如下图来表示dlmalloc256字节以下的空闲chunk块组织方式(所谓的分箱机制)。

图2 分箱式内存管理

dlmalloc程序使用smallbins数组(后面我们就简单的称这种分箱为“smallbins分箱”)来记录这32个双向环形链表表头,该字段定义在结构体malloc_state内,在这里我们先不管malloc_state结构体而只关注smallbins这个字段,它的定义如下:

struct malloc_chunk {

  size_t               prev_foot;  /* Size of previous chunk (if free).  */

  size_t               head;       /* Size and inuse bits. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */

  struct malloc_chunk* bk;

};

mchunkptr  smallbins[(NSMALLBINS+1)*2];

其中,mchunkptr在上一小节已经提过,为“typedef struct malloc_chunk* mchunkptr;”,而宏NSMALLBINS为32,即是“#define NSMALLBINS (32U)”。因此smallbins为一个具有66malloc_chunk结构体指针元素的数组,为什么是66个呢?不是32就可以了么?这里Doug Lea使用了一个技巧,如果按照我们的常规想法,也许会申请32malloc_chunk结构体指针元素的数组,然后再给链表申请一个头节点(即32个),再让每个指针元素正确指向而形成32个具有头节点的链表。事实上,对于malloc_chunk类型的链表“头节点”,其内的prev_foot和head字段是没有任何实际作用的,因此这两个字段所占空间如果不合理使用的话那就是白白的浪费。我们再来看一看66malloc_chunk结构体指针元素的数组占了多少内存空间呢?结果为66*4=264字节。而32malloc_chunk类型的链表“头节点”需要多少内存呢?32*16=512,真的是512么?不是,刚才不是说了,prev_foot和head这两个字段是没有任何实际作用的,因此完全可以被重用(覆盖),因此实际需要内存为32*8=256264大于256(事实上前16个字节都被浪费掉了,如果考虑到8字节的chunk是不存在的,则浪费更多,……),那么这66malloc_chunk结构体指针元素数组所占内存空间就可以存储这32个头节点了,事实上Doug Lea也是这么做的。我们来看下于此相关的这一句代码:

#define smallbin_at(M, i)   ((sbinptr)((char*)&((M)->smallbins[(i)<<1])))

其中的sbinptr也是个malloc_chunk结构体指针类型(typedef struct malloc_chunk* sbinptr;),M表示前面提到的结构体malloc_state,各位仔细体会一下这句代码中的强制转换就会理解这种技巧了。下面给个更直观的图便于理解:


图3 链表头节点内存分布实际情况

对于大小在256字节以上的chunk块,dlmalloc同样也采用了所谓的分箱机制,不过由于大于256的数目有很多,因此这里的分箱不能够像对于0256这个有限区间的分箱来得简单。具体来说如下表:

字节范围

范围大小

箱号

[256, 384)

128

0

[384, 512)

128

1

[512, 768)

256

2

[768, 1024)

256

3

[1024, 1536)

512

4

[1536, 2048)

512

5

[2048, 3072)

1024

6

[3072, 4096)

1024

7

[4096, 6144)

2048

8

[6144, 8192)

2048

9

[8192, 12288)

4096

10

[12288, 16384)

4096

11

[16384, 24576)

8192

12

[24576, 32768)

8192

13

[32768, 49152)

16384

14

[49152, 65536)

16384

15

[65536, 98304)

32768

16

[98304, 131072)

32768

17

[131072, 196608)

65536

18

[196608, 262144)

65536

19

[262144, 393216)

131072

20

[393216, 524288)

131072

21

[524288, 786432)

262144

22

[786432, 1048576)

262144

23

[1048576, 1572864)

524288

24

[1572864, 2097152)

524288

25

[2097152, 3145728)

1048576

26

[4194304, 4194304)

1048576

27

[4194304, 6291456)

2097152

28

[6291456, 8388608)

2097152

29

[8388608, 12582912)

4194304

30

[12582912, 理论上无穷大)

理论上无穷大

31

这个表格里的数字是先用程序打印出来然后再拷贝过来的,先简单解释一下,第一列“字节范围”表示大小在该范围内chunk块被分配到改行对应的第三列“箱号”所指定的箱子内。第二列表示第一列“字节范围”的范围大小,可以看到它们很有规律,在后面我们会讲到这种规律性的作用,因此这里也就先给出来。另外,这种规律性也就是这样分箱的分法,相比前面smallbins的那种直接按8162432…这种确定值的分箱方法相对较为复杂,这里是将一个区间内的值分到一个箱子内,正因如此,对于箱内chunk块的管理也就不能再简单的使用双向链表而是使用树(实际上在具体实现上为Trie树,不过和课本上的那种常见的字典Trie树不一样,Doug Lea给出的注释称之为“bitwise digital trees (aka tries)”,我们就姑且先称这种树为dltree树)结构,当然这种树不是随意的,它具有它自身的最简单规则:①任何节点的左子树上所有节点值均小于它的右子树上所有节点值。②任何节点值和它的左(右)子树上所有节点值不存在确定排序关系。这是直白的描述,如果用更严谨的表述则为如下:

dltree树或者是一颗空树;或者是具有下列性质的二叉树:

1)若左子树和右子树都不空,则左子树上所有结点的值均小于右子树上所有结点的值;

2)左、右子树也分别为dltree树;

每一个箱子里的chunk块都被以dltree树结构组织起来,分了32个箱号,因此dlmalloc具有32dltree树,记录该32dltree树根节点的指针变量treebins(相对于前面的“smallbins分箱”,我们称这种为“treebins分箱”)也定义在结构体malloc_state内,如下:

tbinptr    treebins[NTREEBINS];

#define NTREEBINS (32U)

typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */

其中NTREEBINS为常量宏,类型tbinptr为malloc_tree_chunk结构体指针,数组的32个指针元素指向32个分箱内各个dltree树的根节点,因此通过结构体malloc_state类型变量很方便的找到各种chunk块大小范围的树结构。

图4分箱式内存管理

下面来讨论具体一棵dltree树的组织情况(就以0号箱管理的树为例),根据dltree树中左子树小于右子树的性质,字节[256, 384)范围被按如下规则划分给0号箱管理的dltree树组织:

根的左子树T1管理[256, 320),右子树T2管理[320, 384),即各管理64字节范围。

T1的左子树T3管理[256, 288),树T1的右子树T4管理[288, 320),即各管理32字节范围。树T2类似。

T3的左子树T7管理[256, 272),树T3的右子树T8管理[272, 288),即各管理16字节范围。其它树类似。

……

图4 dltree树组织结构

通过这种分法,任一号箱的字节范围被对应在一颗树上,在查找某一大小的空闲chunk时则按照这种规律来进行“快速”搜索,比如应用程序malloc( 278 ),则由于278[256, 320)范围内,因此先进入树T1,接着由于278[256, 288)范围内,因此由进入T3,接着进入T8,……。

之所以说这种搜索是“快速”的,因为这里的范围划为很巧妙,我们来看一下各字节范围用二进制表示的情况:

根节点T的左子树T1 [256, 320)为: [1 0000 0000 1 00xx xxxx]

而根节点T的右子树T2 [320, 384)为: [1 01xx xxxx 1 01xx xxxx]

其中的x表示为1或者0,可以看到T1T2的管理范围很好区分,即通过判断第6bit位是否为1即可,为1表示在右子树T2范围内,为0表示在左子树T1范围内。

再来看看树T1的左子树T3和右子树T4情况:

T3管理[256, 288)为:[1 0000 0000 1 000x xxxx]

T4管理[288, 320)为:[1 0010 0000 1 001x xxxx]

可以看到T3T4的管理范围也很好区分,即通过判断第5bit位是否为1即可,为1表示在右子树T4范围内,为0表示在左子树T3范围内。

其它的类似……。因此,我们在查找某字节在哪个范围内的树上只需要顺序的比较各bit位就可以了,对比Trie树,我们可以认为dltree是关键码只有01Trie

4. 核心结构体malloc_state

在讲解核心结构体malloc_state之前,先来看看另外一个结构体segment。前面内容介绍的chunk块是dlmalloc内比较细粒度的管理结构,比它们更大的内存块被称之为段(segment),其结构体以及相关定义如下:

struct malloc_segment {

  char*        base;             /* base address */

  size_t       size;             /* allocated size */

  struct malloc_segment* next;   /* ptr to next segment */

  flag_t       sflags;           /* mmap and extern flag */

};

#define is_mmapped_segment(S)  ((S)->sflags & IS_MMAPPED_BIT)

#define is_extern_segment(S)   ((S)->sflags & EXTERN_BIT)

typedef struct malloc_segment  msegment;

typedef struct malloc_segment* msegmentptr;

英文注释很清晰,字段base表示该segment段的起始地址,size表示该segment段的大小,sflags是一个标记字段(两个标记,一个用于标记该segment段是否为通过mmap申请,一个用于标记该segment段是否已经分配),而字段next用于指向下一个segment段,从而形成单链表结构。记录该单链表表头的变量同样定义在结构体malloc_state内,如下:

msegment seg;

这是个结构体变量,而不是结构体指针变量,这一点需要注意。刚才已经提到多个segment段是通过next形成单链表结构来组织管理的,dlmalloc也没有对它们做其它特别的索引处理,因此查找某一个segment段需要在该单链表内线性扫描查找,不过大多数情况下,segment段应该是非常少的,所以并不会造成多大的性能损失。

dlmalloc中对malloc_chunk、malloc_tree_chunk、malloc_segment给出了一个统一的管理结构体,那就是前面反复提到的结构体malloc_state,其具体定义如下:

struct malloc_state {

  binmap_t   smallmap;

  binmap_t   treemap;

  size_t     dvsize;

  size_t     topsize;

  char*      least_addr;

  mchunkptr  dv;

  mchunkptr  top;

  size_t     trim_check;

  size_t     magic;

  mchunkptr  smallbins[(NSMALLBINS+1)*2];

  tbinptr    treebins[NTREEBINS];

  size_t     footprint;

  size_t     max_footprint;

  flag_t     mflags;

#if USE_LOCKS

  MLOCK_T    mutex;     /* locate lock among fields that rarely change */

#endif /* USE_LOCKS */

  msegment   seg;

};

typedef struct malloc_state*    mstate;

该结构体中的一些字段在前面已经详细分析过了,比如smallbins、treebins和seg。其它几个字段的作用,现在也一一说明如下:

smallmap是一个位图,用于标记对应的smallbins链表是否为空,1表示非空,0表示空。

treemap也是一个位图,用于标记对应的treebins树是否为空,1表示非空,0表示空。

dv和dvsize指向一个chunk块(dvsize记录该chunk块大小),该chunk块具有一个特点就是:它是从最近的一次被用来服务小于256字节内存申请的chunk块中分裂出来的chunk块。比较拗口,简单点说就是为了更有效的利用程序的局部性原理,即对于应用程序的一连续小块内存申请请求,dlmalloc总是尽量从同一个chunk块获取空闲内存来满足这些请求,因此分配给应用程序的内存都是在挨得比较近的地址空间内,从局部性原理可以知道,这种内存分配方式在一定程度上可以提高应用程序的性能。当然,这种分配方式只是尽量,如果有其它更好的chunk块选择(比如刚好有某个chunk大小就是应用程序申请的内存大小)则会选择其它chunk块来分配内存,这在后面具体代码的分析过程中会看到。

top 和topsize也是指向一个chunk块(topsize记录该chunk块大小),该chunk块比较特殊,它不被任何分箱管理(即既不位于smallbins,也不位于treebins。),它位于当前活跃segment段(即最近被用来分配空间服务应用程序内存请求的)的最顶端,在最顶端的好处就是它可以自由伸缩(通过库函数sbrk()),因此大小可变。在segment段中间的那些chunk块大小一般不可变,但是有两种情况会变动,一是分配出去一部分内存用于满足应用程序申请,此时chunk块变小;二是,当应用程序释放内存(free())时,dlmalloc将检查该释放内存是否可与前后空闲内存合并,此时就可能导致chunk块变大。

字段least_addr用来记录可获取的最小内存地址,直白点说就是相当于一个边界点,用于做越界安全检查。

字段trim_check用来记录一个临界值。我们知道应用程序free内存空间时,该释放的内存空间并没有直接返还给系统而是被送回dlmalloc中进行管理。当释放的内存空间越来越多时,dlmalloc中管理的空闲chunk块也会变得越来越大(即由于多个连续空闲块合并的结果),对于一个segment段中间的空闲chunk块没有办法释放给系统(因为释放中间的空闲chunk块会将segment段切断),但是对于顶端(top)的chunk块则可以自由伸缩的,缩小顶端的chunk也就是将空闲内存真正的返还给系统。那什么时候需要缩小顶端的chunk块呢?字段trim_check就是记录的这个临界值。当顶端的chunk块大小大于字段trim_check记录的值时就要进行缩减操作了,具体情况在后面源码分析时再看。

其它几个字段不是我们主要关注的内容且比较简单,比如字段magic用于做确认检查;字段mflags用于标记一些属性,比如启用mmap、禁用连续分配,……;字段footprint记录从系统获得的字节数目;字段max_footprint记录从系统获得的最大字节数目;字段mutex用于多线程互斥锁等等,在此就不做过多介绍了。

dlmalloc定义了一个结构体malloc_state的全局变量,相关代码如下:

static struct malloc_state _gm_;

#define gm                 (&_gm_)

#define is_global(M)       ((M) == &_gm_)

#define is_initialized(M)  ((M)->top != 0)

变量_gm_是结构体malloc_state的静态全局变量,因此当使用dlmalloc的应用程序被加载运行时,变量_gm_自动初始化为全0,这对于dlmalloc是十分重要的(例如_gm_结构体字段seg也为全0);另外还有一个gm宏,其值被定义为_gm_的地址,因此可以当指针一样使用它。其它两个宏is_global和is_initialized很明朗,无需多说。

总结起来,可以看到dlmalloc利用静态全局变量_gm_管理着segment段,小块空闲chunk块、大块空闲chunk块以及其它相关信息,所有的内存分配和释放都是围绕着_gm_进行的。

5. 内存分配相关函数

本节主要对dlmalloc内存分配器的核心函数dlmalloc()以及相关函数进行讲解,函数dlmalloc用于服务应用程序的内存空间申请请求,因此也是我们平常使用得最多的两个函数(另外一个dlfree())之一。下面我们先来直接看源码并同时进行分析。(下面给出的源码都已标号,标号为源文件malloc-2.8.3.c内的实际行号,未标号的行都为我给出的分析注解内容。)

5.1 函数dlmalloc

4023. void* dlmalloc(size_t bytes) {

下面的英文注释给出了dlmalloc选择空闲内存来服务应用程序请求的分配策略:

对于小块内存请求(即小于256字节减去块头开销的内存申请请求):

1. 优先选择大小和请求大小刚好一致的空闲chunk块(即在请求大小对应的箱号内找到空闲chunk块),这么做的好处很明显,那就是这种分配不用分割chunk块就可以满足请求服务。如果大小刚好一致的空闲chunk块不存在(即在请求大小对应的箱号内为空)则选择大小比较一致的空闲chunk块,这种比较一致是指空闲chunk块大小和请求大小相差一个箱号(即相差8字节),如果在比请求大小对应的箱号大一的箱子内找到空闲chunk块也可以拿来分配以满足请求服务,否则进入下一步。

2. 如果dv chunk块(见上一篇介绍)足够大,则在该chunk块内分割出一部分内存以满足请求服务。否则的话,进入下一步。

3. 1步中查找空闲chunk块只在请求大小对应的和比其大一的两个箱号内查找,这一步就不做这些限制了,只要smallbins和treebins管理的chunk空闲链/树内有满足请求(即需要比请求字节数目大)的chunk空闲块存在(当然也是选择大小和请求字节数目最接近的chunk空闲块),则分割出一部分内存以满足请求服务,同时将剩余部分作为新的dv chunk。否则的话,进入下一步。

4. 如果top chunk块(见上一篇介绍)足够大,则在该chunk块内分割出一部分内存以满足请求服务。否则的话,进入下一步。

5. dlmalloc从系统获取内存空间。

对于大块内存请求(对于大块内存都是由treebins管理的):

1. treebins管理的空闲chunk块中找一个大小最接近请求字节数目的chunk块(假设为chunkA),如果chunkAdv chunk块更合适(即如果dv chunk块本身就太小而无法满足请求服务或者太大以至于chunkA的大小比dv chunk块大小更接近请求字节数目,我们应注意到dlmalloc总是选择从大小和当前请求字节数目最接近的chunk块中分配内存来服务请求,也就是所谓的最佳适应算法。这些常见内存分配算法比如首次适应算法、循环首次适应算法、最差适应算法等在操作系统原理课程上应该有讲解过。)则使用它。否则的话,进入下一步。

2. 如果dv chunk块足够大则使用它以满足请求服务。否则的话,进入下一步。

3. 如果top chunk块足够大则使用它以满足请求服务。否则的话,进入下一步。

4. 如果请求字节数目超过了预设的mmap调用界限则直接mmap一块内存来满足请求服务。否则的话,进入下一步。

5. dlmalloc从系统获取内存空间。

上面这些步骤的跳转使用了goto语法,以保证所有执行路径的最后都能够执行宏语句POSTACTION。

4024.   /*

4025.      Basic algorithm:

4026.      If a small request (< 256 bytes minus per-chunk overhead):

4027.        1. If one exists, use a remainderless chunk in associated smallbin.

4028.           (Remainderless means that there are too few excess bytes to

4029.           represent as a chunk.)

4030.        2. If it is big enough, use the dv chunk, which is normally the

4031.           chunk adjacent to the one used for the most recent small request.

4032.        3. If one exists, split the smallest available chunk in a bin,

4033.           saving remainder in dv.

4034.        4. If it is big enough, use the top chunk.

4035.        5. If available, get memory from system and use it

4036.      Otherwise, for a large request:

4037.        1. Find the smallest available binned chunk that fits, and use it

4038.           if it is better fitting than dv chunk, splitting if necessary.

4039.        2. If better fitting than any binned chunk, use the dv chunk.

4040.        3. If it is big enough, use the top chunk.

4041.        4. If request size >= mmap threshold, try to directly mmap this chunk.

4042.        5. If available, get memory from system and use it

4043. 

4044.      The ugly goto's here ensure that postaction occurs along all paths.

4045.   */

PREACTION是一个宏,作用和后面的POSTACTION宏相对,用于进行互斥锁定。我们知道“互斥锁是用来保证一段时间内只有一个线程在执行一段代码”,而下面的这段代码在多线程情形下恰好需要这一保证,因此有这一宏的存在。具体来看:

#define PREACTION(M)  ((GLOBALLY_INITIALIZE() || use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0)

#define use_lock(M)           ((M)->mflags &   USE_LOCK_BIT)

#define ACQUIRE_LOCK(l)      pthread_mutex_lock(l)

未初始化(GLOBALLY_INITIALIZE())或者的确需要锁定(use_lock(M))则调用函数pthread_mutex_lock()(Unix/Linux环境,Windows环境类似,以下同)对互斥锁上锁。

4046. 

4047.   if (!PREACTION(gm)) {

4048.     void* mem;

4049.     size_t nb;

MAX_SMALL_REQUEST被定义为小块内存请求的最大值:

#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD)

根据对齐和其它(比如是否具有foot)等设置,该值具体数据稍有不同(比如为247或248等),但都比256小,在这里我们简单的认定它为256

4050.     if (bytes <= MAX_SMALL_REQUEST) {

4051.       bindex_t idx;

4052.       binmap_t smallbits;

对请求内存数目进行对齐处理,另外如果请求内存数目小于最小请求值(MIN_REQUEST)则取最小值chunk块大小(16字节)。

4053.       nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);

small_index是一个宏,用于根据请求字节数目计算对应大小的空闲chunk块所在的箱号:

#define SMALLBIN_SHIFT    (3U)

#define small_index(s)      ((s)  >> SMALLBIN_SHIFT)

4054.       idx = small_index(nb);

将箱号的位图标记移到最右边位。

4055.       smallbits = gm->smallmap >> idx;

4056. 

注意0x3U的二进制为0000 0011(只给出了低8位),因此如果它和smallbits进行位与为真,则smallbits的低2位有三种情况:

第一种情况:1 1

第二种情况:0 1

第三种情况:1 0

1表示该对应箱号内存在对应大小的空闲chunk块,前两种情况都表示存在大小刚好和请求大小一致的空闲chunk块;而第三种情况表示不存在大小刚好和请求大小一致的空闲chunk块,但是存在大小和请求大小只相差一个箱号的空闲chunk块。

4057.       if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */

4058.         mchunkptr b, p;

在第三种情况(1 0)时需要将箱号(idx)加1,下面这行代码将这三种情况都无区别的处理了,即在第一二种情况时箱号(idx)并不会加1,很精巧的代码!

4059.         idx += ~smallbits & 1;       /* Uses next bin if idx empty */

找到对应箱号的chunk块链头节点:

#define smallbin_at(M, i)   ((sbinptr)((char*)&((M)->smallbins[(i)<<1])))

4060.         b = smallbin_at(gm, idx);

4061.         p = b->fd;

4062.         assert(chunksize(p) == small_index2size(idx));

将第一块空闲chunk块(假设为chunkA)从链表中拆出,用于分配。环形双向链表的头节点拆除就不多讲了。

4063.         unlink_first_small_chunk(gm, b, p, idx);

本块chunkA被用于分配使用,因此需要修改边界标记:

宏small_index2size用于根据箱号计算对应箱内空闲chunk块字节数目:

#define small_index2size(i) ((i)  << SMALLBIN_SHIFT)

#define SMALLBIN_SHIFT    (3U)

宏set_inuse_and_pinuse根据设置(FOOTERS是否存在,即是否有footers),下面给出有footers的情况(没有footers的情况相对简单)下set_inuse_and_pinuse宏的定义:

#define set_inuse_and_pinuse(M,p,s)\

  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\

  (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\

 mark_inuse_foot(M,p,s))

该宏第二行用于标记本chunkA在使用中(即将被分配使用以满足请求)并且前一块也在使用中(这是显然的,因为有合并的操作,所以不会存在两个连续的空闲块)。第三行是修改邻接的下一chunk块的PINUSE_BIT标记,表明邻接的下一chunk块的前一邻接chunk块(即当前正要被分配使用的chunkA)在使用中。第四行修改footers标记。前面曾经说过prev_foot用于记录前一邻接chunk块的大小,那是在前一邻接chunk块空闲情况,如果前一邻接chunk块处于使用状况,prev_foot则用于记录它们所在的malloc_state信息,如下:

/* Set foot of inuse chunk to be xor of mstate and seed */

#define mark_inuse_foot(M,p,s)\

  (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic))

mparams.magic起安全检测作用。

4064.         set_inuse_and_pinuse(gm, p, small_index2size(idx));

宏chunk2mem和mem2chunk用于在chunk块头地址和实际可用内存起始地址之间进行相互转换,这点根据chunk块结构体malloc_chunk和malloc_tree_chunk的定义很容易理解:

/* conversion from malloc headers to user pointers, and back */

#define chunk2mem(p)        ((void*)((char*)(p)       + TWO_SIZE_T_SIZES))

#define mem2chunk(mem)      ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES))

4065.         mem = chunk2mem(p);

debug编译时用于做安全检测。

4066.         check_malloced_chunk(gm, mem, nb);

内存申请请求得以满足,跳到最后执行解除互斥锁操作,返回分配的内存起始地址。

就算不考虑块头、对齐等开销,malloc分配的内存也可能会比应用程序实际应分配内存多,即前面的第三种情况:1 0,这种情况时,dlmalloc分配的内存将多出8个字节。由于8个字节不足以组成一个chunk块,因此也就没有必要进行分割,而是全部分配给当前申请请求,下面还可以看到这种情况。

4067.         goto postaction;

4068.       }

4069. 

dv chunk块不够大,因此在所有空闲chunk块中查找可以满足该请求的chunk块。

4070.       else if (nb > gm->dvsize) {

在smallbins中存在可满足该请求的空闲chunk块。

4071.         if (smallbits != 0) { /* Use chunk in next nonempty smallbin */

4072.           mchunkptr b, p, r;

4073.           size_t rsize;

4074.           bindex_t i;

下面两个宏用于位操作,我们来看看:

idx2bit宏取第i位为1,其它位都为0的掩码,举个例子:idx2bit(3) 为 “0000 1000”(只显示8位,下同)。

#define idx2bit(i)              ((binmap_t)(1) << (i))

left_bits宏取x中值为1的最小bit位的左边(不包括值为1的该最小bit所有位都为1,其它位都为0的掩码,举个例子:left_bits(6)为“1111 1100”,因为6的二进制位“0000 0110”,值为1的最小bit位为第1位,因此结果为第1位左边(不包括第1所有位都为1,其它位都为0的掩码。

#define left_bits(x)         ((x<<1) | -(x<<1))

leftbits为所有可满足当前申请请求的箱号。

4075.           binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx));

选择最佳适合的箱号对应的bitmap位码,即获取值为1的最小bit位:x中值为1的最小bit位为1,其它位都为0的掩码。

#define least_bit(x)         ((x) & -(x))

4076.           binmap_t leastbit = least_bit(leftbits);

compute_bit2idx 是一个宏,它使得leastbit的二进制中位为1的最小位置索引,从0开始。举个例子:

leastbit0000 0100,则compute_bit2idx(leastbit, i)的结果是使得i的值为2

因此作用是根据bitmap位码计算对应的箱号。

4077.           compute_bit2idx(leastbit, i);

获取箱号对应的链表头节点。

4078.           b = smallbin_at(gm, i);

4079.           p = b->fd;

4080.           assert(chunksize(p) == small_index2size(i));

拆出链表的第一个空闲chunk块以进行内存分配。

4081.           unlink_first_small_chunk(gm, b, p, i);

计算分割该chunk块(用于服务内存申请请求)后,该chunk块剩余的字节数。

4082.           rsize = small_index2size(i) - nb;

4083.           /* Fit here cannot be remainderless if 4byte sizes */

剩余的字节数太小无法组建一个新的空闲块,因此直接全部分配。

这里的判断利用了短路运算符的特性,我们应注意到当前待分配的chunk块大小和申请请求字节大小至少相差两个箱号的字节数目(即剩余字节数至少有16字节),当SIZE_T_SIZE == 4时,是不可能出现rsize < MIN_CHUNK_SIZE为真的情况的。换句话说,只有当SIZE_T_SIZE!=4的情况下,rsize < MIN_CHUNK_SIZE才有可能为真。至于为什么,可由MIN_CHUNK_SIZE的具体定义找到原因:

#define MIN_CHUNK_SIZE\

  ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)

#define MCHUNK_SIZE         (sizeof(mchunk))

当SIZE_T_SIZE == 4时(对齐832环境),MIN_CHUNK_SIZE为16,自然不会比剩余字节数rsize大。其它对齐设置和环境类似推理。

4084.           if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)

4085.             set_inuse_and_pinuse(gm, p, small_index2size(i));

4086.           else {

分割并将剩余字节组建一个新的空闲chunk块。

#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\

  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\

  mark_inuse_foot(M, p, s))

#define mark_inuse_foot(M,p,s)\

  (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic))

4087.             set_size_and_pinuse_of_inuse_chunk(gm, p, nb);

新组建空闲chunk块结构起始地址

#define chunk_plus_offset(p, s)  ((mchunkptr)(((char*)(p)) + (s)))

4088.             r = chunk_plus_offset(p, nb);

设置新组建空闲chunk块的标记,包括本块大小、本块的前一邻接块在使用中标识以及设置prev_foot数据为前一邻接块大小。

#define set_size_and_pinuse_of_free_chunk(p, s)\

  ((p)->head = (s|PINUSE_BIT), set_foot(p, s))

#define set_foot(p, s)  (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s))

4089.             set_size_and_pinuse_of_free_chunk(r, rsize);

将新组建空闲chunk块替换原来的dv chunk块:

#define replace_dv(M, P, S) {\

  size_t DVS = M->dvsize;\

  if (DVS != 0) {\

    mchunkptr DV = M->dv;\

    assert(is_small(DVS));\

    insert_small_chunk(M, DV, DVS);\

  }\

  M->dvsize = S;\

  M->dv = P;\

}

4090.             replace_dv(gm, r, rsize);

4091.           }

获取对应的实际分配内存起始地址、做debug检测,跳转等。

4092.           mem = chunk2mem(p);

4093.           check_malloced_chunk(gm, mem, nb);

4094.           goto postaction;

4095.         }

4096. 

在smallbins中没有可满足该请求的空闲chunk块,则试图在treebins中查找可满足该请求的空闲chunk块。函数tmalloc_small()的分析在后面给出。

4097.         else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) {

4098.           check_malloced_chunk(gm, mem, nb);

4099.           goto postaction;

4100.         }

4101.       }

4102.     }

超过最大请求值。

4103.     else if (bytes >= MAX_REQUEST)

4104.       nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */

4105.     else {

请求的内存大小在MAX_SMALL_REQUESTMAX_REQUEST之间。

首先进行对齐处理:CHUNK_OVERHEAD为边界标记占用的空间,CHUNK_ALIGN_MASK为对齐设置值。

#define pad_request(req) \

   (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)

4106.       nb = pad_request(bytes);

试图在treebins中查找可满足该请求的空闲chunk块。函数tmalloc_large()的分析在后面给出。

4107.       if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) {

4108.         check_malloced_chunk(gm, mem, nb);

4109.         goto postaction;

4110.       }

4111.     }

4112. 

dv chunk块内分配(注意各个执行路径):

4113.     if (nb <= gm->dvsize) {

4114.       size_t rsize = gm->dvsize - nb;

4115.       mchunkptr p = gm->dv;

剩余空间足够大,则进行分割和组建新chunk块。

4116.       if (rsize >= MIN_CHUNK_SIZE) { /* split dv */

4117.         mchunkptr r = gm->dv = chunk_plus_offset(p, nb);

4118.         gm->dvsize = rsize;

4119.         set_size_and_pinuse_of_free_chunk(r, rsize);

4120.         set_size_and_pinuse_of_inuse_chunk(gm, p, nb);

4121.       }

4122.       else { /* exhaust dv */

否则的话,直接全部分配完。

4123.         size_t dvs = gm->dvsize;

4124.         gm->dvsize = 0;

4125.         gm->dv = 0;

4126.         set_inuse_and_pinuse(gm, p, dvs);

4127.       }

4128.       mem = chunk2mem(p);

4129.       check_malloced_chunk(gm, mem, nb);

4130.       goto postaction;

4131.     }

4132. 

top chunk块内分配(注意各个执行路径):

4133.     else if (nb < gm->topsize) { /* Split top */

4134.       size_t rsize = gm->topsize -= nb;

4135.       mchunkptr p = gm->top;

4136.       mchunkptr r = gm->top = chunk_plus_offset(p, nb);

4137.       r->head = rsize | PINUSE_BIT;

4138.       set_size_and_pinuse_of_inuse_chunk(gm, p, nb);

4139.       mem = chunk2mem(p);

4140.       check_top_chunk(gm, gm->top);

4141.       check_malloced_chunk(gm, mem, nb);

4142.       goto postaction;

4143.     }

4144. 

最后的办法,直接从系统获取内存空间。函数sys_alloc()的分析在后面给出。

4145.     mem = sys_alloc(gm, nb);

4146. 

4147.   postaction:

和前面的PREACTION宏对应,用于进行解锁操作:

#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); }

#define RELEASE_LOCK(l)      pthread_mutex_unlock(l)

4148.     POSTACTION(gm);

4149.     return mem;

4150.   }

4151. 

4152.   return 0;

4153. }

5.2 函数tmalloc_small

3693. /* allocate a small request from the best fitting chunk in a treebin */

这里的参数nb总是小于等于256,应注意这点。

3694. static void* tmalloc_small(mstate m, size_t nb) {

3695.   tchunkptr t, v;

3696.   size_t rsize;

3697.   bindex_t i;

3698.   binmap_t leastbit = least_bit(m->treemap);

3699.   compute_bit2idx(leastbit, i);

3700. 

3701.   v = t = *treebin_at(m, i);

请求字节数目nb <= 256,而tree里的每个chunk块都大于等于256,所以此处不会出现负数,以下某些地方类似考虑。

3702.   rsize = chunksize(t) - nb;

3703. 

前面曾经说过dltree具有一个十分有用的特性(见前面对dltree的分析部分),下面这个循环就是利用这个特性查找最优分配的节点(即是空闲chunk块大小和请求空间nb最接近的节点),也就是最小的chunk块节点。

#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1])

同样应注意到“请求字节数目nb<=256,而tree里的每个chunk块都大于等于256”这一点来帮助理解。

3704.   while ((t = leftmost_child(t)) != 0) {

3705.     size_t trem = chunksize(t) - nb;

3706.     if (trem < rsize) {

3707.       rsize = trem;

3708.       v = t;

3709.     }

3710.   }

3711. 

安全检测。

3712.   if (RTCHECK(ok_address(m, v))) {

3713.     mchunkptr r = chunk_plus_offset(v, nb);

3714.     assert(chunksize(v) == rsize + nb);

3715.     if (RTCHECK(ok_next(v, r))) {

将其拆出dltree

3716.       unlink_large_chunk(m, v);

剩余空间太小则直接全部分配。

3717.       if (rsize < MIN_CHUNK_SIZE)

3718.         set_inuse_and_pinuse(m, v, (rsize + nb));

3719.       else {

否则将剩余空间组建成新的chunk块并替换为新的dv chunk块。

3720.         set_size_and_pinuse_of_inuse_chunk(m, v, nb);

3721.         set_size_and_pinuse_of_free_chunk(r, rsize);

3722.         replace_dv(m, r, rsize);

3723.       }

3724.       return chunk2mem(v);

3725.     }

3726.   }

3727. 

3728.   CORRUPTION_ERROR_ACTION(m);

3729.   return 0;

3730. }

5.3 函数tmalloc_large

3620. /* allocate a large request from the best fitting chunk in a treebin */

MAX_SMALL_REQUEST对齐值<= nb < MAX_REQUEST对齐值

3621. static void* tmalloc_large(mstate m, size_t nb) {

3622.   tchunkptr v = 0;

设置初始值,注意rsizesize_t类型变量,因此一个很小的负数事实上是一个很大的正数。

3623.   size_t rsize = -nb; /* Unsigned negation */

3624.   tchunkptr t;

3625.   bindex_t idx;

计算nb字节数所对应的dltree树结构。

3626.   compute_tree_index(nb, idx);

3627. 

dltree树结构不为空,即存在和nb字节数同在一个箱子内(不理解“同在一个箱子内”则请查看前面讲解dltree内容部分)的空闲chunk块。

3628.   if ((t = *treebin_at(m, idx)) != 0) {

3629.     /* Traverse tree for this bin looking for node with size == nb */

遍历dltree试图找到一个size==nb的空闲chunk块。

前面曾经讲到过dltree就是关键码只有01Trie树。并且根据treebins箱的分法,0号箱和1号箱的关键码都只需7位长(因为其范围为128,表xxx的第二列),2号箱和3号箱的关键码都只需8位长(因为其范围为256,表xxx的第二列),……,依次类似。

宏leftshift_for_tree_index(idx)计算的是32 减去 idx号箱对应的关键码长度 的值。

#define leftshift_for_tree_index(i) \

   ((i == NTREEBINS-1)? 0 : \

    ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2)))

#define NTREEBINS         (32U)

#define SIZE_T_BITSIZE      (sizeof(size_t) << 3)

#define TREEBIN_SHIFT     (8U)

#define SIZE_T_ONE          ((size_t)1)

直接来看具体的值吧,依旧是32位平台下:

箱号idx => leftshift_for_tree_index(idx)值

0=>25

1=>25

2=>24

3=>24

4=>23

5=>23

6=>22

7=>22

8=>21

9=>21

10=>20

11=>20

12=>19

13=>19

14=>18

15=>18

16=>17

17=>17

18=>16

19=>16

20=>15

21=>15

22=>14

23=>14

24=>13

25=>13

26=>12

27=>12

28=>11

29=>11

30=>10

31=>0

因此下面这个sizebits的值就是将nb中起关键码作用的位移到最左边的结果值。

3630.     size_t sizebits = nb << leftshift_for_tree_index(idx);

3631.     tchunkptr rst = 0;  /* The deepest untaken right subtree */

3632.     for (;;) {

3633.       tchunkptr rt;

相差多大?

宏chunksize用于计算chunk块大小。

#define chunksize(p)        ((p)->head & ~(INUSE_BITS))

3634.       size_t trem = chunksize(t) - nb;

比之前选定的chunk块更合适?

3635.       if (trem < rsize) {

3636.         v = t;

没有比它更合适的了,就是它了。

3637.         if ((rsize = trem) == 0)

3638.           break;

3639.       }

记录当前节点的右子树根节点。

3640.       rt = t->child[1];

根据关键码值进入相应的子树。

3641.       t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1];

rst保存包含有最接近nb字节数大小chunk块的dltree子树。

3642.       if (rt != 0 && rt != t)

3643.         rst = rt;

将要继续查找的子树为空,则保存rstt,然后跳出for循环。

3644.       if (t == 0) {

3645.         t = rst; /* set t to least subtree holding sizes > nb */

3646.         break;

3647.       }

3648.       sizebits <<= 1;

3649.     }

3650.   }

3651. 

tv都为0表示请求字节大小对应的treebin为空,因此只有在更大的箱号内查找。

3652.   if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */

和前面的某些代码类似,计算包含大于请求字节数目chunk块的所有箱号对应掩码。

3653.     binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap;

存在。

3654.     if (leftbits != 0) {

3655.       bindex_t i;

选择最合适的分箱。

3656.       binmap_t leastbit = least_bit(leftbits);

计算对应箱号。

3657.       compute_bit2idx(leastbit, i);

箱子对应dltree根节点。

3658.       t = *treebin_at(m, i);

3659.     }

3660.   }

3661. 

执行到这已经可以确定以t为根节点的dltree中所有chunk块都可满足申请请求,下面这个循环只不过试图在这个dltree中找到一个最合适的chunk块来用于内存分配。最合适的chunk块被保存在变量v内。

3662.   while (t != 0) { /* find smallest of tree or subtree */

3663.     size_t trem = chunksize(t) - nb;

3664.     if (trem < rsize) {

3665.       rsize = trem;

3666.       v = t;

3667.     }

leftmost_child宏优先选取左子树,在左子树为空的情况下则选取右子树。

#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1])

3668.     t = leftmost_child(t);

3669.   }

3670. 

3671.   /*  If dv is a better fit, return 0 so malloc will use it */

前面在treebins中选择的chunk块存在(即变量v不为空),并且它比dv chunk块更适合(即选择的chunk块大小比dv chunk块大小更接近当前请求字节数目)用于内存分配以服务当前申请请求。如果dv chunk块更适合用来分配,则本函数将返回0,因此会在返回到dlmalloc函数内再进行在dv chunk块的内存分配操作。

3672.   if (v != 0 && rsize < (size_t)(m->dvsize - nb)) {

下面同样是分割、组建新chunk块、设置边界标记等,在此不再累述。

3673.     if (RTCHECK(ok_address(m, v))) { /* split */

3674.       mchunkptr r = chunk_plus_offset(v, nb);

3675.       assert(chunksize(v) == rsize + nb);

3676.       if (RTCHECK(ok_next(v, r))) {

3677.         unlink_large_chunk(m, v);

3678.         if (rsize < MIN_CHUNK_SIZE)

3679.           set_inuse_and_pinuse(m, v, (rsize + nb));

3680.         else {

3681.           set_size_and_pinuse_of_inuse_chunk(m, v, nb);

3682.           set_size_and_pinuse_of_free_chunk(r, rsize);

3683.           insert_chunk(m, r, rsize);

3684.         }

3685.         return chunk2mem(v);

3686.       }

3687.     }

3688.     CORRUPTION_ERROR_ACTION(m);

3689.   }

3690.   return 0;

3691. }

3692. 

5.4 函数sys_alloc

3322. /* Get memory from system using MORECORE or MMAP */

3323. static void* sys_alloc(mstate m, size_t nb) {

3324.   char* tbase = CMFAIL;

3325.   size_t tsize = 0;

3326.   flag_t mmap_flag = 0;

3327. 

相关设置值的初始化。

3328.   init_mparams();

3329. 

3330.   /* Directly map large chunks */

dlmalloc向系统申请内存有两种方式:一为ORECORE(使用函数sbrk())方式;一为MMAP(使用函数mmap())方式。由于MMAP方式一般都是取到不连续的内存空间,因此只有在某些情况下(见下面)才使用它。

如果设置允许调用mmap函数并且字节数nb已超过了预设的可以调用mmap界限则利用mmap函数向系统申请内存。

3331.   if (use_mmap(m) && nb >= mparams.mmap_threshold) {

函数mmap_alloc()的分析后面给出。

3332.     void* mem = mmap_alloc(m, nb);

3333.     if (mem != 0)

3334.       return mem;

3335.   }

3336. 

根据相对优劣排序依次按照如下三种方法从系统获取内存:

1,利用MORECORE分配连续空间

2,利用MMAP分配新空间

3,利用MORECORE分配不连续空间

3337.   /*

3338.     Try getting memory in any of three ways (in most-preferred to

3339.     least-preferred order):

3340.     1. A call to MORECORE that can normally contiguously extend memory.

3341.        (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or

3342.        or main space is mmapped or a previous contiguous call failed)

3343.     2. A call to MMAP new space (disabled if not HAVE_MMAP).

3344.        Note that under the default settings, if MORECORE is unable to

3345.        fulfill a request, and HAVE_MMAP is true, then mmap is

3346.        used as a noncontiguous system allocator. This is a useful backup

3347.        strategy for systems with holes in address spaces -- in this case

3348.        sbrk cannot contiguously expand the heap, but mmap may be able to

3349.        find space.

3350.     3. A call to MORECORE that cannot usually contiguously extend memory.

3351.        (disabled if not HAVE_MORECORE)

3352.   */

3353. 

宏use_noncontiguous定义如下:

#define use_noncontiguous(M)  ((M)->mflags &   USE_NONCONTIGUOUS_BIT)

3354.   if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) {

试图利用MORECORE分配连续空间

3355.     char* br = CMFAIL;

函数segment_holding()用于查找某一内存地址所属于的segment段。具体代码如下,我们可以看到所谓的查找就是简单的线性扫描查找。

/* Return segment holding given address */

static msegmentptr segment_holding(mstate m, char* addr) {

  msegmentptr sp = &m->seg;

  for (;;) {

    if (addr >= sp->base && addr < sp->base + sp->size)

      return sp;

    if ((sp = sp->next) == 0)

      return 0;

  }

}

3356.     msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top);

3357.     size_t asize = 0;

上锁。

3358.     ACQUIRE_MORECORE_LOCK();

3359. 

top chunk块为空,即第一次分配情况。

3360.     if (ss == 0) {  /* First time through or recovery */

获取内存的当前边界点(即所谓的当前中断点)。CALL_MORECORE间接的调用sbrk()函数。函数sbrk()移动设置新的当前中断点并返回设置之前的中断点。

#define CALL_MORECORE(S)     MORECORE(S)

extern void*     sbrk(ptrdiff_t);

3361.       char* base = (char*)CALL_MORECORE(0);

3362.       if (base != CMFAIL) {

进行对齐处理,这里的对齐一般以一个内存页大小(32位平台下,大部分情况是4K)为基准,也可自由设置,比如64K

3363.         asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);

3364.         /* Adjust to end on a page boundary */

起始边界不是页对齐,则需要将末尾边界调整为页对齐。

#define is_page_aligned(S)\

   (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0)

#define page_align(S)\

 (((S) + (mparams.page_size)) & ~(mparams.page_size - SIZE_T_ONE))

3365.         if (!is_page_aligned(base))

3366.           asize += (page_align((size_t)base) - (size_t)base);

3367.         /* Can't call MORECORE if size is negative when treated as signed */

asize需要小于HALF_MAX_SIZE_T,sbrk()函数的参数是有符号的,如果大于HALF_MAX_SIZE_T,则传到sbrk()函数内部则成了负数。

#define HALF_MAX_SIZE_T     (MAX_SIZE_T / 2U)

#define MAX_SIZE_T           (~(size_t)0)

3368.         if (asize < HALF_MAX_SIZE_T &&

3369.             (br = (char*)(CALL_MORECORE(asize))) == base) {

利用MORECORE分配连续空间成功。

3370.           tbase = base;

3371.           tsize = asize;

3372.         }

3373.       }

3374.     }

3375.     else {

top chunk块存在的情况。

3376.       /* Subtract out existing available top space from MORECORE request. */

3377.       asize = granularity_align(nb - m->topsize + TOP_FOOT_SIZE + SIZE_T_ONE);

3378.       /* Use mem here only if it did continuously extend old space */

3379.       if (asize < HALF_MAX_SIZE_T &&

3380.           (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) {

3381.         tbase = br;

3382.         tsize = asize;

3383.       }

3384.     }

3385. 

部分操作失败的处理。

3386.     if (tbase == CMFAIL) {    /* Cope with partial failure */

3387.       if (br != CMFAIL) {    /* Try to use/extend the space we did get */

3388.         if (asize < HALF_MAX_SIZE_T &&

3389.             asize < nb + TOP_FOOT_SIZE + SIZE_T_ONE) {

3390.           size_t esize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE - asize);

3391.           if (esize < HALF_MAX_SIZE_T) {

3392.             char* end = (char*)CALL_MORECORE(esize);

3393.             if (end != CMFAIL)

3394.               asize += esize;

3395.             else {            /* Can't use; try to release */

3396.               CALL_MORECORE(-asize);

3397.               br = CMFAIL;

3398.             }

3399.           }

3400.         }

3401.       }

3402.       if (br != CMFAIL) {    /* Use the space we did get */

3403.         tbase = br;

3404.         tsize = asize;

3405.       }

如果本次试图申请连续内存空间失败,则再次申请内存时候就直接放弃这种尝试。

#define disable_contiguous(M) ((M)->mflags |=  USE_NONCONTIGUOUS_BIT)

3406.       else

3407.         disable_contiguous(m); /* Don't try contiguous path in the future */

3408.     }

3409. 

解锁。

3410.    RELEASE_MORECORE_LOCK();

3411.   }

3412. 

尝试利用mmap申请内存空间。

3413.   if (HAVE_MMAP && tbase == CMFAIL) {  /* Try MMAP */

3414.     size_t req = nb + TOP_FOOT_SIZE + SIZE_T_ONE;

3415.     size_t rsize = granularity_align(req);

3416.     if (rsize > nb) { /* Fail if wraps around zero */

CALL_MMAP宏定义:

#define CALL_MMAP(s)         mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0)

3417.       char* mp = (char*)(CALL_MMAP(rsize));

3418.       if (mp != CMFAIL) {

3419.         tbase = mp;

3420.         tsize = rsize;

3421.         mmap_flag = IS_MMAPPED_BIT;

3422.       }

3423.     }

3424.   }

3425. 

尝试利用MORECORE申请不连续内存空间。

3426.   if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */

3427.     size_t asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);

3428.     if (asize < HALF_MAX_SIZE_T) {

3429.       char* br = CMFAIL;

3430.       char* end = CMFAIL;

3431.       ACQUIRE_MORECORE_LOCK();

3432.       br = (char*)(CALL_MORECORE(asize));

3433.       end = (char*)(CALL_MORECORE(0));

3434.       RELEASE_MORECORE_LOCK();

3435.       if (br != CMFAIL && end != CMFAIL && br < end) {

3436.         size_t ssize = end - br;

3437.         if (ssize > nb + TOP_FOOT_SIZE) {

3438.           tbase = br;

3439.           tsize = ssize;

3440.         }

3441.       }

3442.     }

3443.   }

3444. 

对新分配内存进行处理,牵扯到的变量和函数比较多,处理也比较繁琐,比如初始化、设置边界、设置top chunk块、块合并等等,但是都比较简单易解,因此也没有什么特别好讲解的,在此略过,如果需要看清这段代码请结合malloc-2.8.3.c源码文件来看,因为下面很多被调用函数在整个本文档内都未列出来。

3445.   if (tbase != CMFAIL) {

3446. 

3447.     if ((m->footprint += tsize) > m->max_footprint)

3448.       m->max_footprint = m->footprint;

3449. 

第一次执行初始化操作。

3450.     if (!is_initialized(m)) { /* first-time initialization */

3451.       m->seg.base = m->least_addr = tbase;

3452.       m->seg.size = tsize;

3453.       m->seg.sflags = mmap_flag;

3454.       m->magic = mparams.magic;

初始化smallbins分箱。

3455.       init_bins(m);

3456.       if (is_global(m)) 

初始化并设置top chunk块。

3457.         init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);

3458.       else {

3459.         /* Offset top by embedded malloc_state */

3460.         mchunkptr mn = next_chunk(mem2chunk(m));

3461.         init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE);

3462.       }

3463.     }

3464. 

3465.     else {

3466.       /* Try to merge with an existing segment */

3467.       msegmentptr sp = &m->seg;

3468.       while (sp != 0 && tbase != sp->base + sp->size)

3469.         sp = sp->next;

top segment合并。

3470.       if (sp != 0 &&

3471.           !is_extern_segment(sp) &&

3472.           (sp->sflags & IS_MMAPPED_BIT) == mmap_flag &&

3473.           segment_holds(sp, m->top)) { /* append */

3474.         sp->size += tsize;

3475.         init_top(m, m->top, m->topsize + tsize);

3476.       }

3477.       else {

3478.         if (tbase < m->least_addr)

3479.           m->least_addr = tbase;

3480.         sp = &m->seg;

3481.         while (sp != 0 && sp->base != tbase + tsize)

3482.           sp = sp->next;

3483.         if (sp != 0 &&

3484.             !is_extern_segment(sp) &&

3485.             (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) {

3486.           char* oldbase = sp->base;

3487.           sp->base = tbase;

3488.           sp->size += tsize;

3489.           return prepend_alloc(m, tbase, oldbase, nb);

3490.         }

3491.         else

3492.           add_segment(m, tbase, tsize, mmap_flag);

3493.       }

3494.     }

3495. 

3496.     if (nb < m->topsize) { /* Allocate from new or extended top space */

3497.       size_t rsize = m->topsize -= nb;

3498.       mchunkptr p = m->top;

3499.       mchunkptr r = m->top = chunk_plus_offset(p, nb);

3500.       r->head = rsize | PINUSE_BIT;

3501.       set_size_and_pinuse_of_inuse_chunk(m, p, nb);

3502.       check_top_chunk(m, m->top);

3503.       check_malloced_chunk(m, chunk2mem(p), nb);

3504.       return chunk2mem(p);

3505.     }

3506.   }

3507. 

3508.   MALLOC_FAILURE_ACTION;

3509.   return 0;

3510. }

5.5 函数mmap_alloc

3106. /*

3107.   Directly mmapped chunks are set up with an offset to the start of

3108.   the mmapped region stored in the prev_foot field of the chunk. This

3109.   allows reconstruction of the required argument to MUNMAP when freed,

3110.   and also allows adjustment of the returned chunk to meet alignment

3111.   requirements (especially in memalign).  There is also enough space

3112.   allocated to hold a fake next chunk of size SIZE_T_SIZE to maintain

3113.   the PINUSE bit so frees can be checked.

3114. */

3115. 

3116. /* Malloc using mmap */

3117. static void* mmap_alloc(mstate m, size_t nb) {

对齐处理。

3118.   size_t mmsize = granularity_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK);

检测溢出错误。

3119.   if (mmsize > nb) {     /* Check for wrap around 0 */

DIRECT_MMAP宏调用mmap函数申请内存空间。

#define DIRECT_MMAP(s)       CALL_MMAP(s)

#define CALL_MMAP(s)         mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0)

3120.     char* mm = (char*)(DIRECT_MMAP(mmsize));

成功情况下:

3121.     if (mm != CMFAIL) {

计算对齐偏移。

3122.       size_t offset = align_offset(chunk2mem(mm));

计算大小。

3123.       size_t psize = mmsize - offset - MMAP_FOOT_PAD;

调整对齐。

3124.       mchunkptr p = (mchunkptr)(mm + offset);

偏移部分被伪装成前一个邻接chunk块,大小记录在prev_foot内。

3125.       p->prev_foot = offset | IS_MMAPPED_BIT;

设置大小和标记等。

3126.       (p)->head = (psize|CINUSE_BIT);

3127.       mark_inuse_foot(m, p, psize);

3128.       chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD;

3129.       chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0;

3130. 

修改least_addr字段,即最小边界地址。

3131.       if (mm < m->least_addr)

3132.         m->least_addr = mm;

修改max_footprint,见前面对字段max_footprint含义的介绍。

3133.       if ((m->footprint += mmsize) > m->max_footprint)

3134.         m->max_footprint = m->footprint;

断言。

3135.       assert(is_aligned(chunk2mem(p)));

检测并返回正确地址。

3136.       check_mmapped_chunk(m, p);

3137.       return chunk2mem(p);

3138.     }

3139.   }

3140.   return 0;

3141. }

6. 内存回收相关函数

本节主要对dlmalloc内存分配器的另外一个核心函数dlfree()以及相关函数进行讲解,函数dlfree用于服务应用程序的内存空间释放回收,即是我们平常使用得最多的两个函数之一。

6.1 函数dlfree

4155. void dlfree(void* mem) {

4156.   /*

4157.      Consolidate freed chunks with preceeding or succeeding bordering

4158.      free chunks, if they exist, and then place in a bin.  Intermixed

4159.      with special cases for top, dv, mmapped chunks, and usage errors.

4160.   */

4161. 

4162.   if (mem != 0) {

获取mem地址对应的chunk块结构,也是dlfree将释放的chunk块,以下称之为curt块。

4163.     mchunkptr p  = mem2chunk(mem);

4164. #if FOOTERS

如果定义了foot则当chunk块处于使用状态时,prev_foot则用于记录该chunk块所在的malloc_state信息,见上一节的代码4064行。

#define get_mstate_for(p)\

  ((mstate)(((mchunkptr)((char*)(p) +\

    (chunksize(p))))->prev_foot ^ mparams.magic))

4165.     mstate fm = get_mstate_for(p);

做安全检测,结合上一节的代码4064行来理解。

/* Check if (alleged) mstate m has expected magic field */

#define ok_magic(M)      ((M)->magic == mparams.magic)

4166.     if (!ok_magic(fm)) {

4167.       USAGE_ERROR_ACTION(fm, p);

4168.       return;

4169.     }

4170. #else /* FOOTERS */

没有定义了foot情况,则直接使用的是全局静态变量gm

4171. #define fm gm

4172. #endif /* FOOTERS */

4173.     if (!PREACTION(fm)) {

做一些DEBUG检测。

4174.       check_inuse_chunk(fm, p);

既然是要释放curt块,那么curt块目前肯定是在使用中,下面宏ok_cinuse用于检测这一断言。

4175.       if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) {

计算curt块大小。

#define chunksize(p)        ((p)->head & ~(INUSE_BITS))

4176.         size_t psize = chunksize(p);

获取下一个chunk块,因为需要根据下一个chunk块(以下称之为next块)状态做相应的不同处理。

#define chunk_plus_offset(p, s)  ((mchunkptr)(((char*)(p)) + (s)))

4177.         mchunkptr next = chunk_plus_offset(p, psize);

前一邻接chunk块空闲中?

4178.         if (!pinuse(p)) {

前一邻接chunk块处于空闲中,那么本chunk块的prev_foot字段记录的是它的大小。

4179.           size_t prevsize = p->prev_foot;

4180.           if ((prevsize & IS_MMAPPED_BIT) != 0) {

判断本chunk块是否是直接由mmap()函数分配的,如果是也就直接调用munmap()函数来进行释放,这个对应前面讲解的函数mmap_alloc()就很容易理解了。

4181.             prevsize &= ~IS_MMAPPED_BIT;

4182.             psize += prevsize + MMAP_FOOT_PAD;

CALL_MUNMAP宏定义如下:

#define CALL_MUNMAP(a, s)    munmap((a), (s))

4183.             if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)

4184.               fm->footprint -= psize;

4185.             goto postaction;

4186.           }

4187.           else {

mmap分配的chunk,获取前一邻接chunk块结构体指针(以下称之为prev块)。

#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s)))

4188.             mchunkptr prev = chunk_minus_offset(p, prevsize);

curt块和prev块合并。

4189.             psize += prevsize;

4190.             p = prev;

4191.             if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */

4192.               if (p != fm->dv) {

prev块从空闲链/树中拆出,注意指定的大小是prevsize,而不是psize。这里之所以要判断prev块是否为dv chunk块,是由于需要对dv chunk块特殊处理。

4193.                 unlink_chunk(fm, p, prevsize);

4194.               }

如果prev块就是dv chunk块,并且next块在使用中。

宏INUSE_BITS被定义如下:

#define INUSE_BITS          (PINUSE_BIT|CINUSE_BIT)

next块的PINUSE_BIT肯定为1,因为它指定的是curt块的状态,而curt块就是待释放的块,当然在使用中。CINUSE_BIT为1表示next块自身的状态,即在使用中。

4195.               else if ((next->head & INUSE_BITS) == INUSE_BITS) {

这种情况下,释放curt块十分简单,直接修改dv chunk的大小即可。当然还需要做边界标记设置。

/* Set size, pinuse bit, foot, and clear next pinuse */

#define set_free_with_pinuse(p, s, n)\

  (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s))

#define clear_pinuse(p)     ((p)->head &= ~PINUSE_BIT)

#define set_size_and_pinuse_of_free_chunk(p, s)\

  ((p)->head = (s|PINUSE_BIT), set_foot(p, s))

#define set_foot(p, s)  (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s))

4196.                 fm->dvsize = psize;

4197.                 set_free_with_pinuse(p, psize, next);

4198.                 goto postaction;

4199.               }

4200.             }

4201.             else

4202.               goto erroraction;

4203.           }

4204.         }

4205. 

有多条执行路径可以执行到这,如代码41944199等。此处对next块进行处理。

下一个if做安全检测,ok_next(p, next)宏用于保证p地址要小于next地址(dlmalloc分配的堆内存是向上增长的),另外next块的P标记应该为1

#define ok_next(p, n)    ((char*)(p) < (char*)(n))

#define ok_pinuse(p)     pinuse(p)

#define pinuse(p)           ((p)->head & PINUSE_BIT)

4206.         if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) {

4207.           if (!cinuse(next)) {  /* consolidate forward */

next块空闲中。

4208.             if (next == fm->top) {

next块就是top chunk空闲块。因此可以和top chunk空闲块合并。

4209.               size_t tsize = fm->topsize += psize;

4210.               fm->top = p;

4211.               p->head = tsize | PINUSE_BIT;

4212.               if (p == fm->dv) {

如果(合并的)curt块同时也是dv chunk块,则设置dv chunk块为0

4213.                 fm->dv = 0;

4214.                 fm->dvsize = 0;

4215.               }

是否需要裁减top chunk块,这通过判断top chunk的大小是否超过设定界限来做决定。

#define should_trim(M,s)  ((s) > (M)->trim_check)

4216.               if (should_trim(fm, tsize))

函数sys_trim()才是将dlmalloc管理的内存实际返还给系统,关于该函数后面给出解析。

4217.                 sys_trim(fm, 0);

4218.               goto postaction;

4219.             }

4220.             else if (next == fm->dv) {

next块就是dv chunk空闲块。

4221.               size_t dsize = fm->dvsize += psize;

4222.               fm->dv = p;

4223.               set_size_and_pinuse_of_free_chunk(p, dsize);

4224.               goto postaction;

4225.             }

4226.             else {

计算next块大小。

4227.               size_t nsize = chunksize(next);

4228.               psize += nsize;

next块拆出空闲链/树。

4229.               unlink_chunk(fm, next, nsize);

4230.               set_size_and_pinuse_of_free_chunk(p, psize);

4231.               if (p == fm->dv) {

4232.                 fm->dvsize = psize;

4233.                 goto postaction;

4234.               }

4235.             }

4236.           }

4237.           else

next块使用中。

4238.             set_free_with_pinuse(p, psize, next);

插入空闲管理链/树。

4239.           insert_chunk(fm, p, psize);

4240.           check_free_chunk(fm, p);

4241.           goto postaction;

4242.         }

4243.       }

4244.     erroraction:

4245.       USAGE_ERROR_ACTION(fm, p);

4246.     postaction:

4247.       POSTACTION(fm);

4248.     }

4249.   }

4250. #if !FOOTERS

4251. #undef fm

4252. #endif /* FOOTERS */

4253. }

6.2 函数sys_trim

3555. static int sys_trim(mstate m, size_t pad) {

3556.   size_t released = 0;

3557.   if (pad < MAX_REQUEST && is_initialized(m)) {

3558.     pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */

3559. 

3560.     if (m->topsize > pad) {

3561.       /* Shrink top space in granularity-size units, keeping at least one */

3562.       size_t unit = mparams.granularity;

3563.       size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit -

3564.                       SIZE_T_ONE) * unit;

3565.       msegmentptr sp = segment_holding(m, (char*)m->top);

3566. 

3567.       if (!is_extern_segment(sp)) {

检查是否可释放。

3568.         if (is_mmapped_segment(sp)) {

mmap分配的内存,用munmap释放。

3569.           if (HAVE_MMAP &&

3570.               sp->size >= extra &&

3571.               !has_segment_link(m, sp)) { /* can't shrink if pinned */

3572.             size_t newsize = sp->size - extra;

3573.             /* Prefer mremap, fall back to munmap */

内存的释放优先采用函数CALL_MREMAP,如果该函数执行失败则再采用函数CALL_MUNMAP

3574.             if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) ||

3575.                 (CALL_MUNMAP(sp->base + newsize, extra) == 0)) {

3576.               released = extra;

3577.             }

3578.           }

3579.         }

3580.         else if (HAVE_MORECORE) {

3581.           if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */

3582.             extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit;

3583.           ACQUIRE_MORECORE_LOCK();

3584.           {

3585.             /* Make sure end of memory is where we last set it. */

获取当前内存中断点。

3586.             char* old_br = (char*)(CALL_MORECORE(0));

3587.             if (old_br == sp->base + sp->size) {

内存缩减,即是内存释放。

3588.               char* rel_br = (char*)(CALL_MORECORE(-extra));

内存释放后的中断点。

3589.               char* new_br = (char*)(CALL_MORECORE(0));

求得实际释放的内存量。

3590.               if (rel_br != CMFAIL && new_br < old_br)

3591.                 released = old_br - new_br;

3592.             }

3593.           }

3594.           RELEASE_MORECORE_LOCK();

3595.         }

3596.       }

3597. 

更新数据。

3598.       if (released != 0) {

3599.         sp->size -= released;

3600.         m->footprint -= released;

3601.         init_top(m, m->top, m->topsize - released);

3602.         check_top_chunk(m, m->top);

3603.       }

3604.     }

3605. 

3606.     /* Unmap any unused mmapped segments */

3607.     if (HAVE_MMAP) 

3608.       released += release_unused_segments(m);

3609. 

3610.     /* On failure, disable autotrim to avoid repeated failed future calls */

释放失败则禁止再次释放,防止反复释放失败。

3611.     if (released == 0)

3612.       m->trim_check = MAX_SIZE_T;

3613.   }

3614. 

3615.   return (released != 0)? 1 : 0;

3616. }

7. 本文档声明

本文档欢迎自由转载,但请务必保持本文档完整或注明来之本文档(即只转载了部分内容,《内存分配器dlmalloc 2.8.3源码浅析》)。

本文档未经lenky0401同意,不得用于商业用途。

最后,如果您能从这个简单文档里获得些许帮助,lenky0401将对自己的一点努力感到非常高兴;lenky0401水平有限,如果本文档中包含的错误给您造成了不便,lenky0401在此提前说声抱歉。J

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

内存分配器dlmalloc 2.8.3源码浅析 的相关文章

  • js 字符串转换成数字的三种方法, 取float型小数点后两位数的方法

    在js读取文本框或者其它表单数据的时候获得的值是字符串类型的 例如两个文本框a和b 如果获得a的value值为11 b的value值为9 那么a value要小于b value 因为他们都是字符串形式的 在网上找了一下js字符串转数字的文章
  • 二次贝塞尔曲线长度

    二次贝塞尔曲线通常以如下方式构建 xff0c 给定二维平面上的固定点P0 P1 P2 xff0c 用B t 表示该条曲线 用一个动画来演示 xff0c 可以更加清楚的表明这条曲线的构建过程 如果t变量本身线形变化的话 xff0c 这条贝塞尔
  • 损坏的主控文件表,CHKDSK被终止.如何恢复数据

    这种情况是分区结构出现异常 引起的分区错误 单击右键属性看到的是RAW格式 移动硬盘的话 xff0c 很大程度是因为强拔之类的操作 xff0c 也可能是坏道 病毒 xff0c 硬盘本身质量问题引起的 因为系统读取移动硬盘信息困难 xff0c
  • Microsoft SQL Server 2008 R2 官方简体中文正式版下载(附激活序列号密钥)

    微软官方发布的Microsoft SQL Server 2008 R2 简体中文完整版 基于SQL Server 2008提供可靠高效的智能数据平台构建而成 xff0c SQL Server 2008 R2 提供了大量新改进 xff0c 可
  • 不重装系统将系统移动到固态硬盘,并修改为C盘

    如今很多人升级电脑都会考虑换块固态硬盘 xff0c 如果重装系统 xff0c 又要重新安装一系列的软件 xff0c 拷贝一系列的数据 这里教大家怎样直接用固态硬盘代替C盘 xff0c 并且不会影响数据和软件 1 首先是在原电脑上加装新固态硬
  • python进行t检验示例

    t检验主要是针对正态总体均值的假设检验 xff0c 即检验样本的均值与某个值的差异 xff0c 或者两个样本的均值是否有差异等 其不需要事先知道总体的方差 xff0c 并且在少量样本情况下也可以进行检验 python进行t检验使用scipy
  • python-正态分布查表应用(scipy.stats.norm)

    1 公式 xff1a 正态分布概率密度公式 xff1a 可通过转换为标准正态分布 2 概率密度 xff1a 标准正态分布在某个点的概率密度可用scipy stats norm pdf计算 xff0c 下面模拟计算 5 5的概率密度 from
  • python-介绍泊松分布(poisson分布)

    一 泊松分布问题 xff1a 假设我每天接到骚扰电话的次数服从泊松分布 xff0c 并且经统计平均每天我会接到20个骚扰电话 请问 xff1a 1 我明天接到15个骚扰电话的概率 xff1f 2 我明天接到24个骚扰电话以下的概率 xff0
  • Word文档编辑技巧

    1 如何让附录显示在目录里 依次点击菜单栏的视图 工具栏 大纲 xff0c 打开大纲工具栏 选中 目录 附录 参考文献 等标题 xff0c 点击大纲工具栏的大纲级别的下拉选项 xff0c 选择 1级 xff0c 此时就完成了对 目录 等标题
  • 谈windows开发学习过程

    MFC作为一个C 43 43 类库 xff0c 而且MFC是Windows SDK的封装 为了开发windows应用更方便 xff0c 让程序员开发应用不需要过多在意细节 xff0c 专注功能上的开发 xff0c 所以推出MFC 但是实际开
  • python-指数分布介绍(scipy.stats.expon)

    一 指数分布问题 xff1a 有一种品牌的路由器 xff0c 据厂家统计知该路由器平均寿命是50000小时 xff0c 现在有2个问题 xff1a 1 去年我买了一个这样的路由器 xff0c 使用到现在已经8000小时了一点问题都没有 xf
  • python-使用LinearRegression进行简单线性拟合(线性回归)

    一元线性拟合 现有两组数据 xff0c 求y 61 a x 43 c的系数 X 61 12 46 0 25 5 22 11 3 6 81 4 59 0 66 14 53 15 49 14 43 2 19 1 35 10 02 12 93 5
  • matplotlib绘制双坐标轴(双纵轴)

    双坐标轴绘图示例 import matplotlib pyplot as plt import numpy as np plt rcParams 39 font sans serif 39 61 39 SimHei 39 用来正常显示中文标
  • pyplot画多个图,在一个图中绘制多个子图

    pyplot绘制多个子图方法 matplotlib在一个图中绘制多个子图用plt subplot 方法 import matplotlib pyplot as plt import numpy as np plt rcParams 39 f
  • mysql数据库备份及恢复

    1 mysql数据库备份 xff1a mysqldump lan db u root pmypassword h 10 49 56 01 gt data backup lan db sql 2 mysql数据库恢复 xff1a 先创建数据库
  • mysql密码过期解决办法,mysql密码永不过期

    1 进入到数据库服务器 xff0c windows需要进入到bin目录 xff0c linux应该是任意目录即可 xff08 但需要账号有权限 xff09 以windows为例 xff1a C Program Files MySQL MyS
  • java任意进制转换

    摘要 我们日常常用的是十进制 计算机是基于二进制的 xff0c 计算机常用的还有十六进制 八进制 本文主要介绍如何实现十进制和任意进制 间转换 计算思想 如果用 0123456789abcdefghijklmnopqrstuvwxyzABC
  • c++与java的应用区别

    文章目录 简介不同点关注点接口调用优化运行方式知识架构java不擅长的地方 基本就这些了 简介 一点粗鄙的总结 xff0c 肯定不全面 java我只接触了 后端部分 不同点 c 43 43 基本上你可以应用在所有的领域 xff0c 但是在后
  • HTTP基本认证(Basic Authentication)

    什么是HTTP基本认证 当打开浏览器访问网页时 xff0c 浏览器弹出一个包含用户名 密码输出项的对话框 xff0c 当用户输入用户名 密码后发送给服务器 xff0c 服务器验证通过后 xff0c 会显示相应的信息 这一认证的过程 xff0
  • Nginx(四):http服务器静态文件查找的实现

    上一篇nginx的文章中 xff0c 我们理解了整个http正向代理的运行流程原理 xff0c 主要就是事件机制接入 xff0c header解析 xff0c body解析 xff0c 然后遍历各种checker xff0c 直到处理成功为

随机推荐

  • 计算机网络-第3章 物理层

    期末考试篇 1 物理层定义了哪四方面的特性 xff1f 机械 xff1a 指明接口所用接线器的形状和尺寸 引脚数目和排列 固定和锁定装置 电气 xff1a 指明在接口电缆的各条线上出现的电压的范围 功能 xff1a 指明某条线上出现的某一电
  • jetson nano ubuntu18.04 ROS安装

    历时一天 xff0c 因为之前用手机热点 xff0c 即使换源了下载还是很慢 今天用校园网以后好了很多了 xff08 校园网还是挺好用的 xff09 xff0c 期间也踩了点坑 xff0c 记录一下 xff0c 也希望能帮到看这篇博客的你
  • 认识c++(二)

    上次说到c 43 43 变量类型 xff0c 接下来说c 43 43 变量作用域 作用域是程序的一个区域 xff0c 一般来说有三个地方可以定义变量 xff1a 1 函数或一个代码块内部声明的变量 xff0c 称为局部变量 2 函数参数的定
  • 老猿学5G扫盲贴:NEF、NRF、AF、UPF以及DN的功能

    专栏 xff1a Python基础教程目录 专栏 xff1a 使用PyQt开发图形界面Python应用 专栏 xff1a PyQt入门学习 老猿Python博文目录 NEF xff1a Network Exposure Function x
  • moviepy音视频剪辑:与大小相关的视频变换函数crop、even_size、margin、resize介绍

    前往老猿Python博文目录 一 引言 在 moviepy音视频剪辑 xff1a moviepy中的剪辑基类Clip详解 介绍了剪辑基类的fl fl time fx方法 xff0c 在 moviepy音视频剪辑 xff1a 视频剪辑基类Vi
  • moviepy音视频开发专栏文章目录

    前往老猿Python博文目录 moviepy音视频开发专栏 为收费专栏 对应免费专栏为 PyQt moviepy音视频剪辑实战 这2个专栏基于老猿阅读moviepy1 03版本的源代码以及大量测试验证的基础上 详细介绍moviepy主要音视
  • 使用PyQt开发图形界面Python应用专栏目录

    前往老猿Python博文目录 本专栏为收费专栏的文章目录 xff0c 对应的免费专栏为 PyQt入门知识目录 xff0c 两个专栏都为基于PyQt的Python图形界面开发基础教程 xff0c 只是收费专栏中的内容介绍更深入 案例代码更全
  • 《OpenCV-Python初学者疑难问题集》专栏目录

    本专栏为免费专栏 https blog csdn net LaoYuanPython article details 109160152 OpenCV Python图形图像处理专栏 的伴生付费专栏 xff0c 用于发布一些学习OpenCV
  • 使用Python+Moviepy 5行代码实现两视频顺序拼接

    老猿Python博文目录 xff1a https blog csdn net LaoYuanPython 一 引言 最近看到好几篇类似 n行Python代码 的博文 xff0c 看起来还挺不错 xff0c 简洁 实用 xff0c 传播了知识
  • 图像滤波基础知识:图像与波的关系以及图像噪声知识

    前往老猿Python博文目录 https blog csdn net LaoYuanPython 一 引言 老猿对图像处理基础知识非常缺乏 xff0c 所以OpenCV Python的学习进度很慢 xff0c 很多基础概念和原理的东西花了大
  • 用Python通过摄像头进行视频录制

    老猿Python博文目录 xff1a https blog csdn net LaoYuanPython 用Python通过摄像头进行视频录制 一 引言 要实现摄像头录播摄像信息 xff0c 通过Python有很多种实现方式 xff0c 老
  • 计算机四级网络工程师——操作系统部分题目笔记汇总【1~10题】

    计算机四级笔记 操作系统部分 xff1a xff08 1 10题 xff09 因篇幅过长 xff0c 为保证学习质量 xff0c 遂将其分成四部分 xff08 四篇博客 xff09 每10题为一篇 xff0c 其他题目在我的计算机四级考试网
  • 一文带你读懂PyQt:用Python做出与C++一样的GUI界面应用程序

    一 简介 Python标准库更多的适合处理后台任务 xff0c 唯一的图形库tkinter使用起来很不方便 xff0c 所以后来出现了针对Python图形界面开发的扩展库 xff0c 今天老猿要介绍的是主流Python图形界面扩展库之一的P
  • Python中可迭代对象是什么?

    Python中可迭代对象 Iterable 并不是指某种具体的数据类型 xff0c 它是指存储了元素的一个容器对象 xff0c 且容器中的元素可以通过 iter 方法或 getitem 方法访问 iter 方法的作用是让对象可以用for i
  • 老猿Python博客文章目录索引

    本目录提供老猿Python所有相关博文的一级目录汇总 xff0c 带星号的为付费专栏 xff1a 一 专栏列表 本部分为老猿所有专栏的列表 xff0c 每个专栏都有该专栏置顶的博文目录 xff1a Python基础教程目录PyQt入门学习
  • 用TAP方式让QEMU虚拟机与host联网

    转载自 cgjvcd 最终编辑 cgjvcd QEMU虚拟机网络的缺省模式是NAT方式 xff0c 即虚拟机可以通过host访问外网 xff0c 但host和外网无法访问虚拟机 如果要想让host访问虚拟机 xff0c 则可以使用TAP方式
  • TCP服务器端怎么判断客户端已经关闭了连接?

    http xidianzhangjun blog 163 com blog static 11548877120114411056939 哎 xff0c 首先 xff0c 又犯了一个大错 xff0c 前几天把这个问题通过实验搞懂了 xff0
  • PDP上下文和PDP地址

    http www mscbsc com 10037062 viewspace 61117 html S要接入外部PDN MS还应具有与该PDN相应的地址 称为PDP地址 PDP地址是用于外部分组数据网识别MS的PDP上下文时使用的地址 如用
  • Nmap从探测到漏洞利用备忘录

    http www freebuf com articles network 32302 html 在侦查期间 xff0c 扫描一直是信息收集的初始阶段 什么是侦查 侦查是尽可能多收集关于目标网络的信息 从黑客的角度来看 xff0c 信息收集
  • 内存分配器dlmalloc 2.8.3源码浅析

    目 录 1 本文档介绍 1 2 xff0e 边界标记法 2 3 分箱式内存管理 6 4 核心结构体malloc state 13 5 内存分配相关函数 16 5 1 函数dlmalloc 16 5 2 函数tmalloc small 25