码迷,mamicode.com
首页 > 系统相关 > 详细

Linux堆管理策略详解

时间:2018-07-07 20:13:47      阅读:603      评论:0      收藏:0      [点我收藏+]

标签:9.png   typedef   define   才有   struct   开始   移除   定义   回收   

最近看了linux堆管理的文章,这篇博文是对文章的提炼和总结。

入门二进制很难啊!

Linux堆管理策略

1、总述

在主线程中调用malloc之后会发现系统给程序分配了堆,且恰好在数据段之上。这说明它是通过brk系统调用实现。并且分配的地址空间大小远大于申请的大小,我们把它称之为main arena(每个arena中含有多个chunk,这些chunk以链表的形式加以组织)。由于申请到的地址空间比你所需的地址空间大很多,主线程在后续如果再申请堆空间的话,会从132KB的剩余部分中申请,直到用完或不够用的时候,再通过增加program break location的方式来增加main arena的大小。同理,当main arena中有过多空闲内存的时候,也会通过减少program break location的方式缩小arena。

在主线程调用Free函数后,在内存中程序的堆空间并未被释放而是由glibc的malloc库函数加以管理。它会将释放的chunk添加到main arenas的bin(记录空闲链表的结构称为bins,之后每次用户调用malloc申请堆空间时,glibc malloc会先尝试从bins中找到一个满足要求的chunk,如果没有才向操作系统申请新的堆空间)。

子线程在申请堆空间时,操作系统并不通过brk系统调用分配而是通过mmap系统调用分配,其他与主线程分配释放均相同。

技术分享图片 

2、Arena介绍

2.1、数量限制

虽然主线程和子线程均有自己独立的arena,但是arena的数量是固定的,它与系统中处理器核心个数相关。

32bit:y=2*x+1

64bit:y=8*x+1

其中y为arena个数,x为处理器核心数

2.2、Arena的管理

对于共享Arena的问题,此处以单核32bit处理器为例。当主线程首次调用malloc时,glibc malloc会直接为它分配一个main arena。当子线程1和子线程2首次调用malloc时,glibc malloc会分别为每一个用户线程创建一个新的arena。但是当第三个子线程申请malloc时,glibc malloc会循环便利所有的Arena,当找到一个可以lock的堆块时(当前子线程未使用堆内存表示可以lock),将其返回给申请的子进程即共享arena。但是如果没有可用的arena,那么申请堆内存的子线程将被阻塞,直到有可用的arena为止。其他复杂情况可以依次类推。

 

3、堆管理

3.1、总述

1)、heap_info:

Heap Header,由于一个thread heap(main heap除外)可以包含多个heaps,因此为了高效管理heap,将每个heap分配一个heap header。(当现在的heap不够用时,从前面的介绍中我们知道,会通过mmap系统调用申请信道heap,那么此时新的heap会被添加到当前的thread heap中,因此出现一个thread heap包含多个heap的情况)

typedef struct_heap_info{

mstate ar_ptr;//属于哪个Arena

struct_heap_info *prev;//前一个heap_info

size_t size;//当前字节大小

size_t mprotect_size;

char pad[-6*SIZE_SZ&MALLOC_ALIGN_MASK];//字节对齐

}heap_info;

 

2)、malloc_state:

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

struct malloc_state{

mutex_t mutex;

int flags;

mfastbinptr fastbinsY[NFASTBINS];//快表

mchunkptr top;

mchunkptr last_remainder;

mchunkptr bins[NBINS*2-2];

unsigned int binmap[BINMAPSIZE];

struct malloc_state *next;

struct malloc_state *next_free;

INTERNAL_SIZE_T system_mem;

INTERNAL_SIZE_T max_system_mem;

};

3)、malloc_chunk

Chunk Header,一个heap可以分成多个chunk,每个chunk的大小由用户调用malloc时参数size“决定”。

struct malloc_chunk{

INTERNAL_SIZE_T prev_size;//前一个空闲chunk的大小

INTERNAL_SIZE_T size;//chunk的大小

struct malloc_chunk *fd;//双向链表,只有空闲chunk存在

struct malloc_chunk *bk;

};

3.2、heap segment和arena关系

线程arena只含有一个malloc_state即arena header,但是有多个heap_info即heap header。且两个heap若通过mmap分配,那么他们在内存上并不相邻而是属于不同的内存区间,为了便于管理,libc malloc将后一个heap_info结构体的prev成员指向前一个heap_info结构体的起始位置即ar_ptr成员,而第一个heap_info的ar_ptr成员指向malloc_state,从而构成一个单链表,方便管理。

 

4、再议chunk

chunk可是说是堆内存管理里最小的操作单位。chunk分为4类:1)allocated chunk、2)free chunk、3)top chunk、4)last remainder chunk。为了方便讨论,暂且分为allocated chunk和free chunk。

4.1、隐式链表技术

 技术分享图片

技术分享图片 

由于堆内存是以chunk为单位进行管理,因此需要明确规定chunk的边界,并且标记以分配块和空闲块。对于已分配的chunk来说有一个指向payload的指针(在malloc时返回的指针就指向payload开始处)。Chunk size作为chunk的头部,因为8字节对齐的原因可以看出chunk size的后三位是无效的,因此它们用作chunk的标志位。其中,第0bit位用于标记chunk是否分配(1表示已分配,0表示空闲)。Padding部分用于地址对齐,整个chunk的大小必须为8的整数倍。

缺点:效率很低,在内存回收的时候难以将相邻多个free chunk合并,这将会产生大量无法分配的小chunk,最终整个内存都将消耗殆尽。

 

4.2、带边界标记的合并技术

为了解决chunk前一个相邻的chunk空闲而合并这两个chunk缺需要遍历整个chunk,并且意味着释放chunk消耗的时间也与堆的大小成线性关系。因此提出一种解决办法-边界标记。

技术分享图片 

在每一个chunk的padding后添加一个头部的副本,我们称之为脚部(Footer)。由于这个脚部处于下一个chunk的前4个字节,因此可以很轻松的定位前一个块并进行合并。

缺点:由于添加了脚部使得chunk的大小变大,而对于申请很小chunk的操作来说会造成很大的性能损耗。同时,我们只对于空闲chunk需要进行合并操作,那么对于已分配的chunk我们并不需要脚部。

优化:将前一个chunk是否分配的标志位存储在当前chunk的1或2bit位上。因此我们可以根据当前chunk的bit位判断前一个chunk是否分配,从而判断当前chunk头的前四个字节是否为前一个chunk的脚部

技术分享图片 

 技术分享图片

4.3、支持多线程的chunk

为了能够标记当前chunk是否属于thread arena,判断申请方式应该由mmap还是brk实现。但是由于当前的chunk只剩下一个多余的bit位,因此需要对chunk进行大改动。

由于头部即保存了本chunk是否被分配也保存了前一个chunk是否被分配,因此我们认为这完全没有必要,当前的chunk是否已分配可以根据下一个chunk的标志位来实现。因此我们将剩下的bit位作为标记多线程的标志位。

技术分享图片 

 技术分享图片

其中N表示当前chunk是否是thread arena,M表示当前chunk是否通过mmap系统调用产生,P表示前一个chunk是否被分配。

优化:发现没有必要保存chunk size的副本,但是空闲块合并的话又必须知道前一个chunk的size。因此我们将chunk的脚部移动到首部的前面同时其不在保存当前chunk的size,而是保存前一个chunk的size。并且只有当前一个chunk空闲时这个脚部才有用,分配的chunk这个脚部将当作payload或者padding的一部分。

技术分享图片 

 技术分享图片

总之,malloc_chunk中的prev_size和size构成了隐式链表,而后续的fd,bk等指针并不是作用于隐式链表的,而是用作加快内存分配和释放效率的显示链表bin(只在free chunk中存在)

 

5、Top Chunk

当一个chunk处于一个arena的最顶部(最高内存地址处)的时候,称之为top chunk。该chunk不属于任何一个bin,而是当系统中所有free chunk即无论哪种bin都无法满足用户请求的内存大小的时候,将此chunk分配给用户使用。如果top chunk的大小比用户请求的大小还要大的话,就将该top chunk作为两个部分:1)用户请求的chunk;2)剩余部分成为新的top chunk。否则,就需要拓展或申请新的heap(在main arena中用sbrk拓展,在thread arena中通过mmap分配)。

 

 

6、Last Remainder Chunk

当用户请求一个small bin且无法被small bin、unsorted bin满足时会通过binmaps遍历bin查找chunk,如果该chunk有剩余部分的话就将该剩余部分变成新的chunk加入unsorted bin,同时变成last remainder chunk。

它的出现主要是为了提高连续malloc(small chunk)操作时的效率。

 

7、bin介绍

Bin是一种记录free chunk的链表结构,根据其大小将其分为4类:Fast bin、Unsorted bin、Small bin、Larger bin。用于记录bin的数据结构有两种:fastbinY:这是一个数组,记录所有fast bins;bins:这也是一个数组,用于记录除fast bins之外的所有bins。事实上,一共有126个bins,分别为:bin 1为unsorted bin;bin 2-63为small bin;bin 64-126为large bin。

具体数据结构定义如下:

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。

技术分享图片 

 

8、Fast bin

Chunk size为16到80字节的chunk叫做fast chunk。Chunk size表示该malloc_chunk的实际整体大小。而下文会用到的chunk unused size就表示该malloc_chunk中刨除诸如prev_size,size,fd,bk这类辅助成员之后实际可用的大小。因此,对free chunk而言,其实际可用的大小总是比实际整体大小少16字节。在所有bin操作中,fast bin是最快的。

1) fast bin的个数固定为10个。

2) 每个fast bin都是一个单链表(只有fd指针)。Fast bin中无论添加还是移除fast chunk都是对链表尾进行操作,不会对某个中间的fast chunk进行操作。即添加free内存到链表尾,删除(malloc内存)是将链表尾部的fast chunk删除。因此在fastbinsY数组中每个fastbin元素均指向了该链表的尾节点,而尾节点的fd指向前一个节点。

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

4) Fast bin中的chunk不进行合并操作,即将其chunk的是否分配标志位总是置为1(已分配状态)。

5) 当用户通过malloc请求的大小属于fast chunk时(用户请求的大小+16字节则为实际的chunk size)。在初始化时fast bin支持的最大内存大小及所有fast bin链表均为空,所以最开始申请内存时不会交由fast bin处理。

当第一次调用malloc(fast bin)的时候,系统执行_int_malloc函数,该函数首先会发现当前fast bin为空,就会转交给small bin处理,发现small bin也为空,就调用malloc_consolidate函数对malloc_state结构体进行初始化。Malloc_state结构体功能如下:

首先判断当前malloc_state结构体中的fast bin是否为空,如果为空就说明整个malloc_都未初始化,则进行初始化。调用malloc_init_state(av)函数,该函数先初始化除fast bin之外的所有bins,再初始化fast bins。然后当再次执行malloc(fast chunk)函数时即可以使用fast bins。反之,当执行free(fast chunk)操作时,先通过chunksize函数根据传入的地址指针获取该指针对应的chunk的大小;然后根据这个chunk大小获取chunk所属的fast bin,然后再将此chunk添加到该fast bin的链尾即可。整个操作在_int_free函数中完成。

 

9、unsorted bin

当释放较小或较大的chunk的时候,如果系统没有将它们添加到对应的bins中,系统就将这些chunk添加到unsorted bin中。这是让管理机制能够第二次机会重新利用最近释放的chunk(第一次为fast bin机制)。Unsorted bin是一个由free chunks组成的循环双链表。在unsorted bin中,对chunk的大小并没有限制,任何大小的chunk都可以归属到unsorted bin中。

 

10、Small bin

小于512字节的chunk称之为small chunk,small bin就是用于管理small chunk的。就内存的分配和释放速度而言,small bin比larger bin快,但比fast bin慢。Small bin特性如下:

1) small bin有62个。每个都是一个由对应free chunk组成的循环双链表。并且内存释放时将新释放的chunk添加到链表的前端,分配内存时从链表的尾端获取chunk。

2) 第一个chunk大小为16字节,后续每个small bin中chunk的大小依次增加8字节,即最后一个small bin的chunk为16+62*8=512字节。

3) 相邻的空闲块需要进行合并操作。

4) Malloc操作与fast bins类似,初始时都为空,交由unsorted bin处理。处理不了则依次向后遍历,直到top chunk经扩充后一定能处理。

5) 当释放small chunk操作时,先检查该chunk相邻的chunk是否为free,是则合并,并从small bin中移除,添加到unsorted bin中。

 

11、Large bin

大于512字节的chunk称之为larger chunk。Large bin用于管理这些large chunk。

1) large chunk数量63个。并且large chunk中的chunk大小可以不一样,但必须处于某个给定的范围。Large chunk可以添加删除在large chunk的任何一个位置。在这63个large bins中,前32个large bin依次以64字节步长为间隔,即第一个large bin中chunk size为512~575字节,第二个large bin中chunk size为576 ~ 639字节。紧随其后的16个large bin依次以512字节步长为间隔;之后的8个bin以步长4096为间隔;再之后的4个bin以32768字节为间隔;之后的2个bin以262144字节为间隔;剩下的chunk就放在最后一个large bin中。鉴于同一个large bin中每个chunk的大小不一定相同,因此为了加快内存分配和释放的速度,就将同一个large bin中的所有chunk按照chunk size进行从大到小的排列:最大的chunk放在链表的front end,最小的chunk放在rear end。

2) 合并操作类似于small bin

3) 初始化操作类似于small bin。初始化完成后,首先确定用户请求的大小属于哪一个large bin,然后判断large bin中最大的chunk的size是否大于用户请求的size(通过链表中front end的size即可),如果大于就从链表尾部遍历第一个大小相近或接近的chunk分配给用户(若此chunk过大就拆分后返回给unsorted bin)。由于bin的个数较多,并且不同bin中的chunk极有可能在不同的内存页,如果遍历每一个bin中的chunk就有可能会发生多次内存页中断操作,因此glibc设计了Binmap结构体来帮助提高bin-by-bin检索的速度。Binmap记录了各个bin中是否为空,通过bitmap可以避免检索一些空的bin。

4) 释放操作与small chunk类似。

技术分享图片 

Linux堆管理策略详解

标签:9.png   typedef   define   才有   struct   开始   移除   定义   回收   

原文地址:https://www.cnblogs.com/xingzherufeng/p/9277644.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!