7

我明白写时复制背后的想法。当我 fork 时,堆被标记为 CoW,并且当任何进程试图更改它时,都会制作一个副本。问题是:我是否必须在孩子的过程中释放它?假设一个父级有一个动态的 char *array,那么它会分叉。一个子进程打印一些 const char,然后退出。子进程根本没有改变堆。会不会有内存泄漏?

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

4

2 回答 2

6

CoW 只是一个惰性优化。您可能会自由地认为,fork()总是会毫不拖延地制作过程的完整副本(至少在内存方面)。但是……</p>

如果您确实准备了动态数据块以“传递”给 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 技术中也是非常有用的优化,在这种技术中,child 只做exec准备好的参数。exec用指定的可执行映像替换当前进程,保留打开的描述符和其他内容(更多内容见man 2 execve)。在这样的 fork 之后复制的唯一页面只是当前堆栈帧,这使得 fork-exec 非常有效。

一些系统还提供vfork了非常严格的不公平的 fork 版本,但在没有 CoW 的系统上,这是有效执行 vfork-exec 的唯一方法。

于 2014-02-24T18:24:05.180 回答
5

首先是逻辑(以流程为中心)视图:

当你 fork 一个进程时,整个地址空间被原样复制到一个新进程中。您的堆在两个进程中基本上是重复的,并且两个进程都可以继续使用它,就像一个进程fork()从未被调用过一样。两个进程都可以释放在 之前完成的分配,fork()如果他们想重用连接到分配的地址范围,它们必须这样做。CoW 映射只是一种不会改变这些语义的优化。


现在是物理(以系统为中心)视图:

您的系统内核不知道您使用分配的数据范围malloc(),它只知道它在请求时分配给进程的内存页malloc()。当您调用fork()它时,它将所有这些页面标记为 CoW,并从两个进程中引用它们。如果两个进程中的任何一个写入任何 CoW 页面,而另一个进程仍然存在,它将陷入复制整个页面的系统。如果其中一个进程退出,它至少会降低这些页面的引用计数,这样就不必再复制它们了。

那么,当您free()在退出之前调用孩子时会发生什么?
好吧,该free()函数很可能会写入包含内存分配的页面,以告知malloc()该块再次可用。这将陷入系统并复制页面,预计此操作需要一两微秒。free()如果你的父进程在孩子还活着的时候调用,同样的情况也会发生。但是,如果您的孩子没有释放页面并退出,内核将知道它不再需要执行 CoW。如果父级之后释放并重用内存区域,则不需要进行复制。


我假设,您的孩子所做的只是检查某些错误条件,如果满足则立即退出。在这种情况下,最谨慎的做法是忘记呼唤free()孩子,让系统完成它的工作。

于 2014-02-24T21:04:09.110 回答