理解 Linux 内存分配:malloc、brk、mmap 关系及 overcommit 参数
作为一名嵌入式系统或内核开发者,当我们调用 malloc() 时,内存究竟是如何运作的?我们通过实际示例来分析 Linux 中的内存分配过程,并理解其背后的底层机制。
1. 探索虚拟内存布局
对于任意一个进程,其内存布局可以通过查看 /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()分配的内存通常来自这里。 - 内存映射区域 (
r-xp):用于共享库以及通过mmap映射的文件。 - 栈区域 (
rw-p [stack]):存放局部变量及函数调用信息。 - 特殊区域 (
[vvar],[vdso],[vsyscall]):与内核相关的特殊内存区域,用于加速系统调用和时间读取。
值得注意的是,虚拟地址空间中存在大量'空洞'。这些是尚未使用的虚拟内存区域,在需要时,内核可以从这些区域中为进程分配新的内存。这些空隙的大小最终决定了单次能够分配的最大内存容量。
2. 内存分配是如何工作的
当你的程序调用 malloc() 时,通常会发生以下两种情况之一:
2.1 小块内存分配:堆(Heap)与 brk()
对于较小的内存分配请求,malloc() 会使用堆空间来完成分配。
// 分配一个小块内存
void *ptr = malloc(10 * 1024);
幕后发生了什么:
malloc()先检查当前堆中是否存在可用的空闲空间。- 如果可用空间不足,它会通过
brk()系统调用来扩展堆的边界。 - 接着,内存分配器为该内存块添加必要的内部管理信息(元数据)。
- 最后,
malloc()返回一个指向可用内存区域的指针。
我们可以通过 strace 来观察这一过程:
$ strace -e brk ./our_program
brk(NULL) = 0x403000
brk(0x424000) = 0x424000
第一次调用 brk(NULL) 用于获取当前的 program break(堆顶位置),第二次调用则在此基础上将堆空间向上扩展。


