堆的相关数据结构

目录

0x01.malloc_chunk

1.malloc_chunk的结构

2.结构中参数说明 

3.chunk的结构 

0x02.bin

数组中的bin:

0x03.Top Chunk

官方说明:

0x04.last remainder

0x05.arena

0x06.heap_info

0x07.malloc_state


0x01.malloc_chunk

  • 在程序的执行过程中,我们称由 malloc 申请的内存为 chunk
  • 这块内存在 ptmalloc 内部用 malloc_chunk 结构体来表示。
  • 当程序申请的 chunk 被 free 后,会被加入到相应的空闲管理列表中。
  • 无论一个 chunk 的大小如何,处于分配状态还是释放状态,它们都使用一个统一的结构
  • 虽然它们使用了同一个数据结构,但是根据是否被释放,它们的表现形式会有所不同。

1.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. */

  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;
};

2.结构中参数说明 

  • 一般来说,size_t 在 64 位中是 64 位无符号整数,32 位中是 32 位无符号整数。
  • prev_size:如果该 chunk 的物理相邻的前一地址chunk(两个指针的地址差值为前一chunk大小)是空闲的话,那该字段记录的是前一个 chunk 的大小(包括 chunk 头)。否则,该字段可以用来存储物理相邻的前一个chunk 的数据。这里的前一 chunk 指的是较低地址的 chunk
  • size:该 chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。该字段的低三个比特位对 chunk 的大小没有影响。
  • 低三个比特位从高到低分别表示 NON_MAIN_ARENA  IS_MAPPED  PREV_INUSE
  • NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1表示不属于,0表示属于。
  • IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的。
  • PREV_INUSE,记录前一个 chunk 块是否被分配。
  • 一般来说,堆中第一个被分配的内存块的 size 字段的P位都会被设置为1,以便于防止访问前面的非法内存。
  • 当一个 chunk 的 size 的 P 位为 0 时,我们能通过 prev_size 字段来获取上一个 chunk 的大小以及地址。这也方便进行空闲chunk之间的合并。
  • fd,bk:  chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时,会被添加到对应的空闲管理链表中。
  • 空闲时,fd 指向下一个(非物理相邻)空闲的 chunk,bk 指向上一个(非物理相邻)空闲的 chunk。
  • 通过 fd 和 bk 可以将空闲的 chunk 块加入到空闲的 chunk 块链表进行统一管理。
  • fd_nextsize, bk_nextsize:也是只有 chunk 空闲的时候才使用,不过其用于较大的 chunk(large chunk)。
  • fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
  • bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
  • 一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适chunk 时挨个遍历。
  • 一个已经分配的 chunk 大概是这样的:

3.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)                      .
next    .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             (size of chunk, but used for application data)    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|1|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 前两个字段称为 chunk header,后面的部分称为 user data。
  • 每次 malloc 申请得到的内存指针,其实指向 user data 的起始处。
  • 当一个 chunk 处于使用状态时,它的下一个 chunk 的 prev_size 域无效,所以下一个 chunk 的该部分也可以被当前chunk使用。这就是chunk中的空间复用。
  • 被释放的 chunk 被记录在链表中(可能是循环双向链表,也可能是单向链表)。
  • 结构大致是:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk, if unallocated (P clear)  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' |             Size of chunk, in bytes                     |A|0|P|
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Forward pointer to next chunk in list             |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Back pointer to previous chunk in list            |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Unused space (may be 0 bytes long)                .
        .                                                               .
 next   .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' |             Size of chunk, in bytes                           |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|0|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 如果一个 chunk 处于 free 状态,那么会有两个位置记录其相应的大小。
  • 本身的 size 字段会记录,它后面的 chunk 会记录。
  • 一般情况下,物理相邻的两个空闲 chunk 会被合并为一个 chunk 。
  • 堆管理器会通过 prev_size 字段以及 size 字段合并两个物理相邻的空闲 chunk 块。

0x02.bin

  • 用户 free 掉的内存并不是都会马上归还给系统,ptmalloc 会统一管理 heap 和 mmap 映射区域中的空闲的 chunk,当用户进行下一次分配请求时,ptmalloc 会首先试图在空闲的chunk 中挑选一块给用户,这样就避免了频繁的系统调用,降低了内存分配的开销。
  • ptmalloc将相似大小的 chunk 用双向链表链接起来,这样的一个链表被称为一个 bin。Ptmalloc 一共维护了 128 个 bin,并使用一个数组来存储这些 bin。
  • 首先,它会根据空闲的 chunk 的大小以及使用状态将 chunk 初步分为4类:fast bins,small bins,large bins,unsorted bin。
  • 每类中仍然有更细的划分,相似大小的 chunk 会用双向链表链接起来。
  • 在每类 bin 的内部仍然会有多个互不相关的链表来保存不同大小的 chunk。
  • 任意两个物理相邻的空闲chunk不能在一起

数组中的bin:

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

fast bin为单链表。采用LIFO(Last In First Out)。fastbinsY[10]只使用前八个bin,所以大小范围为[0x10,0x80]。当用户需要的chunk大小<MAX(fastbin)时,ptmalloc会首先判断fastbin中相应的bin中是否有对应大小的空闲块(这里比较的是无符号整数),如果有,直接从fastbin头结点开始取chunk。free之后如果再次malloc相同size,会申请到同一块内存。不取消prev_inuse位,所以不会相互合并。

0x03.Top Chunk

官方说明:

/*
   Top

    The top-most available chunk (i.e., the one bordering the end of
    available memory) is treated specially. It is never included in
    any bin, is used only if no other chunk is available, and is
    released back to the system if it is very large (see
    M_TRIM_THRESHOLD).  Because top initially
    points to its own bin with initial zero size, thus forcing
    extension on the first malloc request, we avoid having any special
    code in malloc to check whether it even exists yet. But we still
    need to do so when getting memory from system, so we make
    initial_top treat the bin as a legal but unusable chunk during the
    interval between initialization and the first call to
    sysmalloc. (This is somewhat delicate, since it relies on
    the 2 preceding words to be zero during this interval as well.)
 */

/* Conveniently, the unsorted bin can be used as dummy top on first call */
#define initial_top(M) (unsorted_chunks(M))
  •  程序第一次进行 malloc 的时候,heap 会被分为两块,一块给用户,剩下的那块就是 top chunk。
  • top chunk 就是处于当前堆的物理地址最高的 chunk。
  • 这个 chunk 不属于任何一个 bin,它的作用在于当所有的bin 都无法满足用户请求的大小时,如果其大小不小于指定的大小,就进行分配,并将剩下的部分作为新的 top chunk。否则,就对heap进行扩展后再进行分配。
  • top chunk 的 prev_inuse 比特位始终为1,否则其前面的chunk就会被合并到top chunk中。
  • 初始情况下,我们可以将 unsorted chunk 作为 top chunk。

0x04.last remainder

  • 在用户使用 malloc 请求分配内存时,ptmalloc2 找到的 chunk 可能并不和申请的内存大小一致,这时候就将分割之后的剩余部分称之为 last remainder chunk ,unsort bin 也会存这一块。top chunk 分割剩下的部分不会作为last remainer。

0x05.arena

  • arena是指分配区。
  • arena的个数是跟系统中处理器核心个数相关。
  • 与 thread (很多个)不同的是,main_arena(一个) 并不在申请的 heap 中,而是一个全局变量,在 libc.so 的数据段。

0x06.heap_info

  • 程序刚开始执行时,每个线程是没有 heap 区域的。
  • 当其申请内存时,就需要一个结构来记录对应的信息,而heap_info 就是用来记录信息得。
  • 该数据结构是专门为从 Memory Mapping Segment 处申请的内存准备的,即为非主线程准备的。
  • 主线程没有 heap_info 数据结构。

0x07.malloc_state

  • 该结构用于管理堆,记录每个 arena 当前申请的内存的具体状态。
  • main arena 的 malloc_state 并不是 heap segment 的一部分,而是一个全局变量,存储在 libc.so 的数据段。
发布了99 篇原创文章 · 获赞 114 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ATFWUS/article/details/104649721