ptmalloc

1ptmalloc3个层级:arenabinchunk

1) arena

a) arena是内存分配区,主线程会创建main arena, 其他线程会创建thread arena, 也就是存在多个arena,这样可以避免锁的竞争。main arena会通过sbrk()来扩容,它始终是一个连续的内存块。thread arena 不是说每一个线程都是创建自身分配区,它的数量有上限,32bitcpu核心数的2倍,64bit是核心数的8倍,当它的空间不够时,它通过mmap()重新申请一块内存来实现扩容,所以thread arena的内存区域有可能不连续,它通过内部的heap_info结构体来管理多个内存区域。

 

 

b) 上图中有个malloc_state结构体实例,它是用来存放arena的信息,单向链表,串联了不同的arena, 它包含锁,bins, topchunk,remainder chunk等信息。其中main_arenamalloc_state是全局静态实例,因此存放在.data区,thread arenamalloc_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[NFASTBINS];
  /* 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[NBINS * 2 - 2];
  /* Bitmap of bins */
  unsigned int     binmap[BINMAPSIZE];
  /* 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;
};

 

c) thread arena空间扩展是通过mmap实现的,因此内存区域不是连续的,每块内存区域对应一个head_info结构体,通过单向链表串联起来。heap_info结构体数据也是存放在自身堆中。

struct _heap_info {
  mstate ar_ptr; /* Arena for this heap. */
  struct _heap_info *prev; /* Previous heap. */
  size_t size;   /* Current size in bytes. */
  size_t mprotect_size; /* Size in bytes that has been mprotected
   PROT_READ|PROT_WRITE.  */
  /* Make sure the following data is properly aligned, particularly
     that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
     MALLOC_ALIGNMENT. */
  char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;

 

2) chunk

chunk是最小的malloc分配单元,arena内存区域会分成多个大小不一的chunk,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;
  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

 

这个结构体声明有一定的误导性,从上述注释字段可以看出,有些字段在不同场景是不使用的,例如prev_size在前一个chunk是空闲的才使用此字段,fd和bk只有在本chunk空闲时才会被使用,chunk实现了使用边界标记的方式分割内存块。

已分配的chunk如下,size字段的后3位分别代表

N--NON_MAIN_ARENA 0表示属于main arena, 1表示自身属于thread arena

M--IS_MMAPED  0表示是非直接通过mmap获取的内存,1表示通过mmap获取的内存,释放用unmap

P--PREV_INUSE 0表示前一个chunk未被使用,1表示已使用

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |             Size of previous chunk, if allocated            | |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |             Size of chunk, in bytes                      |N|M|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |             User data starts here...                          .
    .                                                               .
    .             (malloc_usable_size() bytes)                      .
    .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |             Size of chunk                                     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

空闲的chunk会有双向链表相连(fast bins为了更快的操作内存,使用单向链表,只用到了fd字段),如下:

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |             Size of previous chunk                            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    `head:' |             Size of chunk, in bytes                         |P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |             Forward pointer to next chunk in list             |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |             Back pointer to previous chunk in list            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |             Unused space (may be 0 bytes long)                .
    .                                                               .
    .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    `foot:' |             Size of chunk, in bytes                           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

3) bin 是一个链表,它会串联不同类型的chunk,从而将其管理起来,根据不同大小的chunkbin有几种:fastbinsmallbinlargebinunsortedbin

a) fast bin  

malloc_state中的fastbinsY[NFASTBINS],NFASTBINS为10,每一个元素都是一个单向链表,存放对应大小的空闲chunk,对于linux64bit系统来说,最小是32B,然后每个数组下标递增16B。对于小块内存申请,直接从fast bin中获取,释放也直接存回fast bin。对比其他三种bin,为追求高效,只有fast bin使用chunk的单向链表,通过cas操作增删chunk指针。fast bin会被合并操作,回收碎片内存,以下四种情况会执行合并操作

 

  (1)small bin中未找到空闲chunk后,执行合并

 

  (2)要到large bin中寻找合适chunk,先执行合并,避免太多碎片

 

  (3)调用brk或者mmap函数前再合并一次

 

  (4)free释放的时候,如果前后chunk合并的size > 64K 则先合并fastbin,因为后面要收缩top chunk

 

b) small bin

malloc_state中的 bins[NBINS * 2 - 2],NBINS 宏定义为128, 第0下标表示unsortedbin,1~128表示smallbin,剩下的表示largebin。小于1024B(64bit)/512B(32bit)的chunk块存放在此容器中,chunk块使用双向链表,chunk的大小也是按照数组下标递增

c) large bin

         大于等于1024B(64bit)/512B(32bit)的chunk块存放在此容器中

d) unsorted bin

         起一个缓冲作用, 其他bin合并后的新chunk块先存放在这里面,chunk块大小无限制,执行申请内存时,在fast bin 和small bin中未找到chunk后,会从unsorted bin中寻找空闲chunk, 如果没有找到, 会将unsorted bin上的chunk重新分配至其他bin上。

2、malloc流程

1) 找内存分配器arena。

查找线程本地存储key值为arena_key对应的malloc_state结构体指针,即查找arena,如果沒有arena,如果未查过上限数,则创建新的thread arena,超过上限则复用之前的arena(main_arena在ptmalloc_init()中被存入arena_key中)。首次申请的arena内存区块不小于32k,不大于32bit512K/64bit32M,获取到arena后再附上锁。

2) 规范化入参长度size

判断申请size是否超过上限值(unsigned long - 2*最小长度),是否小于最小长度(chunk结构体前四个指针的长度,32位系统16字节,64位系统32字节),是否对齐(对齐长度为2*sizeof(size_t), 32位系统8字节,64位系统16字节)

3) if申请size如果小于max_fast(默认值32bit64字节/64bit128字节,可通过mallopt设置),则从fast bins上寻找合适的chunk,如果未找到则继续往下走

4) if申请的size小于1024字节(32位512字节),从small bins寻找合适的空闲chunk,如果未找到,则合并fastbin至unsortedbin

5) else 如果size >= 1024,且有fastbin, 先合并fastbin至unsortedbin。

6) 遍历unsortedbin,针对unsortedbin里的每一个chunk,先移出unsortedbin,如果此chunk大小合适,则返回此chunk的mem指针,否则迁入smallbin或者largebin,如果最后只剩一个remainder chunk,且申请的size小于1024,且remainder chunk的size大于申请的对齐字节,则切割remainder chunk,分配内存。

6) 至此,fastbin和smallbin都没找到空闲chunk,fastbin也经过合并至unsortedbin,然后分配至smallbin和largebin。这时,在largebin中寻找,如果仍没有找到,则从top chunk中切割内存,

7) 如果top chunk的size也不够申请的size+chunk的头两个字节长度,则调用sysmalloc再向系统申请内存,如果size >128k,则直接调用mmap在mmap区域申请内存作为mmap类型的chunk返回给用户,否则,对于main_arena, 调用sbrk扩展top_chunk的长度,对于thread_arena, 调用mmap在堆区申请新的arena分配区来增加新的top chunk的长度

 

3、free的过程

    1) 如果参数为0,直接return

    2) 判断指针所属chunk是否IS_MMAPPED, 如果是,直接调用unmap释放内存

    3)if释放的size小于max_fast(默认值32bit64字节/64bit128字节,可通过mallopt设置), 获取所属arena的malloc_state的锁,将chunk插入fastbin, 并且设置malloc_state的fastbin标志位flag为0(表示有fastbin非空),否则进入4)

    4) else 前一个chunk空闲,则合并前一个chunk, 如果后一个chunk是空闲的,且非top chunk,合并后一个chunk,否则置空后一个chunk的P位,合并后的新chunk存入unsorted bin

    5) 如果后一个chunk是top chunk,合入top chunk

    6) 判断合并后的新chunk是否大于FASTBIN_CONSOLIDATION_THRESHOLD(64k),如果是的,合并fastbin至unsortedbin

    7) 在6)的前提下,如果是main_arena,且top chunk的size >= trim_threshold(默认128K),则systrim,调用sbrk进行内存回收。如果是thread_arena, 则heap_trim,调用madvise 进行内存回收(thread_arena不判断trim_threshold,它总是会缩减内存)

posted @ 2023-07-23 22:42  ho966  阅读(119)  评论(0编辑  收藏  举报