Linux 进程内存分配:malloc、brk、mmap 与 overcommit
调用 malloc() 之后,内存并不是立刻'从物理 RAM 里掏出来'的。真正起作用的是进程的虚拟地址空间,malloc() 只是站在分配器这一层,把请求转成 brk() 或 mmap() 等内核接口。这个分配器怎么选、overcommit 怎么影响结果,实际跑一遍会比只看概念清楚得多。
看看进程的虚拟内存长什么样
进程的内存布局可以直接看 /proc/[pid]/maps:
$ cat /proc/14799/maps
00400000-00402000 r-xp 00000000 00:45 41291688 vaflmalloc
00402000-00403000 r--p 00001000 00:45 41291688 vaflmalloc
00403000-00404000 rw-p 00002000 00:45 41291688 vaflmalloc
00404000-00425000 rw-p 00000000 00:00 0 [heap]
...
这里能看到几块典型区域:
- 代码段 (
r-xp):可执行指令。 - 数据段 (
r--p):已初始化的全局变量、静态变量。 - 堆 (
rw-p [heap]):malloc()常用的空间来源。 - 映射区:共享库、文件映射,或者
mmap()出来的匿名内存。 - 栈 (
rw-p [stack]):局部变量和函数调用现场。 - 特殊区域:比如
[vvar]、[vdso]、[vsyscall],和内核加速路径有关。
虚拟地址空间里通常会有不少空洞。它们不是'没用的垃圾区',而是还没被进程占上的地址段。内核分配新映射时,往往就是从这些空隙里挑地方。单次能分到多大,最后还是要看地址空间是否还够完整。
malloc() 到底走哪条路
小块分配:先看堆,再用 brk() 扩一下
小块内存通常先从堆里找。比如:
void *ptr = malloc(10 * 1024);
分配器大致会这么做:
- 先检查当前堆里有没有现成的空闲块。
- 不够的话,调用
brk()把堆顶往上推。 - 给这块内存补上分配器自己的元数据。
- 把可用指针返回给调用者。
用 strace 看会更直观:
$ strace -e brk ./our_program
brk(NULL) = 0x403000
brk(0x424000) = 0x424000
第一次是查当前 program break,也就是堆顶。第二次是申请把堆顶抬高。
大块分配:直接 mmap() 更省事
当请求变大以后,malloc() 往往不再硬挤堆,而是直接走 mmap()。下面这个例子分配 20MB:


