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))

本文通过Glibc的内存暴增问题,主要介绍了系统的内存管理问题,具体如下: 目录 1. 问题 2. 基础知识 2.1 X86平台Linux进程内存布局 2.1.1 32位模式下进程内存经典布局 2.1.2 32位模式下进程默认内存布局 2.1.3 64位模式下进程内存布局 2.2 操作系统内存分配的相关函数 2.2.1 Heap操作相关函数 2.2.2 Mmap映射域操作相关函数 3. 概述 3.1 内存管理一般性描述 3.1.1 内存管理的方法 3.1.2 内存管理器的设计目标 3.1.3 常见C内存管理程序 3.2 Ptmalloc内存管理概述 3.2.1 简介 3.2.2 内存管理的设计假设 3.2.3 内存管理数据结构概述 3.2.4 内存分配概述 3.2.5 内存回收概述 3.2.6 配置选项概述 3.2.7 使用注意事项 4. 问题分析及解决 5. 源代码分析 5.1 边界标记法 5.2 分箱式内存管理 5.2.1 Small bins 5.2.2 Large bins 5.2.3 Unsorted bin 5.2.4 Fast bins 5.3 核心结构体分析 5.3.1 malloc_state 5.3.2 Malloc_par 5.3.3 分配的初始化 5.4 配置选项 5.5 Ptmalloc的初始化 5.5.1 Ptmalloc未初始化时分配/释放内存 5.5.2 ptmalloc_init()函数 5.5.3 ptmalloc_lock_all(),ptmalloc_unlock_all(),ptmalloc_unlock_all2() 5.6 多分配支持 5.6.1 Heap_info 5.6.2 获取分配 5.6.3 Arena_get2() 5.6.4 _int_new_arena() 5.6.5 New_heap() 5.6.6 get_free_list()和reused_arena() 5.6.7 grow_heap(),shrink_heap(),delete_heap(),heap_trim() 5.7 内存分配malloc 5.7.1 public_mALLOc() 5.7.2 _int_malloc() 5.8 内存释放free 5.8.1 Public_fREe() 5.8.2 _int_free() 5.8.3 sYSTRIm()和munmap_chunk(
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值