目录
一、heap_info是什么?
二、heap_info结构图
三、new_heap的实现
四、grow_heap的实现
五、shrink_heap的实现
六、delete_heap的实现
一、heap_info是什么?
上一章节《ptmalloc源码分析 - 多线程争抢竞技场Arena的实现(04)》我们讲解了多线程环境下,面对线程之间的竞争,ptmalloc除了主分配区外,还会去创建非主分配区。因为线程在调用malloc的时候,首先会去获取一个分配区,如果当前的分配区都被锁定并且没有新的分配区可用的时候,ptmalloc就会去创建一个新的非主分配区。
我们也从上一章节,知道了非主分配区的几个特性:
- 每个进程有一个主分配区,也可以允许有多个非主分配区。
- 主分配区可以使用brk和mmap来分配,而非主分配区只能使用mmap来映射内存块
- 非主分配区的数量一旦增加,则不会减少。
- 主分配区和非主分配区形成一个环形链表进行管理。通过malloc_state->next来链接
在函数_int_new_arena中主要是创建一个新的分配区,该分配区主要是非主分配区类型。主分配区在ptmalloc_init中初始化,并且设置了全局变量main_arena的值。而_int_new_arena中主要调用的是new_heap来创建和初始化一个非主分配区。
这里主要包含几个部分:
-
_heap_info:非主分配区的结构体
-
new_heap(size_t size, size_t top_pad):创建一个新的非主分配区
-
grow_heap(heap_info *h, long diff):扩大一个分配区的size
-
shrink_heap(heap_info *h, long diff):缩小一个分配区的size
-
delete_heap(heap):删除一个分配区结构
二、heap_info结构图
_heap_info来存储分配区的基础信息:
-
ar_ptr:指向当前heap所属的竞技场Arena
-
prev:链表,指向前一个堆的heap_info结构
-
size:当前分配区大小
-
mprotect_size:记录了堆中多大的空间是可读写的
-
pad:通过一个数组的方式,包含该heap_info结构体所需要的内存地址。并且,能够按照0x10字节对齐(x86中则是8字节对齐)
typedef struct _heap_info {mstate ar_ptr; /* 指向当前heap所属的竞技场Arena Arena for this heap. */struct _heap_info *prev; /* 链表,指向前一个堆的heap Previous heap. */size_t size; /* 当前分配区大小 Current size in bytes. */size_t mprotect_size; /* 记录了堆中多大的空间是可读写的 Size in bytes that has been mprotectedPROT_READ|PROT_WRITE. *//* Make sure the following data is properly aligned, particularlythat sizeof (heap_info) + 2 * SIZE_SZ is a multiple ofMALLOC_ALIGNMENT. *//* 用以堆其该结构体,使其能够按照0x10字节对齐(x86中则是8字节对齐) */char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;
三、new_heap的实现
new_heap函数说明:创建一个新的堆空间 ,从 mmap 区域映射一块内存页来作为 heap
映射内存页的大小:
-
在 32 位系统上,该函数每次映射 1M 内存,映射的内存块地址按 1M 对齐;
-
在 64 为系统上,该函数映射 64M 内存,映射的内存块地址按 64M 对齐。
/* HEAP_MIN_SIZE 和 HEAP_MAX_SIZE 最小和最大值*/
/* HEAP_MAX_SIZE:根据操作系统取值,32位 1M;64位 64M*/
#define HEAP_MIN_SIZE (32 * 1024)
#ifndef HEAP_MAX_SIZE
# ifdef DEFAULT_MMAP_THRESHOLD_MAX
# define HEAP_MAX_SIZE (2 * DEFAULT_MMAP_THRESHOLD_MAX)
# else
# define HEAP_MAX_SIZE (1024 * 1024) /* must be a power of two */
# endif
#endif/* DEFAULT_MMAP_THRESHOLD_MAX : 定义不同操作系统下的值大小 */
# if __WORDSIZE == 32
# define DEFAULT_MMAP_THRESHOLD_MAX (512 * 1024) //512K
# else
# define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long)) //32M
# endif
#endif
new_heap函数实现逻辑:
- 首先,判断默认设置的size大小,如果size需要在HEAP_MIN_SIZE和HEAP_MAX_SIZE之间(64位系统,HEAP_MIN_SIZE=32k,HEAP_MAX_SIZE=64M)。
- 如果size设置过大,则报错;如果过小,则默认使用HEAP_MIN_SIZE作为size的最小值
- 创建内存映射区域的时候,使用MMAP函数。每次分配以HEAP_MAX_SIZE为每一页的大小(64位系统 分配一次64M)
- 默认情况下,aligned_heap_area值为NULL,所以代码会直接进入p1的分配流程。p1的分配流程中,会一次性分配HEAP_MAX_SIZE*2两个页,如果分配成功,则将aligned_heap_area设置成第2页的起始位置
- 当第二次进来分配的时候,aligned_heap_area值不为空,说明前面已经分配过一次,并且aligned_heap_area保存了前一次分配第二页的起始地址,所以这次分配直接使用前一次分配的第二页。如果此次分配失败,则继续跳转到p1重新分配。
- 如果p1分配两个页失败,则尝试分配1个页(大小HEAP_MAX_SIZE),如果还是失败则报错
/* 创建一个新的堆空间 从 mmap 区域映射一块内存页来作为 heap* 分配的页大小:* 在 32 位系统上,该函数每次映射 1M 内存,映射的内存块地址按 1M 对齐;* 在 64 为系统上,该函数映射 64M 内存,映射的内存块地址按 64M 对齐。*/
static heap_info *
new_heap(size_t size, size_t top_pad) {size_t pagesize = GLRO(dl_pagesize);char *p1, *p2;unsigned long ul;heap_info *h;/* size 在 HEAP_MIN_SIZE 和 HEAP_MAX_SIZE之间,最小:HEAP_MIN_SIZE,超过HEAP_MAX_SIZE 则返回0 */if (size + top_pad < HEAP_MIN_SIZE)size = HEAP_MIN_SIZE;else if (size + top_pad <= HEAP_MAX_SIZE)size += top_pad;else if (size > HEAP_MAX_SIZE)return 0;elsesize = HEAP_MAX_SIZE;size = ALIGN_UP(size, pagesize);/* A memory region aligned to a multiple of HEAP_MAX_SIZE is needed.No swap space needs to be reserved for the following largemapping (on Linux, this is the case for all non-writable mappingsanyway). *//* aligned_heap_area: 是上一次调用 mmap 分配内存的结束虚拟地址,并已经按照 HEAP_MAX_SIZE 大小对齐。* 如果aligned_heap_area不为空,则从上次虚拟内存地址开始映射HEAP_MAX_SIZE大小的地址 */p2 = MAP_FAILED;if (aligned_heap_area) {p2 = (char *) MMAP(aligned_heap_area, HEAP_MAX_SIZE, PROT_NONE,MAP_NORESERVE);aligned_heap_area = NULL;if (p2 != MAP_FAILED && ((unsigned long) p2 & (HEAP_MAX_SIZE - 1))) {__munmap(p2, HEAP_MAX_SIZE);p2 = MAP_FAILED;}}/* 如果第一次分配(aligned_heap_area=NULL) 或者 P2分配失败(aligned_heap_area不为NULL),则开始重新分配* p1表示第一次分配,调用MMAP分配2倍HEAP_MAX_SIZE的内存映射块,本次使用内存块的第一块部分,并将aligned_heap_area指向第二块部分* */if (p2 == MAP_FAILED) {p1 = (char *) MMAP(0, HEAP_MAX_SIZE << 1, PROT_NONE, MAP_NORESERVE); //分配一块两倍HEAP_MAX_SIZE大小的内存映射块,可以分两次使用if (p1 != MAP_FAILED) { //分配成功p2 = (char *) (((unsigned long) p1 + (HEAP_MAX_SIZE - 1))& ~(HEAP_MAX_SIZE - 1));ul = p2 - p1;if (ul)__munmap(p1, ul);elsealigned_heap_area = p2 + HEAP_MAX_SIZE; //aligned_heap_area 记录下一次分配的地址值__munmap(p2 + HEAP_MAX_SIZE, HEAP_MAX_SIZE - ul);} else {/* 如果P1分配2倍的HEAP_MAX_SIZE失败,则再次重试分配HEAP_MAX_SIZE,分配失败则报错*//* Try to take the chance that an allocation of only HEAP_MAX_SIZEis already aligned. */p2 = (char *) MMAP(0, HEAP_MAX_SIZE, PROT_NONE, MAP_NORESERVE);if (p2 == MAP_FAILED)return 0;if ((unsigned long) p2 & (HEAP_MAX_SIZE - 1)) {__munmap(p2, HEAP_MAX_SIZE);return 0;}}}if (__mprotect(p2, size, PROT_READ | PROT_WRITE) != 0) {__munmap(p2, HEAP_MAX_SIZE);return 0;}h = (heap_info *) p2; //写入heap_info结构h->size = size; //设置大小h->mprotect_size = size; //设置大小LIBC_PROBE(memory_heap_new, 2, h, h->size);return h;
}
四、grow_heap的实现
grow_heap:对堆进行扩容(但是size需要小于分配页的大小HEAP_MAX_SIZE);
实际就是调整size(扩大size,但是size需要小于分配页大小)
/*** 对堆进行扩容,但是size需要小于分配页的大小HEAP_MAX_SIZE* 实际就是调整size(扩大size,但是size需要小于分配页大小)*/
static int grow_heap(heap_info *h, long diff) {size_t pagesize = GLRO(dl_pagesize);long new_size;diff = ALIGN_UP(diff, pagesize);new_size = (long) h->size + diff;if ((unsigned long) new_size > (unsigned long) HEAP_MAX_SIZE)return -1;if ((unsigned long) new_size > h->mprotect_size) {if (__mprotect((char *) h + h->mprotect_size,(unsigned long) new_size - h->mprotect_size,PROT_READ | PROT_WRITE) != 0)return -2;h->mprotect_size = new_size;}h->size = new_size;LIBC_PROBE(memory_heap_more, 2, h, h->size);return 0;
}
五、shrink_heap的实现
shrink_heap:缩小堆区:实际就是缩小size,通过MMAP函数(参数:MAP_FIXED)
static int shrink_heap(heap_info *h, long diff) {long new_size;new_size = (long) h->size - diff;if (new_size < (long) sizeof(*h))return -1;/* Try to re-map the extra heap space freshly to save memory, and make itinaccessible. See malloc-sysdep.h to know when this is true. */if (__glibc_unlikely(check_may_shrink_heap())) {if ((char *) MMAP((char *) h + new_size, diff, PROT_NONE, MAP_FIXED) //丢弃操作== (char *) MAP_FAILED)return -2;h->mprotect_size = new_size;} else__madvise((char *) h + new_size, diff, MADV_DONTNEED);/*fprintf(stderr, "shrink %p %08lx\n", h, new_size);*/h->size = new_size;LIBC_PROBE(memory_heap_less, 2, h, h->size);return 0;
}
六、delete_heap的实现
/* Delete a heap. */#define delete_heap(heap) \do { \if ((char *) (heap) + HEAP_MAX_SIZE == aligned_heap_area) \aligned_heap_area = NULL; \__munmap ((char *) (heap), HEAP_MAX_SIZE); \} while (0)