百家汽车网
您的当前位置:首页Dlmalloc详解

Dlmalloc详解

来源:百家汽车网




Dlmalloc详解

Dlmalloc是目前一个十分流行的内存分配器,其由DougLea1987年开始编写,到目

前为止,最新版本为2.8.3,由于其高效率等特点被广泛的使用。U-boot上使用的dlmalloc的版本是2.6.6

1边界标注

首先解释一下chunk的概念,这个概念对内存分配器而言十分重要。Chunk是大块的意思,在dlmalloc中指包含了用户空间、heap控制信息空间以及出于对齐需求而多出来的空间的内存空间,是dlmalloc分配释放的基本操作对象。

占据了整个heap有两种类型的chunk,已分配的chunk和未分配的chunk,两者交错排列,
空间。注意,没有相邻的两个未分配的chunk,因为在调用free释放被使用过的chunk时,dlmalloc将合并任何相邻的空闲chunk。交错的两种chunk看起来是这样的。

Dlmalloc使用双向链表来管理空闲chunk,其节点数据结构体定义如下:



struct malloc_chunk
{
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk;
};

成员prev_size记录了物理位置上相邻的前一个chunk的大小,利用prev_size可以找到前一个chunk,这在调用free合并前一个空闲块时派上了用场。

成员size记录了该chunk的大小,dllmalloc32位处理器上总是8字节对齐,故size的低3位对size而言是无效的,dlmalloc利用这三位来记录一些信息。

#define

PREV_INUSE

0x01:物理位置上相邻的前一个chunk 是否被分配使用的标

记,如果为0x01,说明被分配;

#define

IS_MMAPPED

0x02:该位为1,表示该chunk 是通过mmap 分配而得的,

那么释放时调用munmap
Fdbk则分别指向双向链表中前一个节点和后一个节点。

其物理布局看起来像这样:

可以看出,chunk指针指向heap内部控制信息,图中headfoot区域的sizeof chunk必须是一样的,如此nextchunk才能根据sizeof chunk准确地找到chunk的位置。

另一种是已分配的chunk,其结构体和未分配的chunk结构体一样,只是不会使用fdbk两个成员,因为被分配后已经不需要这两个域了,其物理布局看起来像下图,chunk指针后面8字节的偏移处,即mem区域,是返回给用户的内存指针,该chunkheap控制信息占据了8个字节。



在调用malloc时,首先会将用户申请的size转换为系统可用的size

#definerequest2size(req) \
(((long)((req)+ (SIZE_SZ + MALLOC_ALIGN_MASK)) < \
(long)(MINSIZE+ MALLOC_ALIGN_MASK)) ? MINSIZE : \
(((req)+ (SIZE_SZ + MALLOC_ALIGN_MASK)) & ~(MALLOC_ALIGN_MASK)))

从这个宏定义中,我们可以获取3点信息:
1、系统可用的size和用户申请的size的差值,最小是0x042、系统可用的size最小为16个字节,即sizeof(malloc_chunk) 3、系统可用的size8字节对齐

说到这,或许你已经发现一个问题,如果用户申请20个字节的空间,系统会被分配24个字节,而chunkheap控制信息占了8个字节,那留给用户使用的只剩下18个字节。如此看来,岂不是会覆盖下一个chunk的“sizeof previous chunk区域?

为解答这个问题,我们先了解什么时候需要定位前一个chunk?只有在释放一块空间,判断前一个chunk是否空闲时才需要该动作。换而言之,当一个chunk被分配使用时,它根本不需要下一个chunk被释放来合并它,既然不需要,就利用起来吧。



从这一点讲,前一图中的“sizeof previous chunkifallocated的表述是不对的,应该是“sizeif previous chunkiffreed

2分箱管理

Bin的英文含义是“箱柜”,当我们谈到bin,是指某个双向链表的头字节,该链表的成员节点存放着某一特定范围size的空闲chunk,通过size,我们可以快速的定位binindex,然后遍历其指向的链表,寻找合适的chunk进行分配,或者将释放的chunk插入到链表中合适的地方。

程序定义了一个全局静态数组av_[]存放每种bin的头节点,Typedefstruct malloc_chunk *mbinptr;
Staticmbinptr av_[128*2+2]



数组类型mbinptr是一个指针,大小为4个字节,数组大小为1032个字节。这就引出一个问题,既然存放头节点,节点类型为malloc_chunk,一个节点就需要16bytes,总共有128个头节点,理应128*16=2048字节,现在av_[]1032个字节,是复合

放下所有的头节点信息的呢?对于头节点而言,有效的是fdbk,成员prev_sizesize并没有用到,既然没用,那空间能否节约下来呢?可以的,看看dlmalloc是如何做到的。

#definebin_at(i) ((mbinptr)((char*)&(av_[2*(i)+ 2]) - 2*SIZE_SZ))

以分配16bytes为例,其箱号为 16/82,于是,bin_at(2)= ((mbinptr)((char*)&(av_[6]) - 2*4)),最终bin_at(2)&av_[4]强行转换为mbinptr指针,用这个指针访问fdbk,得到的其实是av_[6]av_[7]中存放的内容。

av_[]的初始化可知,header[i].fdheader[i].bk指向&header[i],这用于表示Bin[i]

空。

我们来看看dlmalloc中两个特殊的分箱toplast_remainder

#definetop (bin_at(0)->fd) /* The topmost chunk */ #define last_remainder (bin_at(1)) /*remainder from last split */

Top最初被称为wildernesschunk,指向dlmalloc可用内存的最高端的边界chunk,因为在边界上,top是唯一一个可以任意扩展的块。Top比较特殊,它不受任何分箱管理,当其他分箱没有可用的chunk时才会用到top。在dlmalloc初始化刚完成时,整个受dlmalloc管理的内存就是一个chunktop即指向这个chunk

Last_remainder总是指向最近被分割chunk的剩下那一部分。如果malloc在分配时没有找到“精确匹配”的块,则优先去查看last_remainder是否够用。从局部性原理来讲,连续申请分配内存的代码总是趋向于有共同的生命周期,它们释放的chunk也就有很大的机会合并成一个大的chunk



为达到快速检索分箱的目的,dlmalloc使用了一个小技巧

#definebinblocks (bin_at(0)->size)/* bitvector of nonempty blocks */

即用binblocks建立了所有分箱的一个bitmapbinblocksbit来表示连续的4个相邻

bin是否为空,只要有一个不为空,其对应的bit1Binblocks其实就是av_[1]

一个32位数据类型,32×4128,正好对应128bins。扫描时先判断binblocks

相应位,只有当bit不为1时才会真正的去扫描对应的bin

3其他

? Dlmalloc分配空间是通过MORECORE进行的,而MORECORE即被定义为

sbrk,因此外部需提供sbrk函数,并可通过该函数确定所使用空间的地址范围

? 具体若干函数的解释见《malloc函数解释

显示全文