dlmalloc(二)

来源:互联网 发布:python绝技pdf 百度云 编辑:程序博客网 时间:2024/06/06 09:48

        dlmalloc中,申请到的内存被分割成若干个内存块,dlmalloc采用两种不同的数据结构表示这些内存块。小内存块保存在链表中,用struct malloc_chunk表示;大内存块保存在树形结构中,用struct malloc_tree_chunk表示。struct malloc_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;};

        fd表示链表中后面一个malloc_chunk结构,bk表示链表中前一个malloc_chunk结构。head表示这个malloc_chunk代表内存块的大小,另外还包含了一些标志信息。prev_foot表示前一个malloc_chunk的大小,这里的"前一个"不是链表中的"前一个",而是与这个malloc_chunk地址相邻的"前一个"。通过prev_foot和size两个字段dlmalloc就可以快速找到地址相邻的前一个和后一个malloc_chunk结构。

        当内存块被分配给应用程序后,就会被从链表中摘除,这时malloc_chunk结构中的fd和bk两个字段就没有意义了,因此可以供应用程序使用。我们调用malloc()申请内存时,malloc()会返回一个指针,指向申请到的内存块的起始地址p,其实这个地址前还有一个malloc_chunk结构,我们可以通过p-8得到malloc_chunk结构的指针。反过来也可以通过malloc_chunk指针得到分配给应用程序的内存块的起始地址。为此dlmalloc定义了两个宏:

typedef struct malloc_chunk* mchunkptr;// 32位Linux系统中,TWO_SIZE_T_SIZES的值是8#define chunk2mem(p)        ((void*)((char*)(p)       + TWO_SIZE_T_SIZES))#define mem2chunk(mem)      ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES))

我们看下面这个例子:


        上面这块内存区域中包括两个内存块,分别为chunk1和chunk2,紧接着malloc_chunk结构的就是供应用程序使用的内存。按照前面的分析,fd和bk两个字段也可以供应用程序使用。因此ptr1 = chunk1 + 8,ptr2 = chunk2 + 8。还有一点需要注意的是,只有当前面一个chunk空闲时malloc_chunk结构中的prev_foot才保存前一个chunk的大小;当前面一个chunk分配给应用程序后,prev_foot字段也可以供应用程序使用。上图中,当chunk1分配给应用程序后,chunk2中的prev_foot字段就没有意义了,可以供应用程序使用。dlmalloc返回给应用程序的地址是ptr1,这个内存块的大小是size1 + 8 + 4。因此,malloc_chunk结构中,只有head字段永远不会挪作他用,其他三个字段都可以供应用程序使用,通过这种复用最大限度地减少了dlmalloc本身占用的内存。

        dlmalloc对应用程序申请的内存长度有限制,要求内存块长度(包括malloc_chunk结构占用的内存)必须是8字节的倍数。假如应用程序调用malloc(13)申请长度为13字节的内存块,dlmalloc最终分配内存块大小是24字节,除去malloc_chunk结构中head占用的4字节,分配给应用程序的内存块大小是20字节。当然,应用程序不要揣测内存块的实际大小,虽然dlmalloc分配了20字节,但是应用程序最好只使用13字节,不要使用剩余的7字节。否则有两方面后果:(1)应用程序显得混乱,其他人可能无法读懂你的代码。(2)返回多少字节与内存分配器的实现方式有关,换另外一种内存分配器可能返回的就不是20字节了,如果应用程序使用超过13个字节就可能覆盖其他数据了,程序移植性差。

        malloc_chunk结构可以表示的最小内存块是16字节,最大内存块是248字节,因此malloc_chunk可以表示16、24、32、40、......、248共30种长度的内存块。dlmalloc定义了30条链表,相同长度的空闲内存块保存在一个链表中。

        超过248字节的内存就属于大块内存了,大块内存用malloc_tree_chunk表示,这个数据结构定义如下:

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;};

其中prev_foot和head的定义跟malloc_chunk中的定义完全相同。那么其他几个字段表示什么含义呢?dlmalloc中小内存块只有30种情况,可以用30条链表存储;但是大内存块有无数种情况(256、264、272、......),因此就不能用链表表示了,大内存块保存在树形结构中,dlmalloc定义了32棵树存储大内存块,每棵树中存储若干种长度的内存块,每棵树保存的内存块范围如下:


dlmalloc中根据内存块大小计算所在树的编号的宏如下:

#define compute_tree_index(S, I)\{\  size_t X = S >> TREEBIN_SHIFT;  /* TREEBIN_SHIFT的值是8 */ \  if (X == 0)\    I = 0;\  else if (X > 0xFFFF)\    I = NTREEBINS-1;  /* NTREEBINS的值是32 */ \  else {\    unsigned int K;\    __asm__("bsrl %1,%0\n\t" : "=r" (K) : "rm"  (X));\    I =  (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\  }\}

如果感兴趣可以采用这个宏计算一下。我们看一下单棵树中保存的空闲内存块,以编号为0的树为例,这棵树中内存块的范围是[256, 384),按照前面规定内存块的大小必须是8的倍数,因此这棵树中保存的内存块长度分别为256, 264, 272, 280, 288, 296, 304, 312, 320, 328, 336, 344, 352, 360, 368, 376,共16种长度,每种长度的内存块作为树中一个节点。这棵树中可能保存了多个相同长度的内存块,这些相同长度的内存块构成了一棵链表,如下图所示:



现在回过头来看malloc_tree_chunk中各个字段的含义。

prev_foot表示前一个内存块的大小

head表示本内存块的大小

child表示两个子节点

parent表示父节点

index表示内存块所在树的索引号

fd表示链表中下一个内存块

bk表示链表中前面一个内存块

同样,这个结构中只有head字段保持不变,其他字段都可以供应用程序使用。


现在我们来看一个全局变量_gm_,这是struct malloc_state类型的变量,这个数据结构定义如下:

struct malloc_state {  binmap_t   smallmap;  mchunkptr  smallbins[(NSMALLBINS+1)*2];  binmap_t   treemap;  tbinptr    treebins[NTREEBINS];  mchunkptr  dv;  size_t     dvsize;  mchunkptr  top;  size_t     topsize;  char*      least_addr;  size_t     trim_check;  size_t     magic;  size_t     footprint;#if USE_MAX_ALLOWED_FOOTPRINT  size_t     max_allowed_footprint;#endif  size_t     max_footprint;  flag_t     mflags;#if USE_LOCKS  MLOCK_T    mutex;#endif /* USE_LOCKS */  msegment   seg;};static struct malloc_state _gm_;
        我们重点关注前8个字段。smallbins就是dlmalloc中定义的30条链表(加上长度为0和8的内存块,共32条链表)。smalbins[0]-smallbins[3]共16字节,表示一个malloc_chunk结构,对应长度为0的链表。smalbins[2]-smallbins[5]共16字节,表示一个malloc_chunk结构,对应长度为8的链表,以此类推。可以看到相邻两个malloc_chunk结构有重合,这是因为作为链表使用时,malloc_chunk结构中的prev_foot和head字段没有意义,因此可以重合使用。smallmap是smallbins的位图,某个比特置位表示对应的链表上有空闲内存块,比特清零表示对应的链表为空。treebins表示dlmalloc中32棵树,treemap是treebins的位图,置位表示对应树中有空闲内存块,清零表示对应树为空。dv是一个特殊的内存块,如果dlmalloc中找不到一个合适大小的内存块分配给应用程序,那么dlmalloc会将一个较大的内存块分割成两个较小的内存块,一块给应用程序使用,另外一块保存在dv中。下载再找不到合适大小的内存块时,如果dv大小大于应用程序请求的内存块,dlmalloc会将dv分割成两块,一块给应用程序,另一块仍保存在dv中;如果dv小于应用程序请求的内存块,dlmalloc首先将dv保存在链表或树中,然后挑选另外一个内存块分割,一块给应用程序,另一块保存在dv中。因此dlmalloc分配内存块的原则是先匹配大小,后匹配位置,尽量挑选合适大小的内存块给应用程序,实在找不到合适的内存块时就尽量从同一个位置分割内存块,以提高效率(程序执行的局部性原理)。dvsize就是dv表示内存块的大小。top是另外一个特殊的内存块,表示堆空间中对顶端的内存块。dlmalloc尽量不使用这个内存块,只有在_gm_中没有合适大小的内存块并且没有更大的内存块可供分割时才使用top中的内存。为什么尽量不要使用top呢?因为当top被占用时dlmalloc没办法释放其他空闲内存,dlmalloc收缩堆时必须从高地址向低地址收缩,所以主要高地址的内存被占用,即使堆中有再多的空闲内存也没办法释放。topsize表示top的大小。
        先说到这里,下篇文章中详细讲解malloc()申请内存的流程。

原创粉丝点击