ptmalloc2 中用于收缩非主 Arena 堆段的核心函数。当堆段顶部有大块空闲空间时,它尝试通过 shrink_heap 或 munmap 将多余的内存归还给操作系统,以降低进程的 RSS。
函数签名
static int heap_trim (heap_info *heap, size_t pad)
参数说明:
| 参数 | 含义 |
|---|---|
heap | 指向要收缩的堆段的 heap_info |
pad | 收缩后至少保留的空闲空间(填充量) |
返回值:0 表示成功收缩,-1 表示无法收缩。
核心逻辑拆解
1. 获取 Arena 和 Top Chunk
mstate ar_ptr = heap->ar_ptr; unsigned long pagesz = GLRO (dl_pagesize); mchunkptr top_chunk = top (ar_ptr), p; heap_info *prev_heap; long new_size, top_size, top_pad, extra, prev_size, misalign;
-
ar_ptr:堆段所属的 Arena。 -
top_chunk:当前 Arena 的 Top Chunk 指针。 -
top_size:Top Chunk 的总大小。
2. 检查 Top Chunk 是否属于当前堆段
if (top_chunk != (mchunkptr) ((char *) heap + heap->size - CHUNK_HDR_SZ)) return -1;
这是最关键的前置检查:Top Chunk 必须正好位于当前堆段的末尾。
-
heap + heap->size - CHUNK_HDR_SZ是堆段的最后一个 chunk 的起始位置。 -
如果 Top Chunk 不在这里,说明堆段顶部还有活跃的 chunk,无法收缩,直接返回
-1。
3. 计算 Top Chunk 的实际大小
top_size = chunksize (top_chunk);
4. 检查前一个堆段
if (heap->prev)
prev_heap = heap->prev;
else
prev_heap = NULL;
if (prev_heap != NULL) {
/* 计算前一个堆段顶部是否有空闲空间 */
...
}
非主 Arena 的堆段通过 prev 指针构成链表。如果当前堆段不是第一个,可能需要检查前一个堆段的状态。
5. 计算可收缩的大小
new_size = (long) heap->size - (long) top_size; new_size = ALIGN_DOWN (new_size, pagesz);
-
new_size= 堆段总大小 - Top Chunk 大小,再向下对齐到页边界。 -
这是收缩后堆段的大小。
6. 执行收缩
if (new_size < HEAP_MIN_SIZE) new_size = HEAP_MIN_SIZE; misalign = (char *) top_chunk - (char *) heap; if (new_size < misalign) new_size = misalign; if (new_size >= (long) heap->size) return -1; // 没有可收缩的空间 if (shrink_heap (heap, new_size - (long) heap->size) != 0) return -1;
-
确保
new_size不小于HEAP_MIN_SIZE。 -
确保
new_size不小于 Top Chunk 的偏移量(misalign)。 -
如果
new_size不小于当前大小,说明没有可收缩的空间,返回-1。 -
调用
shrink_heap执行实际收缩。
7. 更新 Top Chunk 的大小
top_chunk = (mchunkptr) ((char *) heap + new_size - CHUNK_HDR_SZ); set_head (top_chunk, (heap->size - new_size + top_size) | PREV_INUSE);
-
收缩后,Top Chunk 的位置下移到新的堆段末尾。
-
更新 Top Chunk 的
size字段,使其覆盖收缩后剩余的空闲空间。
8. 更新 Arena 的 Top Chunk 指针
if (top_chunk != ar_ptr->top) ar_ptr->top = top_chunk;
如果 Top Chunk 位置变了,更新 Arena 的 top 指针。
完整流程
heap_trim(heap, pad):
│
▼
1. 获取 Arena 和 Top Chunk
│
▼
2. Top Chunk 在堆段末尾?
├── 否 → 返回 -1(无法收缩)
└── 是 ↓
│
▼
3. 计算 new_size = heap->size - top_size,向下对齐
│
▼
4. 确保 new_size >= HEAP_MIN_SIZE 且 >= misalign
│
▼
5. new_size >= heap->size?
├── 是 → 返回 -1(无空间可收缩)
└── 否 ↓
│
▼
6. 调用 shrink_heap(heap, new_size - heap->size)
│
▼
7. 更新 Top Chunk 的位置和大小
│
▼
8. 更新 Arena 的 top 指针
│
▼
返回 0(成功)
与非主 Arena 堆段的关系
收缩前:
+------------------+------------------+------------------+
| heap_info | 已分配 chunk | Top Chunk |
| + malloc_state | | (大块空闲) |
+------------------+------------------+------------------+
↑ ↑
top_chunk 堆段末尾
收缩后:
+------------------+------------------+------------------+
| heap_info | 已分配 chunk | Top Chunk |
| + malloc_state | | (缩小后的空闲) |
+------------------+------------------+------------------+
↑ ↑
top_chunk 新堆段末尾
(下移)
相关函数对比
| 函数 | 作用 | 系统调用 |
|---|---|---|
heap_trim | 收缩非主 Arena 堆段 | mprotect(通过 shrink_heap) |
systrim | 收缩主 Arena 的 brk 堆 | brk |
shrink_heap | 收缩堆段的可写区域 | mprotect |
grow_heap | 扩展堆段的可写区域 | mprotect |
注意事项
-
Top Chunk 必须在堆段末尾:这是收缩的前提条件。如果 Top Chunk 下方有活跃的 chunk,无法收缩。
-
HEAP_MIN_SIZE限制:堆段不能收缩到小于HEAP_MIN_SIZE(通常 128KB)。 -
pad参数:调用者传入的填充量,确保收缩后仍保留足够的空闲空间,避免频繁扩展。 -
不解除
mmap映射:shrink_heap只改变内存权限,虚拟地址空间仍然保留。 -
仅适用于非主 Arena:主 Arena 使用
systrim,不使用heap_trim。
总结
| 要点 | 说明 |
|---|---|
| 功能 | 收缩非主 Arena 堆段,将顶部空闲内存归还系统 |
| 前提条件 | Top Chunk 必须位于堆段末尾 |
| 核心操作 | 调用 shrink_heap 收缩可写区域,更新 Top Chunk 位置和大小 |
| 限制 | 不能小于 HEAP_MIN_SIZE,不能小于 Top Chunk 偏移量 |
| 返回值 | 0 成功收缩,-1 无法收缩 |
与 systrim 区别 | heap_trim 用于非主 Arena,systrim 用于主 Arena |
一句话理解:
heap_trim是 ptmalloc2 中“收缩非主 Arena 堆段”的函数。它检查 Top Chunk 是否位于堆段末尾,如果是,则计算可收缩的空间,调用shrink_heap将多余内存归还系统,并更新 Top Chunk 的位置和大小。它是非主 Arena 内存回收的核心机制,与主 Arena 的systrim对称。

476

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



