ptmalloc源码分析 - 分配区heap_info结构实现(05)

本文围绕ptmalloc非主分配区的heap_info展开。介绍了非主分配区特性,阐述了_heap_info结构体存储分配区基础信息。详细讲解了new_heap创建新堆空间、grow_heap对堆扩容、shrink_heap对堆缩容以及delete_heap删除堆的操作及实现逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、heap_info是什么?

二、heap_info结构体含义和实现

三、new_heap创建一个heap_info

四、grow_heap对heap进行size扩容

五、shrink_heap对heap进行size缩容

六、delete_heap删除一个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 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函数实现逻辑:

  1. 首先,判断默认设置的size大小,如果size需要在HEAP_MIN_SIZE和HEAP_MAX_SIZE之间(64位系统,HEAP_MIN_SIZE=32k,HEAP_MAX_SIZE=64M)。
  2. 如果size设置过大,则报错;如果过小,则默认使用HEAP_MIN_SIZE作为size的最小值
  3. 创建内存映射区域的时候,使用MMAP函数。每次分配以HEAP_MAX_SIZE为每一页的大小(64位系统 分配一次64M)
  4. 默认情况下,aligned_heap_area值为NULL,所以代码会直接进入p1的分配流程。p1的分配流程中,会一次性分配HEAP_MAX_SIZE*2两个页,如果分配成功,则将aligned_heap_area设置成第2页的起始位置
  5. 当第二次进来分配的时候,aligned_heap_area值不为空,说明前面已经分配过一次,并且aligned_heap_area保存了前一次分配第二页的起始地址,所以这次分配直接使用前一次分配的第二页。如果此次分配失败,则继续跳转到p1重新分配。
  6. 如果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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值