fork()和vfork()有什么区别?

41

fork()vfork()有什么区别?vfork()是否像fork()一样返回?


6
fork的意思是在真实内核上执行函数。vfork的意思是通过代理在真实内核上执行函数。 - tchrist
6个回答

45

vfork的意图是,如果你只想在子进程中执行exec*,则消除复制整个进程映像的开销。因为exec*替换了子进程的整个映像,所以没有必要复制父进程的映像。

if ((pid = vfork()) == 0) {
  execl(..., NULL); /* after a successful execl the parent should be resumed */
  _exit(127); /* terminate the child in case execl fails */
}

对于其他类型的用途,vfork 是危险且不可预测的。

然而,在大多数当前的内核中,包括 Linux 在内,vfork 的主要优点已经消失,因为 fork 的实现方式。执行 fork 时,不是复制整个进程映像,而是使用写时复制技术。


4
基于 BSD 的系统仍然具有真正的 vfork 函数。请查看源代码。 - tchrist
9
Linux也有一个真正的vfork(2)。一般来说,我猜每个被移植到非MMU平台上的*nix都有一个真正的vfork(2) - ninjalj
3
需要注意 写时复制技术。仅在新进程尝试修改内存时才会复制内存。对于像Android这样的系统非常关键。 - Aniket Thakur

30
如前所述,vfork的说明页面清楚地阐明了它们之间的区别。这个主题forkvforkcloneexec进行了很好的描述。
以下是一些常被忽略的forkvfork之间的差异,我在一些Linux 2.6.3x嵌入式系统上遇到过这些差异。
即使使用写时复制技术,如果没有足够的内存来复制父进程使用的内存,fork将会失败。例如,如果父进程使用了2 GB的驻留内存(即已使用而不仅仅是分配的内存),如果剩余的空闲内存少于2 GB,则fork将失败。当你只想执行一个简单的程序并且永远不需要那么大的父进程地址空间时,这是非常令人沮丧的! vfork没有这个内存问题,因为它不会复制父地址空间。子进程更像是一个线程,在其中可以调用exec*_exit而不会损坏父进程。
由于内存页面表不会被复制,因此vforkfork快得多,并且vfork的执行时间不受父进程使用的内存量的影响,正如这里指出的那样:http://blog.famzah.net/2009/11/20/fork-gets-slower-as-parent-process-use-more-memory/。在性能临界或内存有限的情况下,vfork + exec* 可以成为 fork + exec* 的一个良好替代方案。问题在于其安全性较低,而且 man 手册指出 vfork 很可能会在将来被弃用。
一种更加安全且可移植的解决方案可能是查看 posix_spawn 函数,它是更高级别的,并提供了更多选项。它在可能的情况下安全使用 vfork,这取决于您传递给它的选项。我已成功地使用 posix_spawn 并克服了 fork + exec 给我的那个烦人的“双重内存检查问题”。 关于此主题的一篇非常好的页面,其中包含一些 posix_spawn 示例链接。

4
顺便提一下,Java 的 Runtime.exec() 传统上使用了 fork()/exec(),但据我所知,Java 1.7 将在可用的情况下使用 vfork()/exec()posix_spawn() - ninjalj

4

以下是我的 man 手册中的内容:

(来自 POSIX.1) vfork() 函数与 fork(2) 函数具有相同的效果,除了如果由 vfork() 创建的进程修改 pid_t 类型变量以外的任何数据,或者从调用 vfork() 的函数返回,或在成功调用 _exit(2) 或 exec(3) 系列函数之前调用任何其他函数,则行为是未定义的。

vfork() 与 fork(2) 不同之处在于父进程被挂起,直到子进程终止(正常地通过调用 _exit(2) 或异常地通过传递致命信号),或者它调用 execve(2)。在此之前,子进程与父进程共享所有内存,包括堆栈。子进程不能从当前函数返回或调用 exit(3),但可以调用 _exit(2)。


+1 RTF man pages。另外,由于xkcd对于每个SO问题都有漫画,请参考以下链接:http://xkcd.org/293 - user395760
2
值得注意的是,vfork() 大多是一个历史遗物 - 在具有合理写时复制语义的现代系统上,vfork() 通常不会带来太多好处(有时甚至本质上是 fork() 的别名!)。 - Nicholas Knight

2

一些系统有一个名为 vfork() 的系统调用,最初是作为 fork() 的低开销版本设计的。由于 fork() 涉及将整个进程的地址空间复制一遍,因此非常昂贵,因此引入了 vfork() 函数(在 3.0BSD 中)。

然而,自从引入 vfork() 以来,fork() 的实现已经得到了显着改善,尤其是引入了“写时复制”机制,这种机制通过允许两个进程引用同一物理内存,直到它们中的任何一个进程修改该内存,就可以透明地模拟进程地址空间的复制。这主要消除了 vfork() 的正当理由;实际上,很大一部分系统现在完全缺乏原始的 vfork() 功能。不过,出于兼容性考虑,可能仍然存在一个 vfork() 调用,它只是简单地调用 fork() 而不试图模拟所有 vfork() 的语义。

因此,实际上利用 fork() 和 vfork() 之间的任何差异都是非常不明智的。事实上,除非您确切知道自己想要什么,否则使用 vfork() 也可能是不明智的。

两者之间的基本区别在于,使用 vfork() 创建新进程时,父进程会被暂时挂起,而子进程可能会借用父进程的地址空间。这种奇怪的状态会一直持续到子进程退出或调用 execve(),此时父进程恢复。

这意味着 vfork() 的子进程必须小心避免意外修改父进程的变量。特别是,子进程不能从包含 vfork() 调用的函数中返回,也不能调用 exit()(如果需要退出,应该使用 _exit();事实上,这对于普通 fork() 的子进程也是如此)。


1

请参考这里wikipedia-

在某些系统上,vfork()与fork()相同。vfork()函数与fork()的区别仅在于子进程可以与调用进程(父进程)共享代码和数据。


1
两者的基本区别在于,当使用vfork()创建一个新进程时,父进程会被暂时挂起,而子进程可能会借用父进程的地址空间。这种奇怪的状态会一直持续,直到子进程退出或调用execve(),此时父进程才会继续执行。
这意味着vfork()的子进程必须小心,避免意外修改父进程的变量。特别是,子进程不能从包含vfork()调用的函数中返回,并且不能调用exit()(如果需要退出,应该使用_exit();实际上,对于普通fork()的子进程也是如此)。
然而,自从引入 vfork() 以来,fork() 的实现已经得到了极大的改进,其中最显著的是引入了“写时复制”技术,即通过允许两个进程引用相同的物理内存,直到它们中的任意一个修改该内存,从而透明地伪造了进程地址空间的复制。这在很大程度上消除了使用 vfork() 的正当理由;事实上,大部分系统现在完全缺乏 vfork() 的原始功能。但为了兼容性,可能仍然存在一个 vfork() 调用,它只是简单地调用 fork() 而不尝试模拟所有的 vfork() 语义。
因此,实际上利用 fork()vfork() 之间的任何差异都是非常不明智的。事实上,除非你确切知道为什么要使用它,否则使用 vfork() 可能是不明智的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接