A02 — Internal Structure of the Heap: Arena, Bin, and Chunk
Key Insight: The heap managed by malloc is a precision system of bins and chunks.
Structure of malloc_chunk
malloc 把内存分成 chunk(块)来管理:
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; // 前一个 chunk 的大小(如果前一个空闲)
INTERNAL_SIZE_T size; // 本 chunk 大小 + 标志位
malloc_chunk *fd; // 同 bin 的下一个 chunk
malloc_chunk *bk; // 同 bin 的上一个 chunk
// ... (用户数据从这里开始)
};
prev_size 的特殊用法:
- 如果前一个 chunk 是空闲的:prev_size = 前一个 chunk 的大小
- 如果前一个 chunk 是已分配的:prev_size 可以存储用户数据(复用)
size 的低 3 bits 是标志位:
- PREV_INUSE (0x1):前一个 chunk 已被分配
- IS_MMAPPED (0x2):chunk 通过 mmap 分配
- NON_MAIN_ARENA (0x4):chunk 不属于 main arena
最小 chunk(32-bit):32 bytes
- prev_size:4 bytes
- size:4 bytes
- fd:4 bytes
- bk:4 bytes
- 合计 16 bytes... 但需要对齐到 8 字节,所以实际 32 bytesStructure of Bins
Bin = 空闲 chunk 的链表
glibc 有多种 bin:
1. Fast bins(fastbin):
- 大小:16, 24, 32, 40, 48, 56, 64 bytes(共 7 个)
- 单向链表(fd only,无 bk)
- LIFO(后进先出)
- 不会被合并(保持大小)
- 分配 O(1),释放 O(1)
2. Small bins:
- 大小:32, 40, 48, ... ~ 1024 bytes(每 8 bytes 一个,共 62 个)
- 双向循环链表(fd + bk)
- 按大小排序
- 合并相邻空闲块
3. Large bins:
- 大小:>= 1024 bytes
- 双向循环链表
- 按大小排序(递减)
- 每个 bin 管理一个大小范围内的 chunk
4. Unsorted bin:
- 大小:任意(刚释放的块在这里)
- 双向循环链表
- 分配时会先检查 unsorted bin(可能返回小块)Bin 的数据结构:
struct malloc_state {
// fastbins
mfastbinptr fastbinsY[NFASTBINS]; // 7 个 fastbin 头
// smallbins + largebins
malloc_chunk *bins[NBINS * 2 - 2]; // 124 个 bin 头(双向链表)
// unsorted bin
malloc_chunk *bins[2]; // unsorted bin 头
};
bins[i] 是链表的头节点:
- bins[2*(i-NFASTBINS)] = fd(前向指针)
- bins[2*(i-NFASTBINS)+1] = bk(后向指针)Allocation and Free Flow
malloc(size) 分配流程:
1. 计算实际分配大小(size + overhead + 对齐)
2. 进入对应 size 的 fastbin 或 smallbin
3. 如果 bin 不空,取出第一个 chunk(O(1))
4. 如果 bin 空,检查 unsorted bin
5. 如果还空,合并 fastbin 到 smallbin
6. 如果仍然空,向 top chunk 要空间
7. top chunk 不够,调用 brk 扩展堆
示例:malloc(32)
32 -> 48 bytes(对齐到 8)
→ fastbin[5](32-byte chunk 的 fastbin)
→ 如果 fastbin[5] 非空,分配
---
free(ptr) 释放流程:
1. 检查 ptr 是否在 fastbin 范围(<= 64 bytes)
2. 如果是,插入 fastbin(O(1))
3. 如果不是,合并相邻的空闲 chunk
4. 放入 unsorted bin 或对应 size 的 bin
合并(consolidate):
- 检查下一个 chunk 是否空闲 → 合并
- 检查上一个 chunk 是否空闲(如果 PREV_INUSE=0)→ 合并
- 合并后放入 unsorted bin
注意:top chunk 不会和相邻 chunk 合并
- top chunk 是堆顶
- 只有 top chunk 可以 shrink(通过 brk)Top Chunk and Remainder
Top chunk 的特殊性:
top chunk = 堆顶的剩余空间
- 不属于任何 bin
- 是最后的后备空间
分配流程:
1. 所有 bin 都空
2. 向 top chunk 要空间
3. 如果 top chunk >= 需要的大小:
- 分配出去
- 剩余部分成为新的 top chunk
4. 如果 top chunk 不够:
- 调用 brk() 扩展堆
- 新的 top chunk = old top + new space
remainder(剩余):
假设:
top chunk = 1024 bytes
malloc(100) = 需要 128 bytes(含 overhead)
分配后:
分配出去:128 bytes
top chunk 剩余:1024 - 128 = 896 bytes
→ 896 bytes 成为新的 top chunk
如果 top chunk 太小(< 128 bytes):
- 调用 brk 扩展
- 不产生 remainder/proc/self/maps: Viewing Heap Structure
$ cat /proc/self/maps | grep heap
00...-00... rw-p [heap]
$ cat /proc/self/1/smaps | grep -A 20 "[heap]"
00...-00...
Rss: 132 kB
Pss: 132 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 12 kB
Private_Dirty: 120 kB
Ref 1
heap 区域是 brk 管理的区域。
$ cat /proc/self/smaps | grep -E "Anon|Heap"
00007f... rw-p [heap]
AnonHugePages: 0 kB
Anonymous: 8192 kB ← 堆使用的匿名页
对比 mmap 区域:
00007f... rw-p [anon]
Anonymous: 16384 kB ← mmap 匿名映射使用的页Summary
- malloc_chunk: prev_size + size (lower 3 bits are flags) + fd + bk + user data
- Bin Types: fastbin (7, singly-linked LIFO) / smallbin (62, doubly-linked) / largebin (63) / unsorted (merge staging)
- Allocation Flow: fastbin → smallbin → unsorted → top chunk → brk extension
- Free Flow: free → check coalescing → place in fastbin or unsorted
- Top chunk: Last resort; splitting produces a remainder; never merges with adjacent blocks
Next (A03): Why does frequent malloc/free slow down in multi-threaded programs? Arena contention and per-thread arena design.
Comments