目录
一、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 mprotected
PROT_READ|PROT_WRITE. */
/* Make sure the following data is properly aligned, particularly
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
MALLOC_ALIGNMENT. */
/* 用以堆其该结构体,使其能够按照0x10字节对齐(x86中则是8字节对齐) */
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;
三、new_heap创建一个heap_info
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;
else
size = 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 large
mapping (on Linux, this is the case for all non-writable mappings
anyway). */
/* 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);
else
aligned_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_SIZE
is 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对heap进行size扩容
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对heap进行size缩容
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 it
inaccessible. 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删除一个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)