编辑:我的子进程在堆上打印数组,但不修改它。如果我不释放该数组,则Valgrind表示存在内存泄漏。释放后没有泄漏/内存错误。
CoW只是一种懒惰的优化。你可能认为fork()
总是在没有延迟的情况下完全复制进程(至少是内存方面)。但是……
如果您准备了动态数据块以“传递”给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 …
首先是逻辑视角(以进程为中心):
当您分叉一个进程时,整个地址空间将按原样复制到一个新的进程中。您的堆在两个进程中都被重复使用,就像如果fork()
没有被调用一样。两个进程都可以释放在fork()
之前完成的分配,并且如果他们想要重新使用与该分配相关联的地址范围,则必须这样做。 CoW映射只是不改变这些语义的优化。
现在是物理视角(以系统为中心):
您的系统内核不知道您使用malloc()
分配的数据范围,它只知道它已经为进程分配了内存页面。当您调用fork()
时,它将所有这些页面标记为CoW,并从两个进程引用它们。如果其中任何一个进程在其他进程仍然存在时写入任何CoW页面,则会陷入系统中,而系统会复制整个页面。如果其中一个进程退出,它至少会降低这些页面的引用计数,以使它们不必再被复制。
那么,在子进程退出之前调用free()
会发生什么?
好吧,free()
函数很可能会写入包含内存分配的页面,以告知malloc()
该块再次可用。这将陷入系统并复制页面,预计此操作需要花费一两微秒的时间。如果您的父进程在子进程仍然存在时调用free()
,同样会发生这种情况。但是,如果您的子级不释放页面并退出,则内核将知道它不必再执行CoW了。如果父级在此之后释放并重用内存区域,则无需进行复制。
我假设,你的子进程只是检查了一些错误条件,如果满足就立即退出。在这种情况下,最谨慎的方法是忘记在子进程中调用free()
,让系统自己完成它的工作。