堆(glibc)
堆概述
堆简介
堆是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长,用来提供动态分配的内存(用户申请时启用)。只有虚拟地址被真正使用时才会与物理地址形成映射关系。
堆管理器
管理堆的程序,介于用户与系统内核之间。响应用户请求向系统申请内存,一次会申请很大内存,根据用户需求分配堆,堆空间不够时才会再次与系统交互;用户释放内存后内存先有堆管理器管理,不直接返回系统。
堆的申请与释放
相关函数
malloc和free
malloc(size)
: 用户通过malloc函数申请size大小的堆内存,malloc 函数返回对应大小字节的内存块的指针
free(ptr)
: 用户通过free函数释放相应指针指向的堆内存
brk()和sbrk()
malloc和free后面真正与系统进行交互的函数时brk()和sbrk(),还有mmap、unmmap等
堆的大小通过brk()和sbrk()函数移动program_break使堆增长。
brk(ptr)
:参数是一个指针,用于设置program_break指向的位置
sbrk(increment)
:参数与program_break相加来调整program_break的值
执行成功brk()返回0,sbrk()返回上一次program_break的值
初始时,堆的起始地址 start_brk 以及堆的当前末尾 brk 指向同一地址。
- 不开启 ASLR 保护时,start_brk指向 data/bss 段的结尾。
- 开启 ASLR保护时,start_brk 指向data/bss 段结尾后的随机偏移处。
如下图所示:
当用户申请内存过大时,就会使用mmap和unmmap来创建匿名映射段给用户使用和回收
Arena
简介
arena包含一片或数片连续的内存,堆块从中划分。
main_arena
- 主线程的arena,包含start_brk和brk之间连续的内存。
- 只有堆;堆大小不够用brk()扩展
thread_arena
- 其他线程的arena
- 可以有数片连续内存;映射段大小固定,不可扩展,不够用就再次调用mmap()分配新内存
相关数据结构
glibc的malloc源码中涉及三种数据结构:
- Arena --> malloc_state
- Heap --> heap_info
- Chunk --> malloc_chunk
1、Arena - malloc_state
- 每个线程都有一个Arena,但可能多个线程共用一个Arena。
- 每个Arena包含一个malloc_state 结构体(arena_header),保存bins, top chunk, Last reminder chunk等信息。
- 注意,main_arena的arena_header作为全局变量储存在libc.so的数据段中,其他arena的保存在heap segment中
malloc_state结构体定义
struct malloc_state {
//该变量用于控制程序串行访问同一个分配区,当一个线程获取了分配区之后,其它线程要想访问该分配区,就必须等待该线程分配完成后才能够使用。
__libc_lock_define(, mutex); /* Serialize access. */
//flags 记录了分配区的一些标志,比如 bit0 记录了分配区是否有 fast bin chunk ,bit1 标识分配区是否能返回连续的虚拟地址空间
int flags; /* Flags (formerly in max_fast). */
//fastbinsY存放每个 fast chunk 链表头部的指针
mfastbinptr fastbinsY[ NFASTBINS ]; /* Fastbins */
//指向分配区的 top chunk
mchunkptr top; /* Base of the topmost chunk -- not otherwise kept in a bin */
//最新的 chunk 分割之后剩下的那部分
mchunkptr last_remainder; /* The remainder from the most recent split of a small request */
//用于存储 unstored bin,small bins 和 large bins 的 chunk 链表
mchunkptr bins[ NBINS * 2 - 2 ]; /* Normal bins packed as described above */
//ptmalloc 用一个 bit 来标识某一个 bin 中是否包含空闲 chunk
unsigned int binmap[ BINMAPSIZE ]; /* Bitmap of bins, help to speed up the process of determinating if a given bin is definitely empty.*/
struct malloc_state *next; /* Linked list, points to the next arena */
struct malloc_state *next_free; /* Linked list for free arenas. Access to this field is serialized by free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads; /* 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 system_mem; /* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T max_system_mem;
};
+附加介绍
Last remainder
- malloc 请求分配内存时,ptmalloc2 找到的 chunk 可能并不和申请的内存大小一致,这时候就将分割之后的剩余部分称之为 last remainder chunk 。
- unsort bin 也会存这一块。
- top chunk 分割剩下的部分不会作为 last remainder。
2、Heap - heap_info
- 子线程的arena可以有多片连续的内存,这些内存称为heap。
- 一个arena开始时只有一个heap,当这个heap空间用尽时,会获取新的heap。
- 每个heap都有一个heap_info 结构体,通过链表相连,保存了指向其所属arena的指针。
- main_arena只有一个heap,所以没有heap_info 结构,当main arena用尽空间后,会扩展当前的heap空间。
heap_info 结构体定义
typedef struct _heap_info
{
//堆对应的 arena 的地址
mstate ar_ptr; /* Arena for this heap. */
//prev 即记录了上一个 heap_info 的地址。每个堆的 heap_info 是通过单向链表进行链接的。
struct _heap_info *prev; /* Previous heap. */
//size 表示当前堆的大小
size_t size; /* Current size in bytes. */
size_t mprotect_size; /* Size in bytes that has been mprotected PROT_READ|PROT_WRITE. */
//确保对齐
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; /* Make sure the following data is properly aligned, particularly that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of MALLOC_ALIGNMENT. */
} heap_info;
3、Chunk - malloc_chunk
-
Top chunk
① 概念:程序第一次进行 malloc 的时候,heap 会被分为两块,一块给用户,剩下的那块就是 top chunk,是处于当前堆的物理地址最高的 chunk。
② 作用:当所有的 bin都无法满足用户请求的大小时,如果其大小不小于指定的大小,就进行分配,并将剩下的部分作为新的 top chunk。否则,就对 heap进行扩展后再进行分配。在 main arena中通过 sbrk 扩展 heap,而在 thread arena 中通过 mmap分配新的 heap。
③ 注意:top chunk 的 prev_inuse 比特位始终为 1,否则其前面的 chunk 就会被合并到top chunk 中。
④ 初始情况下,我们可以将 unsorted chunk(后面会介绍) 作为 top chunk。 -
chunk申请与释放过程
连续申请3个chunkA、B、C,释放chunkB,被放入bin,成为一个free chunk,再次申请一个chunkB大小的chunk时,malloc从bin中取出chunkB,回到一开始的状态。连续释放chunkA、B,他们相邻就会合成一个大chunk放入bin中
malloc_chunk结构体定义
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;
};
INTERNAL_SIZE_T在64位下是8字节,32位下是4字节
字段解释:
- prev_size:前一 chunk是空闲的话,记录其的大小;否则作为前一个chunk的data部分
- size:该 chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。最后3个比特位被用作状态标识,从高到低分别表示:
- NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1 表示不属于,0 表示属于。
- IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的。
- PREV_INUSE,当它为1时,表示上一个chunk是使用状态,否则为释放状态
- fd,bk:chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时启用,fd、bk分别指向下一个和上一个free chunk
- fd_nextsize, bk_nextsize:也是只有 chunk 空闲的时候才使用,不过其用于较大的 chunk(large chunk)。
Chunk 具体结构
allocated chunk
处于使用中的chunk分为由prev_size和size组成的chunk header,和后面供用户使用的 user data。每次 malloc 申请返回给用户的内存指针其实指向user data 的起始处。
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|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
free chunk
处于释放状态的chunk,fd、bk有效,下一个chunk的P比特位为0并且其prev_size表示这个free chunk的大小。因为bk之后的空间可能为0,所以chunk最小是32字节(64位)或16字节(32位),
包括chunk header和两个指针。
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|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
对上面相关结构的理解图:
Bin
bin概述
chunk被释放时glibc会将它们重新组合构成不同的bin链表,用户再次申请时就从中寻找合适的chunk返回给用户。不同大小区间的chunk划分到不同的bin中,一共有四种bin:Fast bin (特殊的bin)、Small bin、Large bin和Unsorted bin。这些bin都记录在malloc_state结构中:
- fastbinsY:一个bin数组,里面有NFASTBINS 个fast bin。
- bins:一个bin数组,共有126个bin,按顺序分别是:
- bin 1 — unsorted bin
- bin 2~63 — small bin
- bin 64~126 — large bin
…
fast bin
- 程序申请和释放的堆块较小,这类bin用单链表结构,采用LIFO(后进先出)策略
- fast bin的chunk不会合并,下一个chunk的PREV_INUSE始终为1
- 同一个fast bin的chunk大小相同,在fastbinsY中从小到大排序
- 序号0的fast bin容纳chunk大小为4*SIZE_SZ字节,随序号增加,chunk递增2*SIZE_SZ字节
fastbinsY数组示例 - 64位为例
fastbin相关源码
//定义了fastbinY数组的大小
#define NFASTBINS (fastbin_index(request2size(MAX_FAST_SIZE)) + 1)
//根据序号获取fastbinsY中对应的bin
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[ idx ])
//根据chunk大小获取位置。因为minsize=0x20,前两个index不可索引,要减2
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
//定义了fastbin的最大chunk值
#define MAX_FAST_SIZE (80 * SIZE_SZ / 4)
fastbinsY数组没有保存头节点,只保存malloc_chunk的fd成员,fd初始值为null,对应bin为空,直到chunk进来才保存chunk地址,如下图所示:
fastbinsY数组
FASTBIN_CONSOLIDATION_THRESHOLD
#define FASTBIN_CONSOLIDATION_THRESHOLD (65536UL)
fastbin 范围的 chunk不会和其它被释放的 chunk 合并。但是当释放的 chunk 与该 chunk 相邻的空闲 chunk 合并后的大小大于FASTBIN_CONSOLIDATION_THRESHOLD 时,就会触发malloc_consolidate 函数,将fastbin中的chunk与其他free chunk合并,然后移动到unsorted bin中。
global_max_fast
#define set_max_fast(s) \
global_max_fast = \
(((s) == 0) ? SMALLBIN_WIDTH : ((s + SIZE_SZ) & ~MALLOC_ALIGN_MASK))
#define get_max_fast() global_max_fast
fast bin中最大的chunk由global_max_fast 决定,一般在对初始化时设置,运行时也可以设置。
unsorted bin
- 一定大小的chunk被释放时,在进入small bin或者large bin 之前,会先加入unsorted bin。
- 在实践中,一个被释放的chunk常常很快就会被重新使用,所以将其先加入 unsorted bin 可以加快分配的速度。
- unsorted bin使用双链表结构,并采用FIFO(先进先出)的分配策略。与fastbinsY不同,unsorted bin中的chunk大小可能是不同的,并且由于是双链表结构,一个bin会占用bins 的两个元素。
bins中unsorted bin示例 - 64位系统为例
unsorted bin相关源码
获取 unsorted bin
#define unsorted_chunks(M) (bin_at(M, 1))
…
small bin
- 同一个small bin里 chunk的大小相同,采用双链表结构
- small bin在bins 里居第2到第63位,共62个。根据排序,每个 small bin的大小为2*SIZE_SZ*idx(idx表示 bins数组的下标)。
- 在64位系统下,最小的small chunk为2×8×2=32字节,最大的small chunk为2×8×63=1008字节。
- 由于small bin和 fast bin有重合的部分,所以这些chunk在某些情况下会被加入small bin 中。
bins中small bin和large bin示例 - 以64位系统为例
small bin相关源码
// 根据chunk的大小得到small bin对应的索引。
#define smallbin_index(sz) \
((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) \
: (((unsigned) (sz)) >> 3)) + \
SMALLBIN_CORRECTION)
…
large bin
- large bin在 bins里居第64 到第126位,共63个,被分成了6组,每组 bin所能容纳的chunk按顺序排成等差数列,公差分别如下。
从bins[64]开始的区间 | size |
---|---|
32个 bins of size | 64 |
16个 bins of size | 512 |
8个 bins of size | 4096 |
4个 bins of size | 32768 |
2个 bins of size | 262144 |
1个 bins of size | left size |
-
64位下,第一个large bin的 chunk最小为1024字节,第二个large bin的chunk最小为1024+64个字节(处于[1024,1024+64)之间的chunk都属于第一个 large bin),以此类推,32位也类似。
-
large bin 也是采用双链表结构,里面的chunk 从头结点的fd指针开始,按大小顺序进行排列。
-
为了加快检索速度,fd_nextsize 和bk_nextsize指针用于指向第一个与自己大小不同的chunk,所以也只有在加入了大小不同的 chunk时,这两个指针才会被修改。
large bin相关源码
// 根据chunk的大小得到large bin对应的索引。
#define largebin_index(sz) \
(SIZE_SZ == 8 ? largebin_index_64(sz)
: MALLOC_ALIGNMENT == 16 ? largebin_index_32_big(sz) \
: largebin_index_32(sz))
…
bins相关源码
//从bins中获取指定序号的bin。注意bin 0不存在
#define bin_at(m, i) \
(mbinptr)(((char *) &((m)->bins[ ((i) -1) * 2 ])) - \
offsetof(struct malloc_chunk, fd))
//获得当前bin的上一个bin
#define next_bin(b) ((mbinptr)((char *) (b) + (sizeof(mchunkptr) << 1)))
// 这两个宏可以用来遍历bin
// 获取 bin 的位于链表头的 chunk
#define first(b) ((b)->fd)
// 获取 bin 的位于链表尾的 chunk
#define last(b) ((b)->bk)
bin_at(m,i)宏定义中减去的offsetof(struct malloc_chunk, fd)就是prev_size和size成员的大小,因为bins数组只保存了双链表头节点的fd和bk指针,如图:
bins[0]和bins[1]是unsorted bin的fd和bk,bin_at(1)返回的是unsorted bin的头指针,这个指针指向的是bins[0]地址减去offsetof(struct malloc_chunk, fd)的位置。
chunk 相关宏
request2size
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) \
? MINSIZE \
: ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
#define MALLOC_ALIGNMENT (2*SIZE_SZ)
这个宏将请求的req转换为chunk头部(prevsize和size)的chunk大小,示例(默认MINSIZE=0x20):
- req∈[0,0x9),return 0x20
- req∈[0x9,0x18],return 0x20
对于上面第二条,除了用户申请的req,chunk大小还会加上0x10个字节,总共0x28字节,多出的0x8字节由下一个chunk的prev_size提供
chunk2mem()和mem2chunk
#define chunk2mem(p) ((void *) ((char *) (p) + 2 * SIZE_SZ)) //malloc时
#define mem2chunk(mem) ((mchunkptr)((char *) (mem) -2 * SIZE_SZ)) //free时
chunk指针与user data指针的转换
chunk状态相关
- 定义PREV_INUSE、 IS_MMAPPED、 NON_MAIN_ARENA,以及对三者镜像或运算
/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
#define PREV_INUSE 0x1
/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
#define IS_MMAPPED 0x2
/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
from a non-main arena. */
#define NON_MAIN_ARENA 0x4
// Bits to mask off when extracting size
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
- 通过chunk指针p,对某标志位进行提取、检查、置位和清除
#define prev_inuse(p) ((p)->mchunk_size & PREV_INUSE)
#define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)
#define set_non_main_arena(p) ((p)->mchunk_size |= NON_MAIN_ARENA)
/* extract p's inuse bit */
#define inuse(p) \
((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)
/* set/clear chunk as being inuse without otherwise disturbing */
#define set_inuse(p) \
((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size |= PREV_INUSE
#define clear_inuse(p) \
((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size &= ~(PREV_INUSE)
- 由于当前chunk的使用状态由下一个chunk的size成员的P比特位决定,通过下面的宏可以获得或修改当前chunk的inuse状态
/* check/set/clear inuse bits in known places */
#define inuse_bit_at_offset(p, s) \
(((mchunkptr)(((char *) (p)) + (s)))->mchunk_size & PREV_INUSE)
#define set_inuse_bit_at_offset(p, s) \
(((mchunkptr)(((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)
#define clear_inuse_bit_at_offset(p, s) \
(((mchunkptr)(((char *) (p)) + (s)))->mchunk_size &= ~(PREV_INUSE))
-
- set_head_size()修改size是不会修改当前chunk的标志位,但set_head()会。
- set_foot()修改一个chunk的prev_size时当前chunk一定要是释放状态,否则下一个chunk的prev_size没有意义
// SIZE_BITS = 7
#define set_head_size(p, s) \
((p)->mchunk_size = (((p)->mchunk_size & SIZE_BITS) | (s)))
/* Set size/use field */
#define set_head(p, s) ((p)->mchunk_size = (s))
/* Set size at footer (only when chunk is not in use) */
#define set_foot(p, s) \
(((mchunkptr)((char *) (p) + (s)))->mchunk_prev_size = (s))
#define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))
-
- next_chunk()将当前chunk地址加上当前chunk大小获得下一个chunk指针。
- prev_chunk()将当前chunk地址减去prev_size值获得上一个chunk指针,前提是上一个chunk属于释放状态。
- chunk_at_offset()将当前chunk地址加上s偏移处的位置视为一个chunk。
#define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - ((p)->prev_size)))
#define chunk_at_offset(p, s) ((mchunkptr)(((char *) (p)) + (s)))
…
malloc_consolidate()函数
fast bin中的chunk无法释放与相邻的free chunk合并,形成大量的内存碎片,此时glibc就会调用这个函数将fast bin中的chunk取出来与相邻free chunk合并后放入unsorted bin或者与top chunk合并成新的top chunk
static void malloc_consolidate(mstate av) {
mfastbinptr *fb; /* current fastbin being consolidated */
mfastbinptr *maxfb; /* last fastbin (for loop control) */
mchunkptr p; /* current chunk being consolidated */
mchunkptr nextp; /* next chunk to consolidate */
mchunkptr unsorted_bin; /* bin header */
mchunkptr first_unsorted; /* chunk to link to */
/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int nextinuse;
mchunkptr bck;
mchunkptr fwd;
//如果max_fast为0,调用malloc_init_state函数初始化av
if (get_max_fast() != 0) {
// 清除av->flags有关fast bin的标志位,表示所有fast bin都为空了
// 因为要合并 fastbin 中的 chunk 了。
clear_fastchunks(av);
//
unsorted_bin = unsorted_chunks(av);
// 按照 fd 顺序遍历 fastbin 的每一个 bin,将 bin 中的每一个 chunk 合并掉并移除,放入unsorted bin
maxfb = &fastbin(av, NFASTBINS - 1);
fb = &fastbin(av, 0);
do { //外层循环遍历fastbinsY的每一个fast bin
p = atomic_exchange_acq(fb, NULL); //将p的值换为fb
if (p != 0) { //若p=0(NULL),说明改fastbin为空
do { //内层循环遍历fast bin中的每一个chunk
check_inuse_chunk(av, p);
nextp = p->fd;
/* Slightly streamlined version of consolidation code in
* free() */
size = chunksize(p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
if (!prev_inuse(p)) { //向后合并
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
//如果下一个chunk不是top chunk,向前合并,并加入unsorted bin
if (nextchunk != av->top) {
// 判断 nextchunk 是否是空闲的。
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
if (!nextinuse) {
size += nextsize;
unlink(av, nextchunk, bck, fwd);
} else
// 设置 nextchunk 的 prev inuse 为0,以表明可以合并当前 fast chunk。
clear_inuse_bit_at_offset(nextchunk, 0);
first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;
first_unsorted->bk = p;
if (!in_smallbin_range(size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
//如果下一个chunk是top chunk,和top chunk合并,形成新的top chunk
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
} while ((p = nextp) != 0);
}
} while (fb++ != maxfb);
}
else {
malloc_init_state(av); //malloc初始化
// 在非调试情况下没有什么用,在调试情况下,做一些检测。
check_malloc_state(av);
}
…
malloc 相关源码
__libc_malloc
因为定义了宏定义strong_alias(__libc_malloc,malloc)
,glibc源码中的malloc函数实际上是__libc_malloc(),定义如下:
void *__libc_malloc(size_t bytes) {
mstate ar_ptr;
void * victim;
// 检查是否有内存分配钩子,如果有,调用钩子并返回。这个主要用于用户自定义的堆分配函数,方便用户快速修改堆分配函数并进行测试。
void *(*hook)(size_t, const void *) = atomic_forced_read(__malloc_hook);
if (__builtin_expect(hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS(0));
arena_get(ar_ptr, bytes); //寻找一个合适的 arena 来分配内存。
victim = _int_malloc(ar_ptr, bytes); //调用 _int_malloc 函数去申请对应的内存。
//如果没有找到合适的内存,ptmalloc 会尝试再去寻找一个可用的 arena,并分配内存。
if (!victim && ar_ptr != NULL) {
LIBC_PROBE(memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry(ar_ptr, bytes);
victim = _int_malloc(ar_ptr, bytes);
}
//申请到了arena,在退出之前解锁改arena。
if (ar_ptr != NULL) __libc_lock_unlock(ar_ptr->mutex);
assert(!victim || chunk_is_mmapped(mem2chunk(victim)) ||
ar_ptr == arena_for_chunk(mem2chunk(victim))); //检查
return victim;
}
…
_init_malloc
- _init_malloc是内存分配的核心函数,为了以最快的速度找到合适的堆块,glibc更根据申请堆块的大小、各个bin的状态仔细地安排了分配顺序和内存整理的时机
- 大概的搜索顺序:
fast bin -> small bin -> unsorted bin ->large bin -> bins -> top chunk -> 系统函数分配
源码
(1)首先定义一些变量,并将用户请求的byte转化为表示chunk的nb。
static void *_int_malloc(mstate av, size_t bytes) {
INTERNAL_SIZE_T nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin */
mchunkptr victim; /* inspected/selected chunk */
INTERNAL_SIZE_T size; /* its size */
int victim_index; /* its bin index */
mchunkptr remainder; /* remainder from a split */
unsigned long remainder_size; /* its size */
unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */
mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */
const char *errstr = NULL;
checked_request2size(bytes, nb);
如果没有合适的arena,就调用sysmalloc(),用mmap()分配chunk并返回
if (__glibc_unlikely(av == NULL)) {
void *p = sysmalloc(nb, av);
if (p != NULL) alloc_perturb(p, bytes);
return p;
}
(2)其次,检查fast bin中是否有合适的chunk
//如果nb在fast bin的范围内,就通过fast bin分配,这段代码可以在堆初始化之前运行
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast())) {
// 得到对应的fastbin的下标
idx = fastbin_index(nb);
// 得到对应的fastbin的头指针
mfastbinptr *fb = &fastbin(av, idx);
mchunkptr pp = *fb;
// 利用fd遍历对应的bin内是否有空闲的chunk块,
do {
victim = pp;
if (victim == NULL) break;
} while ((pp = catomic_compare_and_exchange_val_acq(fb, victim->fd,
victim)) != victim);
// 存在可以利用的chunk,检查后返回给用户
if (victim != 0) {
// 检查取到的 chunk 大小是否与相应的 fastbin 索引一致。防伪造
if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0)) {
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr(check_action, errstr, chunk2mem(victim), av);
return NULL;
}
// 细致的检查。。只有在 DEBUG 的时候有用
check_remalloced_chunk(av, victim, nb);
// 将获取的到chunk转换为mem模式
void *p = chunk2mem(victim);
// 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
alloc_perturb(p, bytes);
return p;
}
}
(3)检查small bin中是否有合适的chunk
//如果nb在small bin的范围内,就通过small bin分配,堆初始化可能在这里运行
if (in_smallbin_range(nb)) {
idx = smallbin_index(nb);
bin = bin_at(av, idx);
// 如果 victim = bin ,那说明该 bin 为空。
// 如果不相等,那么会有两种情况
if ((victim = last(bin)) != bin) {
// 第一种情况,small bin 还没有初始化。
if (victim == 0)
// 如果victim为NULL,调用malloc_consolidate函数并执行堆初始化,将 fast bins 中的 chunk 进行合并
malloc_consolidate(av);
// 第二种情况,small bin 中存在空闲的 chunk
else {
// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;
// 检查 bck->fd 是不是 victim,防止伪造
if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// 设置 victim 对应的 inuse 位
set_inuse_bit_at_offset(victim, nb);
// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
bin->bk = bck;
bck->fd = bin;
// 如果不是 main_arena,设置对应的标志
if (av != &main_arena) set_non_main_arena(victim);
// 细致的检查,非调试状态没有作用
check_malloced_chunk(av, victim, nb);
// 将申请到的 chunk 转化为对应的 mem 状态
void *p = chunk2mem(victim);
// 如果设置了 perturb_type , 则将获取到的chunk初始化为 perturb_type ^ 0xff
alloc_perturb(p, bytes);
return p;
}
}
}
(4)之后,整理fast bin,计算large bin的index
//到这里还不能满足,就调用malloc_consolidate()整理fastbins,或许就会有合适的chunk
else {
// 获取large bin的下标。
idx = largebin_index(nb);
// 如果存在fastbin的话,会处理 fastbin
if (have_fastchunks(av)) malloc_consolidate(av);
}
(5)接下来,函数进入一个大的外层for循环,包含_init_malloc()函数之后的所有过程,紧接着是内层的第一个while,它会遍历unsorted bin里的每一个chunk,如果大小正好合适,就将其取出,否则就放入small bin或large bin。这是这是唯一将chunk放入small bin或large bin的过程
for (;;) {
int iters = 0;
// 如果 unsorted bin 不为空
// First In First Out
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
// victim 为 unsorted bin 的最后一个 chunk
// bck 为 unsorted bin 的倒数第二个 chunk
bck = victim->bk;
// 判断得到的 chunk 是否满足要求,不能过小,也不能过大
// 一般 system_mem 的大小为132K
if (__builtin_expect(chunksize_nomask(victim) <= 2 * SIZE_SZ, 0) ||
__builtin_expect(chunksize_nomask(victim) > av->system_mem, 0))
malloc_printerr(check_action, "malloc(): memory corruption",
chunk2mem(victim), av);
// 得到victim对应的chunk大小。
size = chunksize(victim);
(6)在内层第一个循环内部,当请求的chunk属于small bin、unsorted bin 只有一个chunk为 last remainder并且满足拆分条件时,就将其拆分
if (in_smallbin_range(nb) && bck == unsorted_chunks(av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
/* split and reattach remainder */
// 获取新的 remainder 的大小
remainder_size = size - nb;
// 获取新的 remainder 的位置
remainder = chunk_at_offset(victim, nb);
// 更新 unsorted bin 的情况
unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
// 更新 av 中记录的 last_remainder
av->last_remainder = remainder;
// 更新last remainder的指针
remainder->bk = remainder->fd = unsorted_chunks(av);
if (!in_smallbin_range(remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 设置victim的头部,
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
// 设置 remainder 的头部
set_head(remainder, remainder_size | PREV_INUSE);
// 设置记录 remainder 大小的 prev_size 字段,因为此时 remainder 处于空闲状态。
set_foot(remainder, remainder_size);
// 细致的检查,非调试状态下没有作用
check_malloced_chunk(av, victim, nb);
// 将 victim 从 chunk 模式转化为mem模式
void *p = chunk2mem(victim);
// 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
alloc_perturb(p, bytes);
return p;
}
(7)接着,将chunk从unsorted bin中移除,如果大小正好合适,就返回给用户
if (size == nb) {
set_inuse_bit_at_offset(victim, size);
if (av != &main_arena) set_non_main_arena(victim);
check_malloced_chunk(av, victim, nb);
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}
(8)如果chunk大小不合适就将其插入到对应的bin中
if (in_smallbin_range(size)) {
victim_index = smallbin_index(size);
//bck指向头节点,fwd是头节点的fd节点。chunk会被插入头节点和fwd节点之间
bck = bin_at(av, victim_index);
fwd = bck->fd;
} else {
// 否则large bin 范围
victim_index = largebin_index(size);
bck = bin_at(av, victim_index); // 当前 large bin 的头部
fwd = bck->fd;
/* maintain large bins in sorted order */
//需要对双链表额外操作,从这里我们可以总结出,largebin 以 fd_nextsize 递减排序。
if (fwd != bck) { // 如果 large bin 链表不空
// 加速比较,应该不仅仅有这个考虑,因为链表里的 chunk 都会设置该位。
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
// 判断 bck->bk 是不是在 main arena。
assert(chunk_main_arena(bck->bk));
if ((unsigned long) (size) <
(unsigned long) chunksize_nomask(bck->bk)) {
// bck->bk 存储着相应 large bin 中最小的chunk。
// 如果遍历的 chunk 比当前最小的还要小,那就只需要插入到链表尾部。
// 令 fwd 指向 large bin 头
fwd = bck;
// 令 bck 指向 largin bin 尾部 chunk
bck = bck->bk;
// victim 的 fd_nextsize 指向 largin bin 的第一个 chunk
victim->fd_nextsize = fwd->fd;
// victim 的 bk_nextsize 指向原来链表的第一个 chunk 指向的 bk_nextsize
victim->bk_nextsize = fwd->fd->bk_nextsize;
// 原来链表的第一个 chunk 的 bk_nextsize 指向 victim
// 原来指向链表第一个 chunk 的 fd_nextsize 指向 victim
fwd->fd->bk_nextsize =
victim->bk_nextsize->fd_nextsize = victim;
} else { //如果不小于最小chunk,通过fd_nextsize找到不比他大的chunk
// 当前要插入的 victim 的大小大于最小的 chunk
// 判断 fwd 是否在 main arena
assert(chunk_main_arena(fwd));
// 从链表头部开始找到不比 victim 大的 chunk
while ((unsigned long) size < chunksize_nomask(fwd)) {
fwd = fwd->fd_nextsize;
assert(chunk_main_arena(fwd));
}
// 如果找到了一个和 victim 一样大的 chunk,
// 那就直接将 chunk 插入到该chunk的后面,并不修改 nextsize 指针。
if ((unsigned long) size ==
(unsigned long) chunksize_nomask(fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else {
// 如果找到的chunk和当前victim大小不一样
// 那么就需要构造 nextsize 双向链表了
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
} else
// 如果largre bin空的话,直接简单使得 fd_nextsize 与 bk_nextsize 构成一个双向链表即可。
victim->fd_nextsize = victim->bk_nextsize = victim;
}
//根据victim所在bin的序号在对应的binmap比特位上记录1
mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
// #define MAX_ITERS 10000
if (++iters >= MAX_ITERS) break;
} //到此,内层第一个循环结束
(9)如果用户申请的是large bin,在第一个循环结束后搜索large bin
chunk拆分过程
当用户申请较小chunk时会先将一个大的chunk进行拆分,合适的部分返回给用户,剩下的部分称为remainder则加入unsorted bin。
同时malloc_state中的last_remainder会记录最近拆分的remainder,起大小至少为MINSIZE(0x20)否则不能拆。
出现拆分的一种情况是,fast bin和small bin中都没有合适的chunk 同时unsorted bin中有且只有一个可拆分的chunk,并且改chunk是last remainder
if (!in_smallbin_range(nb)) {
bin = bin_at(av, idx);
// 如果对应的 bin 为空或者其中的chunk最大的也很小,那就跳过
// first(bin)=bin->fd 表示当前链表中最大的chunk
if ((victim = first(bin)) != bin &&
(unsigned long) chunksize_nomask(victim) >=
(unsigned long) (nb)) {
// 反向遍历链表,直到找到第一个不小于nb大小的chunk
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize(victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
// 如果最终取到的chunk不是该bin中的最后一个chunk,并且该chunk与其前面的chunk
// 的大小相同,那么我们就取其前面的chunk,这样可以避免调整bk_nextsize,fd_nextsize
// 链表。因为大小相同的chunk只有一个会被串在nextsize链上。
if (victim != last(bin) &&
chunksize_nomask(victim) == chunksize_nomask(victim->fd))
victim = victim->fd;
// 计算分配后剩余的大小
remainder_size = size - nb;
// 进行unlink
unlink(av, victim, bck, fwd);
// 如果改chunk-nb<MINSIZE,就直接返回给用户
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset(victim, size);
if (av != &main_arena) set_non_main_arena(victim);
}
//如果大于MINSIZE就将remainder加入到unsorted bin中
else {
// 获取剩下那部分chunk的指针,称为remainder
remainder = chunk_at_offset(victim, nb);
// 插入unsorted bin中
bck = unsorted_chunks(av);
fwd = bck->fd;
// 判断 unsorted bin 是否被破坏。
if (__glibc_unlikely(fwd->bk != bck)) {
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
// 如果不处于small bin范围内,就设置对应的字段
if (!in_smallbin_range(remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 设置分配的chunk的标记
set_head(victim,
nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
// 设置remainder的上一个chunk,即分配出去的chunk的使用状态
// 其余的不用管,直接从上面继承下来了
set_head(remainder, remainder_size | PREV_INUSE);
// 设置remainder的大小
set_foot(remainder, remainder_size);
}
// 检查
check_malloced_chunk(av, victim, nb);
// 转换为mem状态
void *p = chunk2mem(victim);
// 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
alloc_perturb(p, bytes);
return p;
}
}
(10)接下来进入内层第二个循环,根据binmap来搜索bin。因为申请的chunk大小所对应的bin没有找到合适的chunk,所以就从下一个bin开始搜索
++idx;
// 获取对应的bin
bin = bin_at(av, idx);
// 获取当前索引在binmap中的block索引
// #define idx2block(i) ((i) >> BINMAPSHIFT) ,BINMAPSHIFT=5
// Binmap按block管理,每个block为一个int,共32个bit,可以表示32个bin中是否有空闲chunk存在
// 所以这里是右移5
block = idx2block(idx);
// 获取当前块大小对应的映射,这里可以得知相应的bin中是否有空闲块
map = av->binmap[ block ];
// #define idx2bit(i) ((1U << ((i) & ((1U << BINMAPSHIFT) - 1))))
// 将idx对应的比特位设置为1,其它位为0
bit = idx2bit(idx);
for (;;) {
(11)在内层第二个循环内部,寻找第一个不为空的block,再根据比特位找到合适的bin
/*binmap数组的元素类型为unsigned int,4字节,32比特,
所以binmap的一个block能检查32个bin*/
if (bit > map || bit == 0) {
do {
// 如果++block>=BINMAPSIZE,说明遍历了所有bin且都为空
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
} while ((map = av->binmap[ block ]) == 0);
// 获取其对应的bin,因为该map中的chunk大小都比所需的chunk大,而且
// map本身不为0,所以必然存在满足需求的chunk。
bin = bin_at(av, (block << BINMAPSHIFT));
bit = 1;
}
// 找到block不为空的最小的bin对应的bit
while ((bit & map) == 0) {
bin = next_bin(bin);
bit <<= 1;
assert(bit != 0);
}
(12)然后检查bit对应的bin是否为空,是则清空对应比特位,下一个bin开始再次循环,否则将victim从bin中取出
// 获取对应的bin
victim = last(bin);
// 如果victim=bin,那么我们就将map对应的位清0,然后获取下一个bin
// 这种情况发生的概率应该很小。
if (victim == bin) {
av->binmap[ block ] = map &= ~bit; /* Write through */
bin = next_bin(bin);
bit <<= 1;
}
else {
// 获取对应victim的大小
size = chunksize(victim);
/* We know the first chunk in this bin is big enough to use. */
assert((unsigned long) (size) >= (unsigned long) (nb));
// 计算分割后剩余的大小
remainder_size = size - nb;
/* unlink */
unlink(av, victim, bck, fwd);
(13)将取出的victim进行切分并把remainder加入到unsorted bin,如果victim不够切,就直接返回给用户。内层第二个循环到此结束
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset(victim, size);
if (av != &main_arena) set_non_main_arena(victim);
}
/* Split */
// 如果够,尽管分割
else {
// 计算剩余的chunk的偏移
remainder = chunk_at_offset(victim, nb);
// 将剩余的chunk插入到unsorted bin中
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__glibc_unlikely(fwd->bk != bck)) {
errstr = "malloc(): corrupted unsorted chunks 2";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
// 如果在small bin范围内,就将其标记为remainder
if (in_smallbin_range(nb)) av->last_remainder = remainder;
if (!in_smallbin_range(remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 设置victim的使用状态
set_head(victim,
nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
// 设置remainder的使用状态,这里是为什么呢?
set_head(remainder, remainder_size | PREV_INUSE);
// 设置remainder的大小
set_foot(remainder, remainder_size);
}
// 检查
check_malloced_chunk(av, victim, nb);
// chunk状态转换到mem状态
void *p = chunk2mem(victim);
// 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
alloc_perturb(p, bytes);
return p;
}
(14)如果上面操作还不能满足,就只能从top chunk上进行切分
use_top:
// 获取当前的top chunk,并计算其对应的大小
victim = av->top;
size = chunksize(victim);
// 如果分割之后,top chunk 大小还大于MINSIZE,就进行切分
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) {
remainder_size = size - nb;
remainder = chunk_at_offset(victim, nb);
av->top = remainder;
// 这里设置 PREV_INUSE 是因为 top chunk 前面的 chunk 如果不是 fastbin,就必然会和
// top chunk 合并,所以这里设置了 PREV_INUSE。
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE);
check_malloced_chunk(av, victim, nb);
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}
// 否则如果有fastbins,就进行整理,等待外层循环做第二次尝试
else if (have_fastchunks(av)) {
// 先执行一次fast bin的合并
malloc_consolidate(av);
// 判断需要的chunk是在small bin范围内还是large bin范围内
// 并计算对应的索引
// 等待下次再看看是否可以
if (in_smallbin_range(nb))
idx = smallbin_index(nb);
else
idx = largebin_index(nb);
}
// 否则的话,我们就只能从系统中再次申请一点内存了。
// 调用sysmalloc()函数进行分配,外层循环及_init_malloc()函数到此结束
else {
void *p = sysmalloc(nb, av);
if (p != NULL) alloc_perturb(p, bytes);
return p;
}
主线程下,sysmalloc()的大概流程:
- 当申请的大小nb 大于 mp .mmap threshold 时,通过mmap(函数进行分配。其中mp .mmap threshold的默认大小为128×1024字
- 尝试用brk()扩展堆内存,形成新的 top chunk,而旧的 top chunk会被释放。然后从新的top chunk中切分出nb 大小的chunk,返回给用户。
…
free 相关源码
__libc_free
和malloc一样,free()函数实际上是__libc_free(),定义如下:
void __libc_free(void *mem) {
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
// 判断是否有钩子函数 __free_hook,有就执行这个函数并返回
void (*hook)(void *, const void *) = atomic_forced_read(__free_hook);
if (__builtin_expect(hook != NULL, 0)) {
(*hook)(mem, RETURN_ADDRESS(0));
return;
}
// free NULL没有作用
if (mem == 0) /* free(0) has no effect */
return;
// 将mem(指向user data的指针)转换为chunk状态(chunk指针)
p = mem2chunk(mem);
// 如果该块内存是mmap得到的,就用unmmap_chunk()函数释放
if (chunk_is_mmapped(p))
{
if (!mp_.no_dyn_threshold && chunksize_nomask(p) > mp_.mmap_threshold &&
chunksize_nomask(p) <= DEFAULT_MMAP_THRESHOLD_MAX &&
!DUMPED_MAIN_ARENA_CHUNK(p)) {
mp_.mmap_threshold = chunksize(p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE(memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk(p);
return;
}
// 根据chunk获得分配区arena的指针
ar_ptr = arena_for_chunk(p);
// 执行释放
_int_free(ar_ptr, p, 0);
}
…
_int_free
开头定义了一些所需变量
static void _int_free(mstate av, mchunkptr p, int have_lock) {
INTERNAL_SIZE_T size; /* its size */
mfastbinptr * fb; /* associated fastbin */
mchunkptr nextchunk; /* next contiguous chunk */
INTERNAL_SIZE_T nextsize; /* its size */
int nextinuse; /* true if nextchunk is used */
INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */
mchunkptr bck; /* misc temp for linking */
mchunkptr fwd; /* misc temp for linking */
const char *errstr = NULL;
int locked = 0;
size = chunksize(p);
获得想要释放的chunk大小,并对chunk做一些检查
//第一个条件筛掉一些特别大的size;第二个条件检查chunk是否对齐
if (__builtin_expect((uintptr_t) p > (uintptr_t) -size, 0) ||
__builtin_expect(misaligned_chunk(p), 0)) {
errstr = "free(): invalid pointer";
errout:
if (!have_lock && locked) __libc_lock_unlock(av->mutex);
malloc_printerr(check_action, errstr, chunk2mem(p), av);
return;
}
// 检查size是否过小以及size是否对齐
if (__glibc_unlikely(size < MINSIZE || !aligned_OK(size))) {
errstr = "free(): invalid size";
goto errout;
}
// 检查该chunk是否处于使用状态,非调试状态下没有作用
check_inuse_chunk(av, p);
p判断chunk是否在fast bin范围,是就插入fast bin
//如果size<global_max_fast,这将之加入fast bin中
if ((unsigned long) (size) <= (unsigned long) (get_max_fast())
#if TRIM_FASTBINS
//默认 #define TRIM_FASTBINS 0,因此默认情况下下面的语句不会执行
// 如果当前chunk是fast chunk,并且下一个chunk是top chunk,则不能插入
&& (chunk_at_offset(p, size) != av->top)
#endif
) {
// 下一个chunk的大小不能小于两倍的SIZE_SZ,并且
// 下一个chunk的大小不能大于system_mem, 一般为132k
// 如果出现这样的情况,就报错。
if (__builtin_expect(
chunksize_nomask(chunk_at_offset(p, size)) <= 2 * SIZE_SZ, 0) ||
__builtin_expect(
chunksize(chunk_at_offset(p, size)) >= av->system_mem, 0)) {
if (have_lock || ({
assert(locked == 0);
__libc_lock_lock(av->mutex);
locked = 1;
chunksize_nomask(chunk_at_offset(p, size)) <= 2 * SIZE_SZ ||
chunksize(chunk_at_offset(p, size)) >= av->system_mem;
})) {
errstr = "free(): invalid next size (fast)";
goto errout;
}
if (!have_lock) {
__libc_lock_unlock(av->mutex);
locked = 0;
}
}
// 将chunk的mem部分全部设置为perturb_byte
free_perturb(chunk2mem(p), size - 2 * SIZE_SZ);
// 设置fast chunk的标记位
set_fastchunks(av);
// 根据大小获取fast bin的索引
unsigned int idx = fastbin_index(size);
// 获取对应fastbin的头指针,被初始化后为NULL。
fb = &fastbin(av, idx);
/* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */
// 使用原子操作将P插入到链表中
mchunkptr old = *fb, old2;
unsigned int old_idx = ~0u;
do {
// 简单检查fast bin中第一个chunk是不是当前释放chunk,防止对 fast bin double free
if (__builtin_expect(old == p, 0)) {
errstr = "double free or corruption (fasttop)";
goto errout;
}
if (have_lock && old != NULL)
old_idx = fastbin_index(chunksize(old));
p->fd = old2 = old;
} while ((old = catomic_compare_and_exchange_val_rel(fb, p, old2)) !=
old2);
// 确保fast bin的加入前与加入后相同
if (have_lock && old != NULL && __builtin_expect(old_idx != idx, 0)) {
errstr = "invalid fastbin entry (free)";
goto errout;
}
}
chunk合并过程
当一个非fast bin的chunk释放:
- 顺序先向后(上)合并再向前(下)合并
- 向前合并合并的是top chunk就形成新的top chunk
- 向前合并的不是top chunk就加入unsorted bin中
else if (!chunk_is_mmapped(p)) {
if (!have_lock) {
__libc_lock_lock(av->mutex);
locked = 1;
}
nextchunk = chunk_at_offset(p, size);
..........
//向后合并,新chunk地址与上一个相同
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
//到这里就形成了一个新的free chunk,使用unlink()将其从链表中删除
unlink(av, p, bck, fwd);
}
// 如果下一个chunk不是top chunk
if (nextchunk != av->top) {
// 获取下一个 chunk 的使用状态
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
// 如果不在使用,合并,否则清空当前chunk的使用状态。
if (!nextinuse) {
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);
// 把 chunk 放在 unsorted chunk 链表的头部
bck = unsorted_chunks(av);
fwd = bck->fd;
// 简单的检查
if (__glibc_unlikely(fwd->bk != bck)) {
errstr = "free(): corrupted unsorted chunks";
goto errout;
}
p->fd = fwd;
p->bk = bck;
// 如果是 large chunk,那就设置nextsize指针字段为NULL。
if (!in_smallbin_range(size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
bck->fd = p;
fwd->bk = p;
set_head(p, size | PREV_INUSE);
set_foot(p, size);
check_free_chunk(av, p);
}
// 如果要释放的chunk的下一个chunk是top chunk,那就合并到 top chunk
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
check_chunk(av, p);
}
如果chunk不是mmap()生成的,就需要合并,先向后合并再向前合并。如果合并后的chunk超过了FASTBIN_CONSOLIDATION_THRESHOLD,就会整理fast bin 并向系统返回内存。_int_free()到此结束
if ((unsigned long) (size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
if (have_fastchunks(av))
malloc_consolidate(av); // 尝试整理fast bin
// 以下代码用来向系统返回内存,比如返回brk()申请的堆内存
if (av == &main_arena) {
#ifndef MORECORE_CANNOT_TRIM
// top chunk 大于当前的收缩阙值
if ((unsigned long) (chunksize(av->top)) >=
(unsigned long) (mp_.trim_threshold))
systrim(mp_.top_pad, av);
#endif // 非主分配区,则直接收缩heap
} else {
heap_info *heap = heap_for_ptr(top(av));
assert(heap->ar_ptr == av);
heap_trim(heap, mp_.top_pad);
}
}