mqzhuang 发表于 2013-1-14 07:14:03

Glibc内存管理--ptmalloc2源代码分析(十六)

5.3    核心结构体分析

每个分配区是structmalloc_state的一个实例,ptmalloc使用malloc_state来管理分配区,而参数管理使用struct malloc_par,全局拥有一个唯一的malloc_par实例。
 
5.3.1  malloc_state
 
stuct  malloc_state的定义如下:
struct malloc_state {/* Serialize access.*/mutex_t mutex;/* Flags (formerly in max_fast).*/int flags;#if THREAD_STATS/* Statistics for locking.Only used if THREAD_STATS is defined.*/long stat_lock_direct, stat_lock_loop, stat_lock_wait;#endif/* Fastbins */mfastbinptr      fastbinsY;/* Base of the topmost chunk -- not otherwise kept in a bin */mchunkptr      top;/* The remainder from the most recent split of a small request */mchunkptr      last_remainder;/* Normal bins packed as described above */mchunkptr      bins;/* Bitmap of bins */unsigned int   binmap;/* Linked list */struct malloc_state *next;#ifdef PER_THREAD/* Linked list for free arenas.*/struct malloc_state *next_free;#endif/* Memory allocated from the system in this arena.*/INTERNAL_SIZE_T system_mem;INTERNAL_SIZE_T max_system_mem;}; Mutex用于串行化访问分配区,当有多个线程访问同一个分配区时,第一个获得这个mutex的线程将使用该分配区分配内存,分配完成后,释放该分配区的mutex,以便其它线程使用该分配区。
Flags记录了分配区的一些标志,bit0用于标识分配区是否包含至少一个fast bin chunk,bit1用于标识分配区是否能返回连续的虚拟地址空间。
/*FASTCHUNKS_BIT held in max_fast indicates that there are probablysome fastbin chunks. It is set true on entering a chunk into anyfastbin, and cleared only in malloc_consolidate.The truth value is inverted so that have_fastchunks will be trueupon startup (since statics are zero-filled), simplifyinginitialization checks.*/#define FASTCHUNKS_BIT      (1U)#define have_fastchunks(M)   (((M)->flags &FASTCHUNKS_BIT) == 0)#ifdef ATOMIC_FASTBINS#define clear_fastchunks(M)    catomic_or (&(M)->flags, FASTCHUNKS_BIT)#define set_fastchunks(M)      catomic_and (&(M)->flags, ~FASTCHUNKS_BIT)#else#define clear_fastchunks(M)    ((M)->flags |=FASTCHUNKS_BIT)#define set_fastchunks(M)      ((M)->flags &= ~FASTCHUNKS_BIT)#endif 上面的宏用于设置或是置位flags中fast chunk的标志位bit0,如果bit0为0,表示分配区中有fastchunk,如果为1表示没有fast chunk,初始化完成后的malloc_state实例中,flags值为0,表示该分配区中有fast chunk,但实际上没有,试图从fast bins中分配chunk都会返回NULL,在第一次调用函数malloc_consolidate()对fast bins进行chunk合并时,如果max_fast大于0,会调用clear_fastchunks宏,标志该分配区中已经没有fast chunk,因为函数malloc_consolidate()会合并所有的fast bins中的chunk。clear_fastchunks宏只会在函数malloc_consolidate()中调用。当有fast chunk加入fast bins时,就是调用set_fastchunks宏标识分配区的fast bins中存在fast chunk。
/*NONCONTIGUOUS_BIT indicates that MORECORE does not return contiguousregions.Otherwise, contiguity is exploited in merging together,when possible, results from consecutive MORECORE calls.The initial value comes from MORECORE_CONTIGUOUS, but ischanged dynamically if mmap is ever used as an sbrk substitute.*/#define NONCONTIGUOUS_BIT   (2U)#define contiguous(M)          (((M)->flags &NONCONTIGUOUS_BIT) == 0)#define noncontiguous(M)       (((M)->flags &NONCONTIGUOUS_BIT) != 0)#define set_noncontiguous(M)   ((M)->flags |=NONCONTIGUOUS_BIT)#define set_contiguous(M)      ((M)->flags &= ~NONCONTIGUOUS_BIT)Flags的bit1如果为0,表示MORCORE返回连续虚拟地址空间,bit1为1,表示MORCORE返回非连续虚拟地址空间,对于主分配区,MORECORE其实为sbr(),默认返回连续虚拟地址空间,对于非主分配区,使用mmap()分配大块虚拟内存,然后进行切分来模拟主分配区的行为,而默认情况下mmap映射区域是不保证虚拟地址空间连续的,所以非住分配区默认分配非连续虚拟地址空间。
 
Malloc_state中声明了几个对锁的统计变量,默认没有定义THREAD_STATS,所以不会对锁的争用情况做统计。
fastbinsY拥有10(NFASTBINS)个元素的数组,用于存放每个fast chunk链表头指针,所以fast bins最多包含10个fast chunk的单向链表。
top是一个chunk指针,指向分配区的topchunk。
last_remainder是一个chunk指针,分配区上次分配smallchunk时,从一个chunk中分裂出一个small chunk返回给用户,分裂后的剩余部分形成一个chunk,last_remainder就是指向的这个chunk。
bins用于存储unstoredbin,small bins和large bins的chunk链表头,small bins一共62个,large bins一共63个,加起来一共125个bin。而NBINS定义为128,其实bin和bin都不存在,bin为unsorted bin的chunk链表头,所以实际只有126bins。Bins数组能存放了254(NBINS*2 – 2)个mchunkptr指针,而我们实现需要存储chunk的实例,一般情况下,chunk实例的大小为6个mchunkptr大小,这254个指针的大小怎么能存下126个chunk呢?这里使用了一个技巧,如果按照我们的常规想法,也许会申请126个malloc_chunk结构体指针元素的数组,然后再给链表申请一个头节点(即126个),再让每个指针元素正确指向而形成126个具有头节点的链表。事实上,对于malloc_chunk类型的链表“头节点”,其内的prev_size和size字段是没有任何实际作用的,fd_nextsize和bk_nextsize字段只有large bins中的空闲chunk才会用到,而对于large bins的空闲chunk链表头不需要这两个字段,因此这四个字段所占空间如果不合理使用的话那就是白白的浪费。我们再来看一看128个malloc_chunk结构体指针元素的数组占了多少内存空间呢?假设SIZE_SZ的大小为8B,则指针的大小也为8B,结果为126*2*8=2016字节。而126个malloc_chunk类型的链表“头节点”需要多少内存呢?126*6*8=6048,真的是6048B么?不是,刚才不是说了,prev_size,size,fd_nextsize和bk_nextsize这四个字段是没有任何实际作用的,因此完全可以被重用(覆盖),因此实际需要内存为126*2*8=2016。Bins指针数组的大小为,(128*2-2)*8=2032,2032大于2016(事实上最后16个字节都被浪费掉了),那么这254个malloc_chunk结构体指针元素数组所占内存空间就可以存储这126个头节点了。
binmap字段是一个int数组,ptmalloc用一个bit来标识该bit对应的bin中是否包含空闲chunk。
/*Binmap    To help compensate for the large number of bins, a one-level index    structure is used for bin-by-bin searching.`binmap' is a    bitvector recording whether bins are definitely empty so they can    be skipped over during during traversals.The bits are NOT always    cleared as soon as bins are empty, but instead only    when they are noticed to be empty during traversal in malloc.*//* Conservatively use 32 bits per map word, even if on 64bit system */#define BINMAPSHIFT      5#define BITSPERMAP       (1U << BINMAPSHIFT)#define BINMAPSIZE       (NBINS / BITSPERMAP)#define idx2block(i)   ((i) >> BINMAPSHIFT)#define idx2bit(i)       ((1U << ((i) & ((1U << BINMAPSHIFT)-1))))#define mark_bin(m,i)    ((m)->binmap |=idx2bit(i))#define unmark_bin(m,i)((m)->binmap &= ~(idx2bit(i)))#define get_binmap(m,i)((m)->binmap &   idx2bit(i)) binmap一共128bit,16字节,4个int大小,binmap按int分成4个block,每个block有32个bit,根据bin indx可以使用宏idx2block计算出该bin在binmap对应的bit属于哪个block。idx2bit宏取第i位为1,其它位都为0的掩码,举个例子:idx2bit(3)为 “0000 1000”(只显示8位)。mark_bin设置第i个bin在binmap中对应的bit位为1;unmark_bin设置第i个bin在binmap中对应的bit位为0;get_binmap获取第i个bin在binmap中对应的bit。
next字段用于将分配区以单向链表链接起来。
next_free字段空闲的分配区链接在单向链表中,只有在定义了PER_THREAD的情况下才定义该字段。
system_mem字段记录了当前分配区已经分配的内存大小。
max_system_mem记录了当前分配区最大能分配的内存大小。
5.3.2    Malloc_par
 
Struct malloc_par的定义如下:
struct malloc_par {/* Tunable parameters */unsigned long    trim_threshold;INTERNAL_SIZE_Ttop_pad;INTERNAL_SIZE_Tmmap_threshold;#ifdef PER_THREADINTERNAL_SIZE_Tarena_test;INTERNAL_SIZE_Tarena_max;#endif/* Memory map support */int            n_mmaps;int            n_mmaps_max;int            max_n_mmaps;/* the mmap_threshold is dynamic, until the user sets   it manually, at which point we need to disable any   dynamic behavior. */int            no_dyn_threshold;/* Cache malloc_getpagesize */unsigned int   pagesize;/* Statistics */INTERNAL_SIZE_Tmmapped_mem;INTERNAL_SIZE_Tmax_mmapped_mem;INTERNAL_SIZE_Tmax_total_mem; /* only kept for NO_THREADS *//* First address handed out by MORECORE/sbrk.*/char*            sbrk_base;};trim_threshold字段表示收缩阈值,默认为128KB,当每个分配区的topchunk大小大于这个阈值时,在一定的条件下,调用free时会收缩内存,减小top chunk的大小。由于mmap分配阈值的动态调整,在free时可能将收缩阈值修改为mmap分配阈值的2倍,在64位系统上,mmap分配阈值最大值为32MB,所以收缩阈值的最大值为64MB,在32位系统上,mmap分配阈值最大值为512KB,所以收缩阈值的最大值为1MB。收缩阈值可以通过函数mallopt()进行设置。
top_pad字段表示在分配内存时是否添加额外的pad,默认该字段为0。
mmap_threshold字段表示mmap分配阈值,默认值为128KB,在32位系统上最大值为512KB,64位系统上的最大值为32MB,由于默认开启mmap分配阈值动态调整,该字段的值会动态修改,但不会超过最大值。
arena_test和arena_max用于PER_THREAD优化,在32位系统上arena_test默认值为2,64位系统上的默认值为8,当每个进程的分配区数量小于等于arena_test时,不会重用已有的分配区。为了限制分配区的总数,用arena_max来保存分配区的最大数量,当系统中的分配区数量达到arena_max,就不会再创建新的分配区,只会重用已有的分配区。这两个字段都可以使用mallopt()函数设置。
n_mmaps字段表示当前进程使用mmap()函数分配的内存块的个数。
n_mmaps_max字段表示进程使用mmap()函数分配的内存块的最大数量,默认值为65536,可以使用mallopt()函数修改。
max_n_mmaps字段表示当前进程使用mmap()函数分配的内存块的数量的最大值,有关系n_mmaps <= max_n_mmaps成立。这个字段是由于mstats()函数输出统计需要这个字段。
no_dyn_threshold字段表示是否开启mmap分配阈值动态调整机制,默认值为0,也就是默认开启mmap分配阈值动态调整机制。
pagesize字段表示系统的页大小,默认为4KB。
mmapped_mem和max_mmapped_mem都用于统计mmap分配的内存大小,一般情况下两个字段的值相等,max_mmapped_mem用于mstats()函数。
max_total_mem字段在单线程情况下用于统计进程分配的内存总数。
sbrk_base字段表示堆的起始地址。
5.3.3 分配区的初始化
 
Ptmalloc定义了如下几个全局变量:

/* There are several instances of this struct ("arenas") in this   malloc.If you are adapting this malloc in a way that does NOT use   a static or mmapped malloc_state, you MUST explicitly zero-fill it   before using. This malloc relies on the property that malloc_state   is initialized to all zeroes (as is true of C statics).*/static struct malloc_state main_arena;/* There is only one instance of the malloc parameters.*/static struct malloc_par mp_;/* Maximum size of memory handled in fastbins.*/static INTERNAL_SIZE_T global_max_fast;main_arena表示主分配区,任何进程有且仅有一个全局的主分配区,mp_是全局唯一的一个malloc_par实例,用于管理参数和统计信息,global_max_fast全局变量表示fast bins中最大的chunk大小。
 
分配区main_arena初始化函数
/*Initialize a malloc_state struct.This is called only from within malloc_consolidate, which needsbe called in the same contexts anyway.It is never called directlyoutside of malloc_consolidate because some optimizing compilers tryto inline it at all call points, which turns out not to be anoptimization at all. (Inlining it in malloc_consolidate is fine though.)*/#if __STD_Cstatic void malloc_init_state(mstate av)#elsestatic void malloc_init_state(av) mstate av;#endif{int   i;mbinptr bin;/* Establish circular links for normal bins */for (i = 1; i < NBINS; ++i) {    bin = bin_at(av,i);    bin->fd = bin->bk = bin;}#if MORECORE_CONTIGUOUSif (av != &main_arena)#endif    set_noncontiguous(av);if (av == &main_arena)    set_max_fast(DEFAULT_MXFAST);av->flags |= FASTCHUNKS_BIT;av->top            = initial_top(av);} 分配区的初始化函数默认分配区的实例av是全局静态变量或是已经将av中的所有字段都清0了。初始化函数做的工作比较简单,首先遍历所有的bins,初始化每个bin的空闲链表为空,即将bin的fb和bk都指向bin本身。由于av中所有字段默认为0,即默认分配连续的虚拟地址空间,但只有主分配区才能分配连续的虚拟地址空间,所以对于非主分配区,需要设置为分配非连续虚拟地址空间。如果初始化的是主分配区,需要设置fast bins中最大chunk大小,由于主分配区只有一个,并且一定是最先初始化,这就保证了对全局变量global_max_fast只初始化了一次,只要该全局变量的值非0,也就意味着主分配区初始化了。最后初始化top chunk。
 
Ptmalloc参数初始化
/* Set up basic state so that _int_malloc et al can work.*/static voidptmalloc_init_minimal (void){#if DEFAULT_TOP_PAD != 0mp_.top_pad      = DEFAULT_TOP_PAD;#endifmp_.n_mmaps_max    = DEFAULT_MMAP_MAX;mp_.mmap_threshold = DEFAULT_MMAP_THRESHOLD;mp_.trim_threshold = DEFAULT_TRIM_THRESHOLD;mp_.pagesize       = malloc_getpagesize;#ifdef PER_THREAD# define NARENAS_FROM_NCORES(n) ((n) * (sizeof(long) == 4 ? 2 : 8))mp_.arena_test   = NARENAS_FROM_NCORES (1);narenas = 1;#endif} 主要是将全局变量mp_的字段初始化为默认值,值得一提的是,如果定义了编译选项PER_THREAD,会根据系统cpu的个数设置arena_test的值,默认32位系统是双核,64位系统为8核,arena_test也就设置为相应的值。
页: [1]
查看完整版本: Glibc内存管理--ptmalloc2源代码分析(十六)