进程内存分布
进程内存分布
典型的 Linux
进程内存分布图,图片来自这里:
这张图中有映射段的位置,但是还有一个重要的部分的缺失,就是运行时的参数和环境变量,在 Linux/Unix 系统编程手册这本书第 6 章讲进程的内存分配里有给:
malloc
内存分配在映射段
当 malloc
申请分配的内存过大(128K
以上),内部将使用 mmap
而不是 brk
来分配内存。
测试代码:
|
|
输出如下图:
显而易见的几点:
- 程序
program break
的位置没变,说明分配的内存不在堆上;而且malloc
分配的地址与program break
的地址差异巨大,应该在不同的区。 - 不在堆上,而且离栈相对较近,且此时
malloc
分配的内存在栈的下方。
查看 /proc/pid/maps
目录下,获取动态库的映射地址。
关于 maps 里同一个动态库映射多次的原因的解释看这里。
用 python 快速的给分配出的内存地址和这里的 maps 的动态库地址作个排序:
|
|
地址升序排列:
可以看到 malloc
分配的内存区域与动态库的地址是交织在一起的。Linux/Unix 系统编程手册 49.7 章节有描述:
glibc 中的 malloc()实现使用 MAP_PRIVATE 匿名映射来分配大小大于 MMAP_ THRESHOLD 字节的内存块;MMAP_THRESHOLD 在默认情况下是 128 kB
符合这里分配 200k
时,直接将内存分配在动态区。
malloc 内存分配在堆上
如果分配的内存是100k(100 * 1024),程序的输出如下,可以看到 program break 的位置有变化,说明是在堆上分配的内存。
program break:0x663000 malloc:0x642900 program_break_0:0x663000 malloc:0x65b910 program_break_1:0x695000 malloc:0x674920 program_break_2:0x695000 malloc:0x68d930 program_break_3:0x6c7000 malloc:0x6a6940 program_break_4:0x6c7000 malloc:0x6bf950 program_break_5:0x6f9000 malloc:0x6d8960 program_break_6:0x6f9000 malloc:0x6f1970 program_break_7:0x72b000 malloc:0x70a980 program_break_8:0x72b000 malloc:0x723990 program_break_9:0x75d000 stack:0x7fff79915ac0 0x7fff79915ac0
两次分配内存之间的地址差值是 102416(0x65b910 - 0x642900 = 102416 = 100k + 16),100k 返回给 malloc 的调用者,多余的 16 字节用来保存长度相关信息。
总结
- 当调用者请求的内存超过临界值,
malloc
分配的内存是进程的映射区,和动态库的内存占用相同区域;小于临界值时,分配的内存才是在堆上。 - 当
malloc
内存分配在堆上时,实际会多分配几个字节(位于实际返回给调用者的地址之前),用来记录分配的内存的长度;这样free
才能正确的释放内存。 - 即便返回的内存是在堆上,
malloc
也可能并未调用brk
来改变program break
的位置:其一是因为program break
下边本来就有部分未使用的空闲内存(malloc
调用brk
会超额分配内存,多余的至于堆顶);其二在于free
所释放的内存保存在空闲内存列表里,malloc
会首先检查这个列表中是否有合适的内存块(2种策略 first-fit 和 best-fit),之后才会去调用brk
分配更多的堆内存。