典型的 Linux
进程内存分布图,图片来自这里:
这张图中有映射段的位置,但是还有一个重要的部分的缺失,就是运行时的参数和环境变量,在 Linux/Unix 系统编程手册这本书第 6 章讲进程的内存分配里有给:
malloc
内存分配在映射段当 malloc
申请分配的内存过大(128K
以上),内部将使用 mmap
而不是 brk
来分配内存。
测试代码:
printf("program break:%p\n", sbrk(0));
char* ptr[10];
for(int i = 0; i < 10; ++i)
{
ptr[i] = (char* )malloc(200 * 1024);
printf("malloc:%p program_break_%d:%p\n", ptr[i], i, sbrk(0));
}
printf("stack:%p %p\n", &ptr, ptr);
while(1)
{
sleep(3);
}
输出如下图:
显而易见的几点:
program break
的位置没变,说明分配的内存不在堆上;而且 malloc
分配的地址与 program break
的地址差异巨大,应该在不同的区。malloc
分配的内存在栈的下方。查看 /proc/pid/maps
目录下,获取动态库的映射地址。
关于 maps 里同一个动态库映射多次的原因的解释看这里。
用 python 快速的给分配出的内存地址和这里的 maps 的动态库地址作个排序:
a = [
('0x7f423e4ad000', "/usr/lib64/libc-2.28.so"),
('0x7f423e666000', "/usr/lib64/libc-2.28.so"),
('0x7f423e865000', "/usr/lib64/libc-2.28.so"),
('0x7f423e869000', "/usr/lib64/libc-2.28.so"),
('0x7f423e86f000', "/usr/lib64/ld-2.28.so"),
('0x7f423ea97000', "/usr/lib64/ld-2.28.so"),
('0x7f423ea98000', "/usr/lib64/ld-2.28.so")] #动态库的地址
b = [
('0x7f423ea4c010', "program_break_0:0x2482000"),
('0x7f423ea19010', "program_break_1:0x2482000"),
('0x7f423e9e6010', "program_break_2:0x2482000"),
('0x7f423e9b3010', "program_break_3:0x2482000"),
('0x7f423e980010', "program_break_4:0x2482000"),
('0x7f423e94d010', "program_break_5:0x2482000"),
('0x7f423e91a010', "program_break_6:0x2482000"),
('0x7f423e8e7010', "program_break_7:0x2482000"),
('0x7f423e8b4010', "program_break_8:0x2482000"),
('0x7f423e47a010', "program_break_9:0x2482000")] #malloc 内存分配的地址
c = a + b
c.sort(key = lambda x : int(x[0], base = 16))
地址升序排列:
可以看到 malloc
分配的内存区域与动态库的地址是交织在一起的。Linux/Unix 系统编程手册 49.7 章节有描述:
glibc 中的 malloc()实现使用 MAP_PRIVATE 匿名映射来分配大小大于 MMAP_
THRESHOLD 字节的内存块;MMAP_THRESHOLD 在默认情况下是 128 kB
符合这里分配 200k
时,直接将内存分配在动态区。
如果分配的内存是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
分配更多的堆内存。