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 主要在以下场景被调用:
-
非主 Arena 的 Top Chunk 空间不足:当
sysmalloc发现当前堆段的 Top Chunk 不够时,尝试grow_heap扩展。 -
堆段回收前的收缩:
heap_trim可能调用grow_heap的逆操作(传入负的diff)来收缩堆段。
注意事项
-
HEAP_MAX_SIZE限制:堆段最大为 64MB(32 位系统)或 64MB(64 位系统),超过则失败。 -
mprotect失败:如果mprotect失败(如内存不足),返回-1,调用者需要处理。 -
物理内存延迟分配:
mprotect只是改变虚拟内存权限,物理页在首次访问时才分配。 -
diff可以是负数:如果diff为负,且new_size < h->mprotect_size,则可能收缩堆段(但通常不直接调用)。 -
仅适用于非主 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,同时保持了堆段地址的连续性。

366

被折叠的 条评论
为什么被折叠?



