HEAP MANGER 堆管理器

HEAP MANGER

为什么要有堆管理器

堆是动态分配的虚拟内存(只有在程序访问到这块虚拟内存时,系统才会建立虚拟内存和物理内存的映射,称为延迟绑定机制,大大节省了内存),从低地址向高地址生长,程序可能需要频繁的向操作系统申请内存的系统调用,与此同时就伴随这从用户态到内核态的多次转换,转换的同时需要保存context,影响程序性能,堆管理器就出现了,在操作系统和用户当作中间人,他会申请较大的内存,管理分配,1、响应用户申请内存的请求2、管理用户所释放的内存,堆管理器并非由内核实现的,Linux标准发行版中使用的堆分配器是glibc中的堆分配器:ptmalloc2

平台:操作系统 --> 堆管理器 --> 用户
资源:物理内存 --> arena  -->可用内存

在这里插入图片描述

堆管理器ptmalloc2

arena 指的是管理器在第一次malloc时申请到的大内存快,堆内存区域本身,并不是结构,可以理解为内存池;主线程的 main arena通过==(s)brk==创建,没开alsr分布在数据区(.data/.bss)的结尾,开启后在尾部随机偏移处;其他线程的 arena包括一些主线程的大内存 是通过 mmap 创建,分布在Memory-mapped segment,也就是我们glic动态链接库加载的区域

malloc_statemain arena malloc_state 并不是 heap segment 的一部分,而是一个全局变量,存储在 libc.so 的数据段)管理 arena 的核心结构,包含堆的状态信息、bins 链表等;main arena 对应的 malloc state 结构存储在 glibc 全局变量中;其他线程 arena 对应的 malloc_state 存储在 arena 本身中

bin 用来管理空闲内存块,通常用链表的结构来进行组织

chunks 内存块结构,是malloc具体在arena上申请给用户的

chunks

top chunk arena中从未被使用过的内存区域,inuse 比特位始终为 1,否则会合并旁边的chunk

malloc_chunk 已被分配且填写了相应数据的chunk

free chunk 被释放掉的malloced chunk

allocated chunk是一个更加通用的术语,它可以指任何已经被分配给应用程序使用的内存块。

last remainder chunk malloc分割原chunk后剩余的部分

在这里插入图片描述

/*
  This struct declaration is misleading (but accurate and necessary).
  It declares a "view" into memory allowing access to necessary
  fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
	/*above chunk——head*/
  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;
};
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)                      .
next    .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             (size of chunk, but used for application data)    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|1|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • prev_size

    • 如果该 chunk 的**物理相邻的前一地址 chunk(两个指针的地址差值为前一chunk 大小)**是空闲的话,那该字段记录的是前一个chunk 的大小 (包括chunk头)。否则,该字段可以用来存储物理相邻的前一个chunk 的数据。这里的前一 chunk 指的是较低地址的 chunk ,也就是当前一个chunk是malloced,那么这个字段是无用的,为了不浪费,我们会把这块内存在必要时给到前一个chunk,比如我们在64位程序下,malloc(0x10),实际会分配0x20个字节,会包括chunk_head,free掉后,我们再次malloc(0x18),该chunk的高地址处的chunk的pre_size字段是无用的,会被利用起来,也就是会再次把之前malloc(0x10)的chunk给到我们,这就是 chunk 中的空间复用

  • size

    • chunk的大小包括chunk_head,是安照2 * SIZE_SZ(字长)对齐的,也就是32位是0x8,64位是0x10,恒是8的倍数,这样后三位bit位恒为0,我们将其设置为标志位

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

    allocated状态下从这里开始为用户数据区,也就是我们通常说的malloc返回给用户的地址就是从这里开始的,free状态下会被添加到对应的空闲管理链表中

    fd 指向下一个(非物理相邻)空闲的 chunk

    bk 指向上一个(非物理相邻)空闲的 chunk

    fdbkforwardbackward的缩写。

  • fd_nextsize bk_nextsize

    • free时使用,用于large chunk

      fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。

      bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。

      一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适 chunk 时挨个遍历。

一般情况下,物理相邻的两个空闲 chunk 会被合并为一个 chunk 。堆管理器会通过 prev_size 字段以及 size 字段合并两个物理相邻的空闲 chunk 块。

bin

用户释放掉的 chunk 不会马上归还给系统,会放入bin或者合并到top chunk中去,ptmalloc 会统一管理 heap 和 mmap 映射区域中的空闲的 chunk。当用户再一次请求分配内存时,ptmalloc 分配器会试图在空闲的 chunk 中挑选一块合适的给用户。这样可以避免频繁的系统调用,降低内存分配的开销。mmap申请释放时直接归还操作系统 不进入bin

ptmalloc2维护bins数组

根据空闲的 chunk 的大小以及使用状态将 chunk 初步分为 4 类:fast bins(小),small bins(中),large bins(大),unsorted bin(未整理)。

  1. 第一个为 unsorted bin,字如其面,这里面的 chunk 没有进行排序,存储的 chunk 比较杂。
  2. 索引从 2 到 63 的 bin 称为 small bin,同一个 small bin 链表中的 chunk 的大小相同。两个相邻索引的 small bin 链表中的 chunk 大小相差的字节数为 2 个机器字长,即 32 位相差 8 字节,64 位相差 16 字节。
  3. small bins 后面的 bin 被称作 large bins。large bins 中的每一个 bin 都包含一定范围内的 chunk,其中的 chunk 按 fd 指针的顺序从大到小排列。相同大小的 chunk 同样按照最近使用顺序排列。

fast bin

malloc state 中的 fastbinsY[]

fastbinsY[]x86(size_t=4)x64(size_t=8)
00x100x20
10x180x30
20x200x40
30x280x50
40x300x60
50x380x70
60x400x80
  1. ptmalloc2 为了提高分配的速度,会把一些小的 chunk 放到 fast bins 的容器内,fast bins P位始终为1,不合并物理相邻的chunk
  2. 是个单链表结构,只是用fd成员
  3. 用户申请chunk大小<=MAX_FAST_SIZE时,优先从fastbins查找空闲块,LIFO规则,满足频繁的申请和释放同一块chunk,满足局部性
  4. ptmalloc 默认情况下会调用 set_max_fast(s) 将全局变量 global_max_fast 设置为 DEFAULT_MXFAST,也就是设置 fast bins 中 chunk 的最大值。当 MAX_FAST_SIZE 被设置为 0 时,系统就不会支持 fastbin 。
  5. 在这里插入图片描述

unsortedbin

  1. 可以视为空闲 chunk 回归其所属 bin 之前的缓冲区,刚刚被释放,还未分类的chunk。bin[1]
  2. FIFO的双向链表,当执行free()函数时,内存分配器会根据释放的内存块的大小,将它们放置到不同的内存块链表中。对于小于等于FASTBIN_MAX_SIZE的内存块,它们会被放置到fastbin中,而对于大于FASTBIN_MAX_SIZE的内存块,则会被放置到unsorted bin中。存放未被整理的 chunk
  3. malloc时,内存分配器首先会尝试从fastbin中分配空闲内存块,当分配器需要分配大于FASTBIN_MAX_SIZE的内存时,它会尝试从unsorted bin中分配内存块。如果分配器发现unsorted bin中有适合大小的空闲块,它将选择其中一个,并返回给应用程序。如果没有适合的空闲块,则分配器会将unsorted bin中的空闲块合并,并将合并后的大块添加到large bin中。会依次查找small binlarge binmmapped等其他内存块,直到找到合适的内存块。
  4. 在这里插入图片描述

smallbin

下标SIZE_SZ=4(32 位)SIZE_SZ=8(64 位)
21632
32448
43264
54080
x24x28x
635041008
  1. 每个链表中存储的 chunk 大小都一致
  2. 循环双向链表,每个链表都有链表头结点
  3. small bins 中每个 bin 对应的链表采用 FIFO 的规则
  4. 释放的时候会检查相邻的是不是free的,如果是进行合并然后放到 unsortedbin
  5. 在这里插入图片描述

large Bin

  1. bins[64] ~ bins[126]

  2. 循环双向链表 FIFO

  3. 这 63 个 bin 被分成了 6 组,每组 bin 中的 chunk 大小之间的公差一致,具体如下:

    数量公差
    13264B
    216512B
    384096B
    4432768B
    52262144B
    61不限制
  4. 在这里插入图片描述

heap_info

一般申请的 heap 是不连续的,因此需要记录不同 heap 之间的链接结构。

该数据结构是专门为从 Memory Mapping Segment 处申请的内存准备的,即为非主线程(mmap申请)准备的。

malloc_state

该结构用于管理堆,记录每个 arena 当前申请的内存的具体状态,比如说是否有空闲 chunk,有什么大小的空闲 chunk 等等。无论是 thread arena 还是 main arena,它们都只有一个 malloc state 结构。会在最新申请的arena中

struct malloc_state {
    /* Serialize access.  */
    __libc_lock_define(, mutex);

    /* Flags (formerly in max_fast).  */
    int flags;

    /* 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, help to speed up the process of determinating if a given bin is definitely empty.*/
    unsigned int binmap[ BINMAPSIZE ];

    /* Linked list, points to the next arena */
    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;
};

分配和释放过程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值