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 …
2 回答
CoW只是一种懒惰的优化 . 你可以自由地认为
fork()
总是制作完整的过程副本(至少在内存方面),没有任何延迟 . 但…如果你准备动态数据块以“传递”给fork的子进程,那么在fork之后你有两个进程有两个动态数据块:父进程和子进程(都是副本) . 当孩子退出时,它的内存副本被回收,但是父母应该在分叉后自己释放那个块 .
更清楚,这是一个例子:
CoW在fork-exec技术中也是非常有用的优化,其中child除了
exec
之外什么都不做 .exec
用指定的可执行映像替换当前进程,保留开放描述符和其他内容(更多内容在man 2 execve
中) . 在这样的fork之后复制的唯一页面只是当前的堆栈帧,使得fork-exec非常有效 .有些系统还提供
vfork
,这是非常严格的不公平版本的fork,但是在没有CoW的系统上,这是有效运行vfork-exec的唯一方法 .First the logical (process centered) view:
分叉进程时,整个地址空间将按原样复制到新进程中 . 您的堆在两个进程中基本上都是重复的,并且两个进程都可以继续使用它,就像从未调用
fork()
时的一个进程一样 . 两个进程都可以释放在fork()
之前完成的分配,如果他们想重用与分配相关的地址范围,则必须这样做 . CoW映射只是一种不会改变这些语义的优化 .Now the physical (system centered) view:
您的系统内核不知道您使用
malloc()
分配的数据范围,它只知道它在malloc()
请求时分配给进程的内存页面 . 当您调用fork()
时,它会将所有这些页面标记为CoW,并从两个进程中引用它们 . 如果两个进程中的任何一个写入任何CoW页面而另一个进程仍然存在,则它将陷入复制整个页面的系统 . 如果其中一个进程退出,它将至少降低这些页面的引用计数,这样就不必再复制它们了 .那么,在退出之前在孩子中调用
free()
会发生什么?好吧,
free()
函数很可能会写入包含内存分配的页面,告诉malloc()
该块可以再次使用 . 这将陷入系统并复制页面,期望此操作需要一微秒或两秒 . 如果您的父进程在子进程尚未运行时调用free()
,则会发生同样的情况 . 但是,如果您的孩子没有释放页面并退出,内核将知道它不再需要执行CoW . 如果父进程释放并重新使用内存区域,则不需要进行复制 .我假设,您孩子所做的只是检查一些错误情况,如果符合则立即退出 . 在这种情况下,最谨慎的方法是忘记在孩子中调用
free()
,让系统完成它的工作 .