浅析堆内存管理

学习文章:Linux堆内存管理深入分析下 | Introspelliam

                  Linux堆内存管理深入分析上 | Introspelliam

                  linux 堆溢出学习之malloc堆管理机制原理详解_jmp esp-CSDN博客_malloc_state

堆介绍:

(1)堆在内存空间中不一定连续

(2)堆可由用户管理

常见的分配堆的方式:

  • dlmalloc : general purpose allocator
  • ptmalloc2 : glibc
  • jemalloc : FreeBSD 、Firefox and Android
  • tcmalloc : Google Chrome
  • libumem : Solaris

本文针对的是Linux glibc使用的ptmalloc2

这是一张程序内存图,heap结束于program break。开始于bss区上,不一定与bss区相连。

  • 不开启ALSR保护时,start_brk以及brk会指向data/bss段的结尾。

  • 开启ALSR保护时,start_brk以及brk也会指向同一位置,只是这个位置是在data/bss段的结尾后的随机偏移处。

 malloc开辟堆空间有两种方式,brk与mmap。

①brk是将数据段(.data)的最高地址指针_edata往高地址推
②mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。

第一次malloc将会分配出一块大于所需的区域,称之为arena。主线程创建的被称为main arena。创建的区域只是虚拟内存,只有真正使用时才会分配物理内存。

free后这块内存并不会归还,而是保留到bin中,等待下次使用。

arena 对于32位系统,数量最多为核心数量2倍,64位则最多为核心数量8倍,可以用来保证多线程的堆空间分配的高效性。主要存储了较高层次的一些信息。有一个main_arena,是由主线程创建的,thread_arena则为各线程创建的,当arena满了之后就不再创建而是与其他arena共享一个arena,方法为依次给各个arena上锁(查看是否有其他线程正在使用该arena),如果上锁成功(没有其他线程正在使用),则使用该arena,之后一直使用这个arena,如果无法使用则阻塞等待。
 

堆主要由下三种数据结构组成

1.heap_info: 即Heap Header,因为一个thread arena(注意:不包含main thread)可以包含多个heaps,所以为了便于管理,就给每个heap分配一个heap header。那么在什么情况下一个thread arena会包含多个heaps呢?在当前heap不够用的时候,malloc会通过系统调用申请新的堆空间,新的堆空间会被添加到当前thread arena中,便于管理。

2.malloc_state: 即Arena Header,每个thread只含有一个Arena Header。Arena Header包含bins的信息、top chunk以及最后一个remainder chunk等。

3.malloc_chunk: 即Chunk Header,一个heap被分为多个chunk,至于每个chunk的大小,这是根据用户的请求决定的。

     chunk的结构体

struct malloc_chunk {
  /* #define INTERNAL_SIZE_T size_t */
  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. 这两个指针只在free chunk中存在*/
  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;
};

下图是只有一个heap segment的main arena和thread arena的内存分布图:

这张图仔细读,表示的非常清晰,main arena的malloc_state是保存在data segment(of libc.so)中的,main arena没有heap_info,因为main_arena通过brk,这样可以直接在原来的基础上增加一块连续的区域,这样就只需要一个heap,而thread_arena通过mmap,如果要再进行申请就需要一个新的heap,一个新的heap结构,这些heap结构会组成一个链表。

下图则是一个拥有多个heap的thread arena(图真的很棒,炒鸡清晰):

 我们可以看到,其中有非常多的chunk

Chunk总共分为4类:

1)allocated chunk;

2)free chunk;

3)top chunk;

4)Last remainder chunk。

struct malloc_chunk {
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk *fd;
Struct malloc_chunk *bk;
}
  • prev_size 这个变量只在内存块为空闲的时候有意义,否则为用户数据。表示当前块前一个块的大小。 .size当前块的大小,包括头数据,由于对齐原因,其末三位永远为0,所以为了充分利用空间,末三位则作为标志位使用,最低位表示是否前一个chunk被使用,倒数第二位表示该chunk是否是由mmap分配的,倒数第三位表示该chunk是否存在于main_arena
  • fd 同样只有在空闲块中存在,否则为用户数据,双向链表的前向指针
  • bk 在空闲块中存在,否则为用户数据,双向链表的后向指针。

当前glibc malloc allocated chunk格式:

 当前glibc malloc free chunk格式 (图比语言传递信息方便得多:

 

我们如何利用malloc,free则跟bin息息相关。还记的struct malloc_state吗?

struct malloc_state
{
  ……
  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];
  ……
  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];  // #define NBINS    128
  ……
};
这里mfastbinptr的定义:typedef struct malloc_chunk *mfastbinptr;
mchunkptr的定义:typedef struct malloc_chunk* mchunkptr;

在glibc中用于记录bin的数据结构有两种,分别如下所示:

fastbinsY: 这是一个数组,用于记录所有的fast bins;

bins: 这也是一个数组,用于记录除fast bins之外的所有bins。事实上,一共有126个bins,分别是:

  • bin 1 为unsorted bin;
  • bin 2 到63为small bin;
  • bin 64到126为large bin。

而chunk之间是用链表的方式连接的 

FastBin的个数——10个

下图是fast bin的示意图(只有fd,无bk,无论是添加还是移除fast chunk,都是对“链表尾”进行操作,而不会对某个中间的fast chunk进行操作,采用LIFO算法。

 chunk size:10个fast bin中所包含的fast chunk size是按照步进8字节排列的,即第一个fast bin中所有fast chunk size均为16字节,第二个fast bin中为24字节,依次类推。在进行malloc初始化的时候,最大的fast chunk size被设置为80字节(chunk unused size为64字节),因此默认情况下大小为16到80字节的chunk被分类到fast chunk

不会对free chunk进行合并操作。鉴于设计fast bin的初衷就是进行快速的小内存分配和释放,因此系统将属于fast bin的chunk的P(未使用标志位)总是设置为1,这样即使当fast bin中有某个chunk同一个free chunk相邻的时候,系统也不会进行自动合并操作,而是保留两者。虽然这样做可能会造成额外的碎片化问题,但瑕不掩瑜。

那么fast bin 是在哪?怎么进行初始化的呢?当我们第一次调用malloc(fast bin)的时候,系统执行_int_malloc函数,该函数首先会发现当前fast bin为空,就转交给small bin处理,进而又发现small bin 也为空,就调用malloc_consolidate函数对malloc_state结构体进行初始化,malloc_consolidate函数主要完成以下几个功能:

  • 首先判断当前malloc_state结构体中的fast bin是否为空,如果为空就说明整个malloc_state都没有完成初始化,需要对malloc_state进行初始化。
  • malloc_state的初始化操作由函数malloc_init_state(av)完成,该函数先初始化除fast bin之外的所有的bins(构建双链表,详情见后文small bins介绍),再初始化fast bins。

SmallBin的个数-62个

每个small bin也是一个由对应free chunk组成的循环双链表。同时Small bin采用FIFO(先入先出)算法:内存释放操作就将新释放的chunk添加到链表的front end(前端),分配操作就从链表的rear end(尾端)中获取chunk。

同一个small bin中所有chunk大小是一样的,且第一个small bin中chunk大小为16字节,后续每个small bin中chunk的大小依次增加8字节,即最后一个small bin的chunk为16 + 61*8 = 508字节。

类似于fast bins,最初所有的small bin都是空的,因此在对这些small bin完成初始化之前,即使用户请求的内存大小属于small chunk也不会交由small bin进行处理,而是交由unsorted bin处理,如果unsorted bin也不能处理的话,glibc malloc就依次遍历后续的所有bins,找出第一个满足要求的bin,如果所有的bin都不满足的话,就转而使用top chunk,如果top chunk大小不够,那么就扩充top chunk。

当释放small chunk的时候,先检查该chunk相邻的chunk是否为free,如果是的话就进行合并操作:将这些chunks合并成新的chunk,然后将它们从small bin中移除,最后将新的chunk添加到unsorted bin中

LargeBin的个数-63个

Large bin类似于small bin,只是需要注意两点:一是同一个large bin中每个chunk的大小可以不一样,但必须处于某个给定的范围(特例2) ;二是large chunk可以添加、删除在large bin的任何一个位置。

初始化完成之前的操作类似于small bin,这里主要讨论large bins初始化完成之后的操作。首先确定用户请求的大小属于哪一个large bin,然后判断该large bin中最大的chunk的size是否大于用户请求的size(只需要对比链表中front end的size即可)。如果大于,就从rear end开始遍历该large bin,找到第一个size相等或接近的chunk,分配给用户。如果该chunk大于用户请求的size的话,就将该chunk拆分为两个chunk:前者返回给用户,且size等同于用户请求的size;剩余的部分做为一个新的chunk添加到unsorted bin中。

UnsortedBin的个数-1个

unsorted bin是一个由free chunks组成的循环双链表。对chunk的大小并没有限制,任何大小的chunk都可以归属到unsorted bin中。

本文顺序按照“Linux堆内存管理深入分析”梳理,初学难免有所纰漏,还请见谅。有些地方加了一些本人的理解,随着学习深入,未来也会有所增改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值