你想知道的 vfork(2) 的全部

最近vfork(2)的问题一再被问到,这里做一个总结,以求终止关于它的疑问。:-)

1. 为啥会有vfork(2)这个东西?

man page中说得很清楚:

However, in the bad old days a fork(2) would require making a complete copy
of the caller’s data space, often needlessly, since usually immediately afterwards an
exec(3) is done. Thus, for greater efficiency, BSD introduced the vfork() system call…
所以其实就是历史原因。从这里我们也可以看出,既然Linux的fork(2)实现使用了COW,所以现在再使用vfork(2)是没太大必要的。后面还提到:
This system call will be eliminated when
proper system sharing mechanisms are implemented. Users should not
depend on the memory sharing semantics of vfork() as it will, in that
case, be made synonymous to fork(2).
2. 为什么使用vfork(2)的限制有那么多?
the behavior is undefined if the process created by vfork()
either modifies any data other than a variable of type pid_t used to
store the return value from vfork(), or returns from the function in
which vfork() was called, or calls any other function before success-
fully calling _exit(2) or one of the exec(3) family of functions.
首先,为什么我只能在其后调用execve(2)和_exit(2)?看内核源代码,vfork(2)其实是通过completion实现的,在do_fork()时wait_for_completion(),而只有在do_execve()和do_exit()时才complete()(mm_release()),所以这就决定了你除了调用这两个系统调用外调用别的都是错的。

其次,为什么我不能从当前的函数中返回?很简单,因为vfork()的实现用的是CLONE_VM,它决定了父子进程之间是完全共享内存的(而不是COW)。所以如果子进程对堆栈内容做了修改,对父进程也会有影响。

一个随之而来的问题是:既然它们共享内存,那为什么子进程执行execve(2)就不会覆盖父进程呢?因为虽然CLONE_VM让do_fork()跳过了复制mm_struct,但是do_execve()在bprm_mm_init()时又创建了一个新的mm_struct。

3. 什么时候我应该使用vfork(2)而不是fork(2)?

90%以上的情况下你不应该这么做。假设父子进程哪个先运行很可能是一个不好的设计,你应该重新设计一下你的程序,或者考虑用其它方法实现。