A01 —— malloc 的底层:brk 和 mmap 如何协同
金句:你用的每一块 malloc 的内存,背后都是系统在为你做分配决策。
malloc 不是系统调用
malloc 是 glibc(ptmalloc)的库函数,不是系统调用。它内部可能:
malloc 的两条路:
1. brk/sbrk(系统调用)— 小对象
- 调整堆顶(heap top)
- 扩展/收缩堆
- 用户态分配(无需系统调用)
2. mmap/munmap(系统调用)— 大对象
- 创建匿名映射
- 直接向内核申请内存
- 分配/释放都是系统调用
阈值(默认 128KB):
size <= 128KB:走 brk(小对象)
size > 128KB:走 mmap(大对象)
性能对比:
- brk:~10ns(用户态操作)
- mmap:~500ns(系统调用 + TLB miss)brk 的工作原理
brk 系统调用:
int brk(void *addr);
- 设置堆顶(brk)为 addr
- addr > current brk:扩展堆
- addr < current brk:收缩堆
glibc 的 sbrk:
void *sbrk(intptr_t increment);
- brk = brk + increment
- 返回 old brk
glibc malloc 的堆管理:
- main_arena 管理主堆(通过 brk)
- top chunk:堆顶剩余空间
- 如果 top chunk 不够,调用 brk 扩展
brk 扩展过程:
1. malloc 发现 top chunk 不够
2. 调用 brk(new_brk) 扩展堆
3. 内核分配物理页(demand paging)
4. 更新 brk
5. 新的 top chunk 供分配使用
brk 收缩过程(free 时):
- free 的 chunk 不会主动归还给内核
- 只有 top chunk 可以 shrink
- 调用 brk(lower_addr) 收缩堆mmap 的工作原理
mmap 系统调用:
void *mmap(void *addr, size_t length, int prot,
int flags, int fd, off_t offset);
匿名映射(MAP_ANONYMOUS):
- fd = -1
- 不绑定文件,直接分配虚拟内存
用途:
1. 大块内存分配(> 128KB)
2. mlock 锁定内存(防止换出)
3. 创建共享内存
mmap 分配内存的特点:
- 分配的虚拟地址是连续的
- 物理页按需分配(demand paging)
- 释放用 munmap(整块释放,不能部分释放)
为什么大对象用 mmap:
1. 释放简单(munmap 整块)
2. 不会产生碎片(整块分配,整块释放)
3. 不会污染堆(堆是长期使用的)malloc 的完整策略
malloc(size) 的决策流程:
1. size <= 128KB(默认阈值)
→ 从 thread arena 的 bin 分配
→ 如果 bin 空,从 top chunk 取
→ 如果 top chunk 不够,调用 brk 扩展堆
2. size > 128KB
→ 直接调用 mmap 匿名映射
→ mmap 返回虚拟地址
→ munmap 释放
阈值可调(M_MMAP_THRESHOLD):
mallopt(M_MMAP_THRESHOLD, 64*1024);
// 把阈值改为 64KB
mmap 阈值的影响:
- 阈值小:更多使用 mmap(系统调用多,但碎片少)
- 阈值大:更少使用 mmap(系统调用少,但碎片多)glibc malloc 的关键变量:
- main_arena:主线程的 arena(管理堆)
- thread_arena:每个线程的 arena(避免锁)
- top chunk:堆顶剩余空间
- bin:空闲链表(small bin + large bin)
线程缓存(thread cache,glibc 2.26+):
- per-thread 的 fast bin
- 无锁分配
- 大大减少锁竞争/proc/self/maps 查看内存映射
$ cat /proc/self/maps | grep -E "(heap|anon)"
00...-00... rw-p [heap]
00...-00... rw-p [heap] ← 可能多个 heap(多个 arena)
$ cat /proc/self/maps | grep "[anon"
00...-00... rw-p [anon:00007f...] ← mmap 分配的匿名区域$ cat /proc/self/maps | head -20
00400000-00404000 r-xp /bin/cat ← 代码段
00600000-00601000 rw-p 00000000 00:00 0 ← 数据段
00...-00... rw-p [heap] ← 主堆(brk 扩展)
7f...-7f... rw-p [anon] ← mmap 匿名映射
malloc 用 mmap 分配的大块会显示为 [anon]
brk 管理的堆显示为 [heap]总结
- malloc 是库函数:不是系统调用,内部调用 brk/sbrk 或 mmap
- 阈值 128KB:≤128KB 用 brk(快),>128KB 用 mmap(释放简单)
- brk:调整堆顶(heap top),用户态操作(快),扩展/收缩堆
- mmap:匿名映射,系统调用(~500ns),整块分配/释放,不产生碎片
- 阈值可调:mallopt(M_MMAP_THRESHOLD) 可以修改默认值
下篇预告(A02):堆的内部结构是什么样的?arena、bin、chunk 的关系,以及 free list 是怎么管理的。
关注公众号「AI不着急」,回复”资料”获取内存学习路线图。
评论