需求提出:数据库snapshot
之所以有这个需求,是因为我手头有一个数据库工程,我们打snapshot的时候,是在愿进程调用一个fork,构造了一个子进程,在子进程dump数据的。但是数据库嘛,吃内存是肯定的,于是在我的渣渣虚拟机上,就遇到了内存占用在top观察超过60%,没法fork进行snapshot的窘境。
于是我就想到这个问题:难道一个内存占用超过一定比例的进程,不能调用fork??毕竟,fork会拥有原进程所有的资料,只是fork之后新的进程只拥有当前运行的线程。所以fork之后,两个进程在top里看起来因该是一样的。那么,到底有没有可能,fork之后,两个进程加起来,memory百分比超过100呢?
观点一:必须可以,有虚拟内存映射
我写了个简单的代码,进行测试:
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int j = 1024 * atoi(argv[1]);
while(j--)
{
int *x = (int*)malloc(1024 * 1024);
memset(x, 0, 1024 * 1024);//注意这里一定要memset才会实际申请内存,否则只会是虚拟内存
}
printf("now ready to fork!\n");
sleep(10);
int pid = fork();
if(pid == 0)
{
sleep(5);
printf("child\n");
}else if(pid == -1)
{
sleep(2);
printf("fork failed:%d, %s\n", errno, strerror(errno));
}else
{
printf("child pid:%d\n", pid);
wait(pid);
sleep(10);
}
return 0;
}
编译运行,参数为申请内存数量,单位为MB,我在一个6GB内存,1GB缓存的机器上测试,得到这样的结果:
➜ fork ./a.out 3000
now ready to fork!
fork failed:12, Cannot allocate memory
嗯,申请3000MB内存就gg了。实际内存占用在top里面看,刚好是51%,有兴趣的可以自己测试下。
观点二:不可以,top里面看到的内存总和不能超过100%
实际上如果我不懂Linux内存管理,我也会这么认为,但是考虑到Linux的以下特性,我无论如何都不能接收这个答案:
- 交换分区,允许将内存换出到磁盘
- 延后分配,只在实际使用的时候分配内存(也就是为什么要调用memeset)
- Copy On Write,这个是最重要的,理论来说,fork的时候不拷贝内存,只在新进程需要修改对应区域的时候,才申请内存。但是这个fork调用,在调用的时候直接失败了,甚至都没有尝试fork一下。
综上所述,其实如果我fork一个进程,在子进程我不做任何操作,Linux实际上一点额外内存都不需要分配,不应该在fork的时候就失败,哪怕真的后面内存不够用了,也应该fork之后在算账啊。
答案揭晓:overcommit_memory
Linux内核实际上有一个选项专门解决这个问题:
/proc/sys/vm/overcommit_memory
该选项默认为0,如果该选项为1,则表示允许从一个高内存的进程fork一个子进程,两个进程加起来可以超过100%内存
有兴趣的同学可以试一下,将他设置为1,就可以在top里面看到memory的百分比加起来超过100%的景象了。