堆内存(1) - ptmalloc2分配器

ptmalloc2分配器

ptmalloc2 分配给用户的内存都以 chunk 来表示,可以理解为 chunk 为分配释放内存的载体。下面根据glibc2.31对ptmalloc进行了分析。

chunk

malloc_chunk

malloc_chunk用于管理内存块,该结构体维护了一张已使用和未使用内存块的链表

struct malloc_chunk {
  INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk, if it is free. */
  INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */
  struct malloc_chunk* fd;                /* double links -- used only if this chunk is 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 this chunk is free. */
  struct malloc_chunk* bk_nextsize;
};

typedef struct malloc_chunk* mchunkptr;

prev_size代表上一个chunk的大小,是否有效取决于size的属性位P

INTERNAL_SIZE_Tsize_t用于屏蔽平台间的差异size代表当前chunk的大小和属性根据,malloc_chunk结构体的大小的对齐原则,在64位系统中为8字节对齐在32位的系统中为4字节对齐。因此在64位系统中,struct malloc_chunk结构体的大小的最后4个字节是没有被使用的,32位系统中,最后3个字节是没有被使用的分别代表

  • NON_MAIN_ARENA:记录当前 chunk 是否不属于主线程。
  • IS_MAPPED:记录当前chunk是否是由mmap分配的。
  • PREV_INUSE:如果前面一个chunk处于分配状态,那么此位为1。一般来说,堆中第一个被分配的内存块的 size 字段的 P 位都会被设置为 1,以便于防止访问前面的非法内存。当一个chunk 的size 的P位为0时,我们能通过 prev_size 字段来获取上一个 chunk 的大小以及地址。这也方便进行空闲chunk之间的合并。

当chunk为空闲时,加入fd,bk双链表中管理

fd_nextsize bk_nextsize 只用在 large bin 中,表示 上/下一个大小的指针,加快链表遍历。

ptmalloc2分配器有以下特点:

  1. 当前一个chunk非空闲,prev_size无意义,可被前一个chunk使用(已经被使用知道了prev_size也没有意义)
  2. size的低三位为属性位,size一定是8的倍数A为是否为主分配区,M为是否为mmap P为前一个chunk是否被使用
  3. chunk非空闲,fd bk,fd_nextsize bk_nextsize,都无意义,因此返回给用户的可用内存首先得返回size
  4. 分为主分配区和非主分配区

与块相关的宏定义如下

/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
#define PREV_INUSE 0x1
/* extract inuse bit of previous chunk */
#define prev_inuse(p)       ((p)->size & PREV_INUSE)
/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
#define IS_MMAPPED 0x2
/* check for mmap()'ed chunk */
#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)

/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
   from a non-main arena.  This is only set immediately before handing
   the chunk to the user, if necessary.  */
#define NON_MAIN_ARENA 0x4

/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)

typedef struct malloc_chunk* mchunkptr;

#define chunk2mem(p)   ((void*)((char*)(p) + 2*SIZE_SZ))
#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))

/* The smallest possible chunk */
#define MIN_CHUNK_SIZE        (offsetof(struct malloc_chunk, fd_nextsize))

#define MALLOC_ALIGN_MASK      (MALLOC_ALIGNMENT - 1)

#define MINSIZE  \
  (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

#define request2size(req)                                         \
  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
   MINSIZE :                                                      \
   ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

mem为用户可以使用的内存起始地址,如果不是large chunk的话chunk的最小大小至少为8*4=32字节fd_nextsize 和 bk_nextsize 只有在 large chunk 才用的上。

由于chunk可以复用,如果在当前chunk正在使用时,相邻prev_size无效,可当做chunk使用。request2size 将用户申请的内存大小转化为 需要分配的 chunk 大小,用户请求大小 (req + prev_size + MALLOC_ALIGN_MASK) = req + 8B + 16B=req + 24B。这个计算公式的意义是:

1、由于chunk的复用,所以要在请求的大小基础上加上SIZE_SZ;
2、由于最终大小必须是2 * SIZE_SZ对齐,所以要向上对齐;
3、根据结果与MINSIZE比较,确定最终大小MINSIZE还是对齐后的计算结果。

chunk的实际分配模型如下

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of previous chunk, if unallocated (P clear)  |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of chunk, in bytes                     |A|M|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             User data starts here...                          .
            .                                                               .
            .             (malloc_usable_size() bytes)                      .
            .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             (size of chunk, but used for application data)    |
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            |             Size of next chunk, in bytes                |A|0|1|
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

malloc_state

用户申请的chunk可能来自三个地方

  • mmap直接分配
  • 主分配区分配:优先使用brk
  • 非主分配区分配:优先使用mmap

malloc_state是用来管理分配区的,非主分配区的出现是为了环节多线程竞争锁的情况,通常一个线程对应一个分配区,尽管是这样还是会加锁当线程数达到CPU核数,会停止创建分配区,对分配区进行复用,复用是通过轮询判断是否能加锁的。CPU核心数和分配区的对应关系如下

For [32 bit] systems:     Number of arena = 2 * number of cores + 1.
For [64 bit] systems:     Number of arena = 8 * number of cores + 1.

malloc_state结构体如下

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.  */
  int have_fastchunks;
  /* 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;
  /* 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;
};
  • mutex,为了支持多线程进入

  • flags,bit0 表示是否有 fast bin chunk,bit1 表示是否能返回连续的虚拟地址空间。

  • fastbinY,就是存储 fast bins 的数组,NFASTBINS 为 10。

  • top,top chunk ,直接与操作系统关联的地方,bin中申请不到内存从top chunk申请,top chunk不足则扩展top chunk,释放内存给操作系统也是从top chunk开始释放。

  • last_remainder,分配区若上次分配 small chunk 且还有剩余,则暂存入这个指针,会合并到其他bin中。

  • bins,即 unsorted bin + small bins + large bins = 1 + 62 + 63 = 125,bin[0] 和 bin[127] 没有用,NBINS为128,但是 bins 的大小=2*128-2=254,将所有bin保存在bins结构体中,然后构成一个双链表的结构体,可以减少内存。

  • binmap,标识 bit 指向的 bin 是否有空闲 chunk。

  • next,链接分配区。

  • system_mem,当前分配区已分配内存大小,可通过 malloc_stats(3) 进行查看。

Bin

bin通过显示链表将相同属性的空闲块串联起来方便管理,。bin 可以理解桶(数据结构类似于哈希桶),存放着 chunk ,在 ptmalloc 分配器存在四种 bin。

  • fast bins:fast bins 是小内存块的缓存,当小内存块被回收时,会先放入 fast bins,当下次分配小内存时,就会优先从 fast bins 中找,节约时间。
  • unsorted bin:unsorted bin 只有一个,回收的 chunk 若大于 fast bins 的阈值即 global_max_fast,则放入 unsorted bin
  • small bins:small bins 顾名思义,就是 ptmalloc 觉得小的 chunk,就放进去,呈等差数列的形式递增,每个 bin 的 chunk 均为同一大小,通过 fd, bk 链接 chunk 链表。
  • large bins:large bins 同上,不过每个 bin 中的 chunk 有大小排序,大的在前,小的在后,通过 fd_nextsize, bk_nextsize 快速找到上/下 一个大小节点。
#define NBINS             128

bins 共有 small bins 有 62 个, large bins 有 63个, unsorted bin 为 1个,总共为 62+63+1 = 126 个,其中 bin[0] 和 bin[127] 不用,因此 bins 总数为 128 个。要注意 fast bins 并不放入同一数组。每个分配区维护了一个bins位图

bin数组存储在malloc_statemchunkptr bins[NBINS * 2 - 2]中映射关系如下图:每个bin由bin数组中的两个元素维护,两个元素构成双链表,起了节省空间的作用。

preview

Fast bin

fast bins 是小内存块的缓存,大小小于DEFAULT_MXFAST的chunk分配都会在fast bins中先查找,默认为128字节,该宏可以通过mallopt设置,最大值为160B,当小内存块被回收时,会先放入 fast bins将相同大小的内存块存放在一起,当下一次分配小内存时,就会优先从fast bins中找,节约时间。(16、24、32、40、48、56、64、72、80、88),fast chunk的特点就是两个相邻的fast chunk不需要合并fast chunk的maloc和free都是在对应的fast bin的链表头增加和删除,由于取用fast chunk都是从第一个开始取用,因此fast bins不需要维护双链表,使用后进先出的方式。Prev inuse始终置1,避免块合并。

#ifndef DEFAULT_MXFAST
#define DEFAULT_MXFAST     (64 * SIZE_SZ / 4)
#endif

typedef struct malloc_chunk* mfastbinptr;
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])

/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
  ((((unsigned int)(sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

/* The maximum fastbin request size we support */
#define MAX_FAST_SIZE     (80 * SIZE_SZ / 4)

#define NFASTBINS  (fastbin_index(request2size(MAX_FAST_SIZE))+1)

#define FASTBIN_CONSOLIDATION_THRESHOLD  (65536UL)

fast bins有以下特点

  1. 一共有10个fast bin,每个fast bin维护一个单链表,增加和删除都在链表的头或者尾
  2. 初始化时候fast chunk最大的大小设置为64,因此默认16到64字节的被划分为快速块
  3. 8位对齐fast bin同样使用8字节对齐,数组第一个元素存储的都是接近16bytes的块列表,数组第二个存储的则是接近24字节块的列表
  4. fast bin设置了prev INUSE所以快速块无法被合并,虽然可能产生外部碎片,但是能够快速找到合适大小的chunk
  5. 刚开始fast bin为空,及时用户申请fast bin也无法在fast bin中分配到内存,但是会优先尝试。

Fast Bins

NFASTBINS取值计算:

#define NFASTBINS  (fastbin_index(request2size(MAX_FAST_SIZE))+1)
=fastbin_index((0x50*2+0x8+0x10) &~ 0xe) +1 
=fastbin_index(0xb8) & 0xfffffff0 )+1
转换为 2进制 
=fastbin_index((10111000) & 0xfffffff0 ) +1
= ( 10110000 >> 4 ) -2 +1
= 00001011 -2 +1
= 00001010

可得NFASTBINS的取值为 10

Unsorted bin

未排序的块,当 small chunk或者large chunk被free的时候不会直接加入到相应的small bin和large bin中,而是被添加到Unsorted bin;主要的目的是为了重新利用这些刚被释放的chunk;由free chunks组成的循环双链表,且没有大小限制;

Bins

Small bin

和快速块类似16、24……,504bytes在内存分配和释放方面,小容器比大容器快(但比快容器慢)。,插入发生在头,删除发生在尾部。

  • 一共有62个 Small bin
    • 每个small bin包含一共环形双链表,在small bin中块与块之间无隔断,因此需要双链表维护
  • 8字节对齐
    • small bin的链表也是每隔8字节一个元素,最小的small bin(bins[2])位16字节
    • 同一个 bin中的所有chunk大小一致
  • 相邻的空闲小块会被合并成一个块
  • 小块在未初始化的时候也为空,当没有小块会在 未排序箱子中尝试
  • 在第一次调用malloc时,malloc_state结构体可以看到结构,和bins指针,但是是空的
  • 释放的时候会检查前一个块和后一个块是否空闲,空闲会合并到未排序块中

Large bin

大于等于512字节的块称为大块,装有大块的bin称为large bin

  • 一共有63个large bin
    • 每个large bin维护一个空闲块的双链表,双链表可以在任何位置插入和删除。
    • 32个bins的间隔是64字节
    • 16个间隔512字节
    • 8个间隔4096字节
    • 4个间隔32768字节
    • 2个间隔262144字节
    • 剩下一个存储更大的
  • 不同与small bin每个large bin里面的chunk大小不相同但会处于一定范围(上面的间隔即区间大小),大的存在前面小的存在binlist的后面,在添加和删除时按照顺序添加进行操作.
  • 最初所有的large bin都为空
  • 优先会将最大的块分配给用户,如果最大块大于用户请求则从后遍历该binlist,当其接近或等于用户请求,该块会被分割成两个块
  • 如果最大快大小小于用户请求,则会尝试从下一个非空的large bin来服务用户请求,如果没有找到,则尝试从top chunk分配
  • 要注意的是

top chunk

位于分配区顶部边界的大块被称为top chunk ,它不属于任何bin,top chunk用于无法在bin中分配到内存时将top chunk 分割为两部分给用户使用,另一部分则称为remaining chunkremaining chunk会变为新的top

当top chunk 不足时则[使用sbrkmmap扩展top chunk

Last remainder chunk

从最近拆分的一个请求中得到的余数。Last remainder chunk是为了优化连续 小块的malloc 请求,这些请求最终可能会被紧密地分配给其他块。

分配原则

鉴于 bin 的个数较多,因为不同 bin中的 chunk极有可能在不同的内存页中,如果按照上一段中介绍的方法进行遍历的话,就可能会发生多次内存页中断操作,进而严重影响检索速度,所以 glibcmalloc 设计了 binmap 结构体来帮助提高 bin-by-bin 检索的速度。binmap 记录了各个 bin中是否为空,通过位图算法可以避免检索一些空的 bin 。如果通过 binmap 找到了下一个非空的 large bin的话,就将其分为两部分 ,否则就使用 top chunk 来分配合适的内存。

​ 如果用户请求是一个小块,且unsorted bin中有块,该块会被分为两个,一个返回给用户另一个添加到unsorted bin中

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值