2021年末面试蔚来汽车,面试官考察了malloc/free的实现机制。当时看过相关的文章,有一点印象,稍微说了一点东西,不过自己感到不满意。今天尝试研究malloc的实现细节,看了几篇博文,发现众说纷纭,且实现比较复杂。在此,对malloc的实现机制进行研究,了解其大概的工作机制,没有深究细节。
先说结论:
内存的分配:
可以把内存空间的分配分为两个过程:
1)从操作系统获取到内存,实际获取到的内存一般比进程申请的空间大一些。
2)从申请到的空间中拿出来一部分给到进程。
所以,一般会有部分备用的内存空间。所以,就有了下面的结论。
1.如果进程申请的空间小于备用的空间,则直接把内存分配给进程。
2.如果进程申请的空间大于备用的空间,
1)申请的空间<128KB,通过系统调用brk在现有地址的基础上扩张,即新分配的空间和之前的空间是连续的。
2)申请的空间>128KB,通过mmap在物理内存映射一段空间。
内存的释放:
试验环境:ubuntu虚拟机,gcc:Ubuntu 9.4.0-1ubuntu1~20.04.1
测试代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
typedef unsigned char uint8;
int main()
{
//step1
getchar();
uint8* p1 = (uint8*)malloc(127 * 1024 * sizeof(uint8));
printf("p1:%p,size:%ld\n", p1, malloc_usable_size(p1));
//step2
getchar();
free(p1);
//step3
getchar();
uint8* p2 = (uint8*)malloc(127 * 1024 * sizeof(uint8));
printf("p2:%p,size:%ld\n", p2, malloc_usable_size(p2));
//step4
getchar();
uint8* p3 = (uint8*)malloc(127 * 1024 * sizeof(uint8));
printf("p3:%p,size:%ld\n", p3, malloc_usable_size(p2));
//step5
getchar();
uint8* p4 = (uint8*)malloc(500 * 1024 * sizeof(uint8));
printf("p4:%p,size:%ld\n", p4, malloc_usable_size(p4));
//step6
getchar();
uint8* p5 = (uint8*)malloc(1024 * sizeof(uint8));
printf("p5:%p,size:%ld\n", p5, malloc_usable_size(p5));
//step7
getchar();
free(p5);
//step8
getchar();
free(p4);
getchar();
return 0;
}
测试方法:
1.通过strace跟踪进程执行的过程。
2.通过cat /proc/PID/maps观察堆区的分配情况。
测试过程和分析:
strace ./a.out
略
brk(NULL) = 0x55c726c6e000
brk(0x55c726c8f000) = 0x55c726c8f000
//进程启动之后,系统自动分配了0x55c726c8f000-0x55c726c6e000,约为400K的内存,这部分内存,是备用的。
//step1:55c726c6e000-55c726c8f000 [heap]
//进程请求分配127K内存,由于系统已有400K空余的内存,够用,所以,直接给进程返回一块内存。起始地址为p1=0x55c726c6e6b0,和预留的起始地址相比,有一定的偏移量
read(0,
"\n", 1024) = 1
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "p1:0x55c726c6e6b0,size:130056\n", 30p1:0x55c726c6e6b0,size:130056
) = 30
//step2:55c726c6e000-55c726c8f000 [heap]
//free(p1),系统只是把这块空间标记为可用的,并没有真正的释放其内存空间。
read(0,
"\n", 1024) = 1
//step3:55c726c6e000-55c726c8f000 [heap]
//再次请求分配127K的内存,备用的内存空间够用,同step1,直接分配给进程。
read(0,
"\n", 1024) = 1
write(1, "p2:0x55c726c6e6b0,size:130056\n", 30p2:0x55c726c6e6b0,size:130056
) = 30
//step4:55c726c6e000-55c726ccf000 [heap]
//再次请求分配129K的内存空间,之前剩余的备用空间约为400K-127K-127K=146K,不够用了。而且,请求分配的内存<128K,在原来地址的基础上,通过brk扩充内存空间,即和原来的内存是连续的
read(0,
"\n", 1024) = 1
brk(0x55c726ccf000) = 0x55c726ccf000
write(1, "p3:0x55c726c8e6d0,size:130056\n", 30p3:0x55c726c8e6d0,size:130056
) = 30
//step5
//55c726c6e000-55c726ccf000 [heap]
//f2b3313f000-7f2b331bd000
//再次请求分配500K的内存空间,之前剩余的备用空间不够用了,而且,请求分配的内存>128K,通过mmap在物理内存映射一块空间,且和原来的空间是不连续的
read(0,
"\n", 1024) = 1
mmap(NULL, 516096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2b3313f000
write(1, "p4:0x7f2b3313f010,size:516080\n", 30p4:0x7f2b3313f010,size:516080
) = 28
//step6
//55c726c6e000-55c726ccf000 [heap]
//f2b3313f000-7f2b331bd000
//同step1
read(0,
"\n", 1024) = 1
<pre>write(1, "p5:0x55c726cae2e0,size:1032\n", 28p5:0x55c726cae2e0,size:1032</pre>
//step7
//55c726c6e000-55c726ccf000 [heap]
//f2b3313f000-7f2b331bd000
//同step2
read(0,
"\n", 1024) = 1
//step8:55c726c6e000-55c726ccf000 [heap]
//通过munmap释放内存
read(0,
"\n", 1024) = 1
munmap(0x7f2b3313f000, 516096)