释放未被更改的“写时复制”内存

时间:2014-02-24 18:11:09

标签: c memory-leaks fork copy-on-write

我得到了写时复制背后的想法。当我fork时,堆被标记为CoW,当任何进程尝试更改它时,会生成一个副本。问题是:我是否必须在孩子的过程中释放它?假设一个父有一个动态char *数组,然后它就是forks。子进程打印一些const char,然后退出。子进程根本没有改变堆。会有内存泄漏吗?

编辑:我的子进程在堆上打印数组,但不修改它。 Valgrind说如果我不释放那个阵列就会有泄漏。我释放它时没有泄漏/内存错误。

2 个答案:

答案 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(),让系统完成其工作。