ptmalloc源码分析 - 分配器状态机malloc_state(02)

 

目录

一、Linux系统的堆栈结构

二、主分配区数据结构malloc_state

三、状态机组织chunk的方式

1. 基础类型的bins:fast、unsorted、small、large

2. bins之间的步长关系

3. 特殊类型的bins:top chunk、mmaped chunk、last remainder chunk

四、多线程争夺问题的解决

五、状态机的初始化malloc_init_state

六、BINS的宏定义和操作

1. BINS的基础定义字段

2. BINS的基本查找函数


一、Linux系统的堆栈结构


Linux的内存空间地址从低到高一般分为五个部分:内核空间、栈区域、堆区域、BBS段、数据段和代码段

  • 内核空间:我们在编写应用程序(非内核空间程序)的时候,这一块地址我们是不能够使用的
  • 栈区域:程序中局部变量、函数参数、返回地址的地方地址。地址从低到高分配
  • 堆区域:由malloc,calloc等创建的空间,是运行的时候由程序申请的。地址由高到低
  • BBS段:未初始化或初值为0的全局变量和静态局部变量
  • 数据段:已初始化且初值非0的全局变量和静态局部变量
  • 代码段:可执行代码、字符串字面值、只读变量

所以,我们使用ptmalloc的内存分配的时候,一般都是在堆上进行操作的。说白了,ptmalloc就是通过管理堆上的内存分配,来管理整个用户层malloc函数发起的内存请求

  • 通过brk方式分配的内存,一般都是从高地址到低地址;(堆的操作,操作系统提供了brk的函数,glibc提供了sbrk的函数)
  • 通过mmap方式分配的内存,一般都是从低地址到高地址的。
#include <unistd.h>
int brk( const void *addr )
void* sbrk ( intptr_t incr );
  • Brk:参数设置为新的brk上界地址,成功返回1,失败返回0;

  • Sbrk:参数为申请内存的大小,返回heap新的上界brk的地址

#include <sys/mman.h>
void *mmap(void *addr, size\_t length, int prot, int flags, int fd, off\_t offset);
int munmap(void *addr, size_t length);
  • mmap:第一种用法是映射此盘文件到内存中;第二种用法是匿名映射,不映射磁盘文件,而向映射区申请一块内存。
  • munmap:函数用于释放内存。

二、主分配区数据结构malloc_state


ptmalloc通过malloc_state的状态机来管理内存的分配。malloc_state主要用来管理分配的内存块,比如是否有空闲的chunk,有什么大小的空闲chunk 等等。(chunk是内存管理的最小单元,后面一章会重点讲解)。当用户层调用malloc/free等函数的时候,都会通过ptmalloc内核模块进行内存的分配,每一块从操作系统上分配的内存,都会使用malloc_state结构体来管理。

我们先看一下malloc_state的关键数据结构

  • mutex:线程锁,当多线程进行内存分配竞争的时候,需要首先拿到该锁才能进行分配区上的操作。
  • flags:记录了分配区的一些标志,比如 bit0 记录了分配区是否有 fast bin chunk ,bit1 标识分配区是否能返回连续的虚拟地址空间
  • have_fastchunks:用于标记是否有fast bins。
  • fastbinsY:fast bins是bins的高速缓冲区,大约有10个定长队列。当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。
  • top:指向分配区的 top chunk。top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。 
  • last_remainder:最新的 chunk 分割之后剩下的那部分
  • bins:用于存储 unstored bin,small bins 和 large bins 的 chunk 链表。
  • binmap:ptmalloc 用一个 bit 来标识某一个 bin 中是否包含空闲 chunk 。
  • next:分配区全局链表,主分配区放头部,新加入的分配区放main_arean.next 位置。
  • next_free:空闲的分配区
/**
 * 全局malloc状态管理
 */
struct malloc_state
{
  /* Serialize access. 同步访问互斥锁 */
  __libc_lock_define (, mutex);

  /* Flags (formerly in max_fast).
   * 用于标记当前主分配区的状态
   *  */
  int flags;

  /* Set if the fastbin chunks contain recently inserted free blocks.  */
  /* Note this is a bool but not all targets support atomics on booleans.  */
  /* 用于标记是否有fastchunk */
  int have_fastchunks;

  /* Fastbins fast bins。
   * fast bins是bins的高速缓冲区,大约有10个定长队列。
   * 当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。
   * */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  /* Top chunk :并不是所有的chunk都会被放到bins上。
   * top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。 */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above
   * 常规 bins chunk的链表数组
   * 1. unsorted bin:是bins的一个缓冲区。当用户释放的内存大于max_fast或者fast bins合并后的chunk都会进入unsorted bin上
   * 2. small bins和large bins。small bins和large bins是真正用来放置chunk双向链表的。每个bin之间相差8个字节,并且通过上面的这个列表,
   * 可以快速定位到合适大小的空闲chunk。
   * 3. 下标1是unsorted bin,2到63是small bin,64到126是large bin,共126个bin
   * */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins
   * 表示bin数组当中某一个下标的bin是否为空,用来在分配的时候加速
   * */
  unsigned int binmap[BINMAPSIZE];

  /* 分配区全局链表:分配区链表,主分配区放头部,新加入的分配区放main_arean.next 位置 Linked list */
  struct malloc_state *next;

  /* 分配区空闲链表 Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

三、状态机组织chunk的方式


ptmalloc的空闲chunk都是通过在malloc_state上的bins数组来管理的。

1. 基础类型的bins:fast、unsorted、small、large


一共分为四种类型的bins:fast bins、 unsorted bin、small bins和large bins。

  • fast bins:fast bins是bins的高速缓冲区,大约有10个定长队列。当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。当用户下次需要申请内存的时候首先会到fast bins上寻找是否有合适的chunk,然后才会到bins上空闲链表里面查找的chunk。ptmalloc会遍历fast bin,看是否有合适的chunk需要合并到bins上。主要放置在fastbinsY数组上。
  • unsorted bin:是bins的一个缓冲区,bins数组下标为1的即是unstored bin。当用户释放的内存大于max_fast或者fast bins合并后的chunk都会进入unsorted bin上。当用户malloc的时候,先会到unsorted bin上查找是否有合适的bin,如果没有合适的bin,ptmalloc会将unsorted bin上的chunk放入bins上,然后到bins上查找合适的空闲chunk。
  • small bins:小于512字节(64位机器1024字节)的chunk被称为small chunk,而保存small chunks的bin被称为small bin。数组从2开始编号到63,前62个bin为small bins,small bin每个bin之间相差8个字节(64位16字节),同一个small bin中的chunk具有相同大小。起始bin大小为16字节(64位系统32)。
  • large bins:大于等于512字节(64位机器1024字节)的chunk被称为large chunk,而保存large chunks的bin被称为large bin。位于small bins后面,数组编号从64开始,后64个bin为large bins。同一个bin上的chunk,可以大小不一定相同。large bins都是通过等差步长的方式进行拆分。(以32位系统为例,前32个bin步长64,后16个bin步长512,后8个步长4096,后四个32768,后2个262144)(编号63到64的步长跟)。起始bin大小为512字节(64位系统1024)。

2. bins之间的步长关系


然后再看一下,small bin和large bin每个bin之间的步长等差数列。在32位系统下面,前63个bin(small bins)的步长为8,后32个bins(large bins)步长64,以此类推。

  • small bins一个62个,每个bins之间的步长一致,32位系统步长8,64位系统步长16;
  • large bins 一共32+16+8+4+2+1=63个 + 起始bin(512或者1024)。以32位系统为例,前32个bin步长64,后16个bin步长512,后8个步长4096,后四个32768,后2个262144,后一个不限制
    Bins for sizes < 512 bytes contain chunks of all the same size, spaced
    8 bytes apart. Larger bins are approximately logarithmically spaced:

    64 bins of size       8
    32 bins of size      64
    16 bins of size     512
     8 bins of size    4096
     4 bins of size   32768
     2 bins of size  262144
     1 bin  of size what's left

3. 特殊类型的bins:top chunk、mmaped chunk、last remainder chunk


除了上面可见的bins管理之外,还是三种例外的chunk管理方式:top chunk,mmaped chunk 和last remainder chunk

Top chunk:top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。top chunk大小比用户所请求大小还大的时候,top chunk会分为两个部分:User chunk(用户请求大小)和Remainder chunk(剩余大小)。其中Remainder chunk成为新的top chunk。当top chunk大小小于用户所请求的大小时,top chunk就通过sbrk(main arena)或mmap(thread arena)系统调用来扩容。

mmaped chunk:当分配的内存非常大(大于分配阀值,默认128K)的时候,需要被mmap映射,则会放到mmaped chunk上,当释放mmaped chunk上的内存的时候会直接交还给操作系统。

Last remainder chunk:Last remainder chunk是另外一种特殊的chunk,就像top chunk和mmaped chunk一样,不会在任何bins中找到这种chunk。当需要分配一个small chunk,但在small bins中找不到合适的chunk,如果last remainder chunk的大小大于所需要的small chunk大小,last remainder chunk被分裂成两个chunk,其中一个chunk返回给用户,另一个chunk变成新的last remainder chunk。

 

四、多线程争夺问题的解决


ptmalloc的分配器为了解决多线程争夺问题,分为主分配区main_area和非主分配区thread_arena

  • 每个进程有一个主分配区,也可以允许有多个非主分配区。
  • 主分配区可以使用brk和mmap来分配,而非主分配区只能使用mmap来映射内存块
  • 非主分配区的数量一旦增加,则不会减少。
  • 主分配区和非主分配区形成一个环形链表进行管理。通过malloc_state->next来链接

我们可以看一下一个线程调用malloc的时候的流程以及分配区的状态:

  1. 当一个线程使用malloc分配内存的时候,首选会检查该线程环境中是否已经存在一个分配区,如果存在,则对该分配区进行加锁,并使用该分配区进行内存分配
  2. 如果分配失败,则遍历链表中获取的未加锁的分配区
  3. 如果整个链表都没有未加锁的分配区,则ptmalloc开辟一个新的分配区,假如malloc_state->next全局队列,并该线程在改内存分区上进行分配
  4. 当释放这块内存的时候,首先获取分配区的锁,然后释放内存,如果其他线程正在使用,则等待其他线程
  /* 分配区全局链表:分配区链表,主分配区放头部,新加入的分配区放main_arean.next 位置 Linked list */
  struct malloc_state *next;

  /* 分配区空闲链表 Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

五、状态机的初始化malloc_init_state


状态机的初始化,通过malloc_init_state。初始化过程主要做了三件事情:

  • 将bins进行初始化,生成bins数组
  • 处理fastchunk的状态
  • 初始化Top chunk,默认指向了unsorted bin上的第一个chunk
static void
malloc_init_state (mstate av)
{
  int i;
  mbinptr bin;

  /* Establish circular links for normal bins  */
  /* NBINS=128 */
  /**
   * 说明:ptmalloc通过bins数组来管理chunk双向链表,初始化的chunk链表指针都指向了自己
   * 1. bins上管理三种bin:unsorted bin、small bins和large bins
   * 2. 下标默认从1开始,其中下标为1的,则是unsorted bin
   */
  for (i = 1; i < NBINS; ++i)
    {
      bin = bin_at (av, i);
      bin->fd = bin->bk = bin;
    }

#if MORECORE_CONTIGUOUS
  if (av != &main_arena)
#endif
  set_noncontiguous (av);
  if (av == &main_arena)
    set_max_fast (DEFAULT_MXFAST);
  /* 默认fastchunk 是false的,没有被初始化的 */
  atomic_store_relaxed (&av->have_fastchunks, false);

  /* 初始化Top chunk,默认指向了unsorted bin上的第一个chunk */
  av->top = initial_top (av);
}


//通过bin_at方法,找到bin对应位置
/* addressing -- note that bin_at(0) does not exist */
#define bin_at(m, i) \
  (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))			      \
             - offsetof (struct malloc_chunk, fd))

六、BINS的宏定义和操作


前面说过,我们的空闲chunk,都会在bins上管理。ptmalloc提供了一些方便的宏函数,用于操作bin上的chunk。

1. BINS的基础定义字段


  1. NBINS:值为128,用于循环遍历bin的个数,实际bin的个数为127个,因为下标从1开始
  2. NSMALLBINS:定义了small bin的个数为64个,large bin个数为62个
  3. SMALLBIN_WIDTH:small bin的步长宽度,32位系统8个字节,64位系统16个字节
  4. MIN_LARGE_SIZE:large bin最小的值是64 * SMALLBIN_WIDTH,所以32位系统是512字节,64位系统是1024字节
#define NBINS             128 //默认128个bins
#define NSMALLBINS         64 //small bin的个数
#define SMALLBIN_WIDTH    MALLOC_ALIGNMENT //small bin的长度
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
#define MIN_LARGE_SIZE    ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)

2. BINS的基本查找函数


  1. bin_at:通过下标找到bin

  2. next_bin:通过bin上的一个chunk,找到物理地址的下一个chunk地址

  3. first和last:获取空闲bins上的chunk双向链表指针,找到前后的chunk

  4. bin_index:将 sz 大小转换成对应的数组下标。(判断属于 large bin 还是 small bin)。例:第一个largebin的起始大小为1024,那么1024>>6=16,所以其在bins数组中的下标为48+16=64

/* addressing -- note that bin_at(0) does not exist */
//通过下标找到bin
#define bin_at(m, i) \
  (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))			      \
             - offsetof (struct malloc_chunk, fd))

/* analog of ++bin */
//通过bin上的一个chunk,找到物理地址的下一个chunk地址
#define next_bin(b)  ((mbinptr) ((char *) (b) + (sizeof (mchunkptr) << 1)))

/* Reminders about list directionality within bins */
//在bins上管理的chunk是通过fd/bk双向链表指针链接起来的
//first=获取前一个指针地址 last=获取后一个指针地址
#define first(b)     ((b)->fd)
#define last(b)      ((b)->bk)
/* sz 大小属于 small bins 置 1,否则置 0 */
#define in_smallbin_range(sz)  \
  ((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)

/* 将 sz 大小 转换成对应的数组下标。(对 samll bin) */
#define smallbin_index(sz) \
  ((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) : (((unsigned) (sz)) >> 3))\
   + SMALLBIN_CORRECTION)

/* 将 sz 大小 转换成对应的数组下标。(对 32 位 large bin) */
#define largebin_index_32(sz)                                                \
  (((((unsigned long) (sz)) >> 6) <= 38) ?  56 + (((unsigned long) (sz)) >> 6) :\
   ((((unsigned long) (sz)) >> 9) <= 20) ?  91 + (((unsigned long) (sz)) >> 9) :\
   ((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
   ((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
   ((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
   126)

#define largebin_index_32_big(sz)                                            \
  (((((unsigned long) (sz)) >> 6) <= 45) ?  49 + (((unsigned long) (sz)) >> 6) :\
   ((((unsigned long) (sz)) >> 9) <= 20) ?  91 + (((unsigned long) (sz)) >> 9) :\
   ((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
   ((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
   ((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
   126)

// XXX It remains to be seen whether it is good to keep the widths of
// XXX the buckets the same or whether it should be scaled by a factor
// XXX of two as well.
/* 将 sz 大小 转换成对应的数组下标。(对 64 位 large bin) */
/* 第一个largebin的起始大小为1024,那么1024>>6=16,所以其在bins数组中的下标为48+16=64 */
#define largebin_index_64(sz)                                                \
  (((((unsigned long) (sz)) >> 6) <= 48) ?  48 + (((unsigned long) (sz)) >> 6) :\
   ((((unsigned long) (sz)) >> 9) <= 20) ?  91 + (((unsigned long) (sz)) >> 9) :\
   ((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
   ((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
   ((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
   126)

/* 将 sz 大小 转换成对应的数组下标。(根据 SIZE_SZ 判断 large bin 转换宏)*/
#define largebin_index(sz) \
  (SIZE_SZ == 8 ? largebin_index_64 (sz)                                     \
   : MALLOC_ALIGNMENT == 16 ? largebin_index_32_big (sz)                     \
   : largebin_index_32 (sz))

/* 将 sz 大小 转换成对应的数组下标。(判断属于 large bin 还是 small bin) */
#define bin_index(sz) \
  ((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页