我得到了写时复制背后的想法。当我fork时,堆被标记为CoW,当任何进程尝试更改它时,会生成一个副本。问题是:我是否必须在孩子的过程中释放它?假设一个父有一个动态char *数组,然后它就是forks。子进程打印一些const char,然后退出。子进程根本没有改变堆。会有内存泄漏吗?
编辑:我的子进程在堆上打印数组,但不修改它。 Valgrind说如果我不释放那个阵列就会有泄漏。我释放它时没有泄漏/内存错误。
答案 0 :(得分:5)
CoW只是一种懒惰的优化。您可以自由地认为fork()
总是制作完整的过程副本(至少在内存方面),没有任何延迟。但...
如果您确实准备了动态数据块,那么"传递"对于fork的子进程,然后在fork之后你有两个进程有两个动态数据块:父进程和子进程(都是副本)。当孩子退出时,它的内存副本被回收,但是父母应该在分叉之后立即释放该块。
更清楚,这是一个例子:
char *buf = malloc(123456);
// … fill buf for child …
int res = fork();
if (res == -1) {
fprintf(stderr, "fork failed\n");
exit(EXIT_FAILURE);
}
if (res == 0) {
// this is child process
// … do work with buf …
_Exit(EXIT_SUCCESS); // child reclaims buf by means of exit
}
// this is parent process
free(buf); // we don't need it in parent
// … other parent tasks here …
CoW在fork-exec技术中也是非常有用的优化,其中除了exec
之外,child只使用准备好的参数。 exec
用指定的可执行映像替换当前进程,保留开放描述符和其他内容(man 2 execve
中的更多内容)。在这样的fork之后复制的唯一页面只是当前的堆栈帧,这使得fork-exec非常有效。
有些系统还提供vfork
,这是非常严格的不公平版本的fork,但是在没有CoW的系统上,这是有效运行vfork-exec的唯一方法。
答案 1 :(得分:4)
首先是逻辑(以流程为中心)的视图:
分叉进程时,整个地址空间将按原样复制到新进程中。您的堆在两个进程中基本上都是重复的,并且两个进程都可以继续使用它,就像从未调用fork()
时的一个进程一样。这两个进程都可以释放在fork()
之前完成的分配,如果他们想重用与分配相关的地址范围,则必须这样做。 CoW映射只是一种不会改变这些语义的优化。
现在是物理(系统中心)视图:
您的系统内核不知道您使用malloc()
分配的数据范围,它只知道它在malloc()
请求时分配给进程的内存页面。当您致电fork()
时,它会将所有这些页面标记为CoW,并从两个流程中引用它们。如果两个进程中的任何一个写入任何CoW页面而另一个进程仍然存在,则它将陷入复制整个页面的系统。如果其中一个进程退出,它将至少降低这些页面的引用计数,这样就不必再复制它们了。
那么,在退出之前给孩子打电话free()
会发生什么?
好吧,free()
函数很可能会写入包含内存分配的页面,告诉malloc()
该块可以再次使用。这将陷入系统并复制页面,期望此操作需要一微秒或两秒。如果您的父进程在子进程尚未运行时调用free()
,则会发生同样的情况。但是,如果您的孩子没有释放页面并退出,内核将知道它不再需要执行CoW。如果父级之后释放并重新使用内存区域,则不需要复制。
我认为,您孩子所做的只是检查一些错误情况,如果符合则立即退出。在这种情况下,最谨慎的做法是忘记在孩子中调用free()
,让系统完成其工作。