0%

【C】测验malloc的极限(二)

为什么有个续集

本来是准备写关于 OOM-killercgroup 的一篇文章,然而在准备过程中对上一篇博文的测量程序重新做验证时,发现了点有意思的东西,感觉还可以当作一道面试题 :p。

重新用 malloc 检测可用内存

上一篇博文中我通过不断申请 size=1024B 的块来测量内存,最终结果是进程被 SIGKILL。为了让程序效率高一点,我对它进行了如下改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// test1.c
#include <stdio.h>
#include <stdlib.h>

int main(){
void *p = 0;
int counter1 = 0, counter2 = 0;
while (p = malloc(1024*1024*1024))
counter1++;
printf("%d GB allocated\n", counter1);
while (p = malloc(1024*1024))
counter2++;
printf("%d MB allocated\n", counter2);
return 0;
}

一运行我就傻眼了,好家伙:

1
2
131061 GB allocated
0 MB allocated

难道是我代码写错了?那我再换个粒度进行测量:

1
2
3
4
5
6
7
8
9
10
11
12
// test2.c
#include <stdio.h>
#include <stdlib.h>

int main(){
void *p = 0;
int counter2 = 0;
while (p = malloc(1024*1024))
counter2++;
printf("%d MB allocated\n", counter2);
return 0;
}

结果呢,熟悉的 SIGKILL又出现了。

(愚笨的我此刻并没有意识到问题出在哪里),于是打开 htop 再次分别运行两个程序。结果发现 test1.c 运行时间较短,CPU 负载适中,Mem 部分增长, Swp压根没动;test2.c 运行时间较长,各项系统资源全面爆炸。

那么问题的原因就显而易见了:test1.cmalloc 虚假地分配了内存,而 test2.cmalloc 真实地分配了内存。

进一步验证分析

这里还要提一下 malloc 大致的工作原理:在初始化时,它会通过系统调用向操作系统申请一块内存,并用这块内存维护一个内存池。当遇到程序调用 malloc 申请小块内存时, malloc 会直接从内存池中找到最匹配的块交给调用者,避免了因系统调用导致的额外开销。而遇到程序调用 malloc 申请大块内存时,内存池中的存货显然不足以满足要求了,这时 malloc 就会再次通过系统调用(brk 或者 mmap,可以参考这篇文章)向操作系统申请内存。

什么叫虚假分配呢?就是只喊口号不干活。谁不干活呢?系统调用不干活。为什么不干活呢?懒。怕你占着茅坑不拉屎。当你真正要用这块申请来的内存的时候,我再真正分配给你。为了进一步验证这一点,我们对 test1.c 进行一点修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// test3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
void *p = 0;
int counter1 = 0;
while (p = malloc(1024*1024*1024)) {
counter1++;
memset(p, 1, 1024*1024*1024);
}
printf("%d GB allocated\n", counter1);
return 0;
}

再次运行,果真出现了 SIGKILL。

留一个小问题:为什么 test1.c 会申请得到一百多 TB 的内存呢?