dlmalloc解析连载(4)

来源:互联网 发布:淘宝歆曼商学院怎么样 编辑:程序博客网 时间:2024/04/28 15:28

原文地址:http://blog.chinaunix.net/uid-7907749-id-2037210.html


上两篇讲解的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_chunkmalloc_tree_chunkmalloc_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;

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

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

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

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

top topsize也是指向一个chunk块(topsize记录该chunk块大小),该chunk块比较特殊,它位于当前活跃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_globalis_initialized很明朗,无需多说。

 

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


原创粉丝点击