A01 —— malloc 的底层:brk 和 mmap 如何协同

金句:你用的每一块 malloc 的内存,背后都是系统在为你做分配决策。


malloc 不是系统调用

malloc 是 glibc(ptmalloc)的库函数,不是系统调用。它内部可能:

Bash
malloc 的两条路:

1. brk/sbrk(系统调用)— 小对象
   - 调整堆顶(heap top)
   - 扩展/收缩堆
   - 用户态分配(无需系统调用)

2. mmap/munmap(系统调用)— 大对象
   - 创建匿名映射
   - 直接向内核申请内存
   - 分配/释放都是系统调用

阈值(默认 128KB):
  size <= 128KB:走 brk(小对象)
  size > 128KB:走 mmap(大对象)

性能对比:
  - brk:~10ns(用户态操作)
  - mmap:~500ns(系统调用 + TLB miss)

brk 的工作原理

Bash
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 的工作原理

Bash
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 的完整策略

Bash
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(系统调用少,但碎片多)

Bash
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 查看内存映射

Bash
$ 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 分配的匿名区域

Bash
$ 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不着急」,回复”资料”获取内存学习路线图。

最后修改: 2024年10月25日

作者

评论

发表评论

您的邮箱地址不会被公开。