malloc grow_heap

ptmalloc2 中用于扩展现有非主 Arena 堆段的函数。它不创建新堆段,而是通过 mprotect 将堆段末尾的只读/不可访问内存变为可读写,从而增大堆段的可用空间。

函数签名

static int
grow_heap (heap_info *h, long diff)

参数说明

参数含义
h指向要扩展的堆段的 heap_info
diff需要增加的可读写空间大小(字节)

返回值0 表示成功,-1 表示失败。

核心逻辑拆解

1. 检查是否需要扩展
long new_size;

new_size = (long) h->size + diff;
if ((unsigned long) new_size > (unsigned long) HEAP_MAX_SIZE)
  return -1;
  • h->size 是堆段当前已映射的总大小。

  • new_size 是扩展后的大小。

  • 如果超过 HEAP_MAX_SIZE(通常是 64MB),返回失败。

2. 对齐到页边界
if ((unsigned long) new_size > h->mprotect_size)
  {
    size_t pagesize = GLRO (dl_pagesize);
    new_size = ALIGN_UP ((unsigned long) new_size, pagesize);
    ...
  }
  • h->mprotect_size 是堆段当前已设置为可读写的大小。

  • 如果 new_size 超过了 mprotect_size,说明需要调用 mprotect 扩大可写区域。

  • new_size 向上对齐到页边界。

3. 调用 mprotect 扩展可写区域
if (__mprotect ((char *) h + h->mprotect_size,
                (unsigned long) new_size - h->mprotect_size,
                PROT_READ | PROT_WRITE) != 0)
  return -1;

h->mprotect_size = new_size;
  • mprotect 的起始地址是 h + h->mprotect_size,即当前可写区域的末尾

  • 长度是 new_size - h->mprotect_size,即需要新增加的可写空间。

  • 权限设置为 PROT_READ | PROT_WRITE

  • 成功后更新 h->mprotect_size

4. 更新堆段大小
h->size = new_size;
return 0;

工作原理:预留 + 按需扩展

grow_heap 的设计基于一个重要的策略:预留大块地址空间,按需扩展可写区域

1. 预留大块地址空间

当 alloc_new_heap 创建堆段时,它会通过 mmap 映射一块比实际需要更大的地址空间(通常预留 HEAP_MAX_SIZE = 64MB),但只将初始需要的部分设置为 PROT_READ | PROT_WRITE,其余部分设置为 PROT_NONE(不可访问)。

堆段布局:
+------------------+------------------+------------------+
| heap_info        | 已映射,可读写   | 已映射,不可访问 |
| + malloc_state   | (mprotect_size)  | (PROT_NONE)      |
+------------------+------------------+------------------+
                   ↑                  ↑
                   可写区域末尾        预留区域
2. 按需扩展

当需要更多空间时,grow_heap 调用 mprotect,将预留区域的一部分变为可读写:

扩展后:
+------------------+------------------+------------------+
| heap_info        | 已映射,可读写   | 已映射,可读写   | 仍不可访问 |
+------------------+------------------+------------------+
                   ↑                  ↑
                   原可写末尾         新可写末尾

优势

  • 避免频繁 mmap:只需一次 mmap 预留大块地址空间,后续扩展用 mprotect 即可。

  • 地址连续:扩展后的堆段在虚拟地址空间中是连续的,便于管理。

  • 按需分配物理内存PROT_NONE 的区域不占用物理内存,只有 mprotect 变为可读写后,才会在首次访问时分配物理页。

与 alloc_new_heap 的对比

函数作用系统调用是否创建新堆段
alloc_new_heap分配新堆段mmap✅ 是
grow_heap扩展现有堆段的可写区域mprotect❌ 否

调用场景

grow_heap 主要在以下场景被调用:

  1. 非主 Arena 的 Top Chunk 空间不足:当 sysmalloc 发现当前堆段的 Top Chunk 不够时,尝试 grow_heap 扩展。

  2. 堆段回收前的收缩heap_trim 可能调用 grow_heap 的逆操作(传入负的 diff)来收缩堆段。

注意事项

  1. HEAP_MAX_SIZE 限制:堆段最大为 64MB(32 位系统)或 64MB(64 位系统),超过则失败。

  2. mprotect 失败:如果 mprotect 失败(如内存不足),返回 -1,调用者需要处理。

  3. 物理内存延迟分配mprotect 只是改变虚拟内存权限,物理页在首次访问时才分配。

  4. diff 可以是负数:如果 diff 为负,且 new_size < h->mprotect_size,则可能收缩堆段(但通常不直接调用)。

  5. 仅适用于非主 Arena:主 Arena 使用 brk 扩展,不使用 grow_heap

总结

要点说明
功能通过 mprotect 扩展现有堆段的可写区域
大小限制不超过 HEAP_MAX_SIZE(通常 64MB)
对齐新大小向上对齐到页边界
系统调用mprotect
返回值0 成功,-1 失败
与 alloc_new_heap 区别grow_heap 扩展现有堆段,alloc_new_heap创建新堆段

一句话理解

grow_heap 是 ptmalloc2 中“就地扩展”非主 Arena 堆段的函数。它不申请新的地址空间,而是通过 mprotect 将堆段末尾预留的、不可访问的内存变为可读写,从而增大可用空间。这种“预留 + 按需扩展”的策略,避免了频繁 mmap,同时保持了堆段地址的连续性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值