Why is vfork() intended to be used when the child process calls exec() or exit() immediately after creation?
When a child process created by
vfork()
callsexec()
, doesn'texec()
modify the address space of the parent process, by loading the new program?
No, exec()
provides a new address space for the new program; it doesn’t modify the parent address space. See for example the discussion of the exec
functions in POSIX, and the Linux execve()
manpage.
When a child process created by vfork() calls exit(), does exit() not modify the address space of the parent process when terminating the child?
Plain exit()
might – it runs exit hooks installed by the running program (including its libraries). vfork()
is more restrictive; thus, on Linux, it mandates the use of _exit()
which doesn’t call the C library’s clean-up functions.
vfork()
turned out to be quite difficult to get right; it’s been removed in current versions of the POSIX standard, and posix_spawn()
should be used instead.
However, unless you really know what you’re doing, you should not use either vfork()
or posix_spawn()
; stick to good old fork()
and exec()
.
The Linux manpage linked above provides more context:
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 afterward anexec(3)
is done. Thus, for greater efficiency, BSD introduced thevfork()
system call, which did not fully copy the address space of the parent process, but borrowed the parent's memory and thread of control until a call toexecve(2)
or an exit occurred. The parent process was suspended while the child was using its resources. The use ofvfork()
was tricky: for example, not modifying data in the parent process depended on knowing which variables were held in a register.
When you call vfork()
, a new process is created and that new process borrows the process image of the parent process with the exception of the stack. The child process is given an own new stack star however does not allow to return
from the function that called vfork()
.
While the child is running, the parent process is blocked, as the child borrowed the address space of the parent.
Regardless of what you do, everything that just accesses the stack modifies only the private stack of the child. If you however modify global data, this modifies the common data and thus also affects the parent.
Things that modify global data are e.g.:
calling malloc() or free()
using stdio
modifying signal settings
modifying variables that are not local to the function that called
vfork()
....
Once you call _exit()
(important, never call exit()
), the child is terminated and control is given back to the parent.
If you call any function from the exec*()
family, a new address space is created with new program code, new data and a part of the stack from the parent (see below). Once this is ready, the child no longer borrows the address space from the child, but uses an own address space.
The control is given back to the parent, as it's address space is no longer in use by another process.
Important: On Linux, there is no real vfork()
implementation. Linux rather implements vfork()
based on the Copy on Write fork()
concept introduced by SunOS-4.0 in 1988. In order to make users believe that they use vfork()
, Linux just sets up shared data and suspends the parent while the child did not call _exit()
or one of the exec*()
functions.
Linux therefore does not benefit from the fact that a real vfork()
does not need to set up an address space description for the child in the kernel. This results in a vfork()
that is not faster than fork()
. On systems that implement a real vfork()
, it is typically 3x faster than fork()
and affects the performance of shells that use vfork()
- ksh93
, the recent Bourne Shell
and csh
.
The reason why you should never call exit()
from the vfork()
ed child is that exit()
flushes stdio in case there is unflushed data from the time before calling vfork()
. This could cause strange results.
BTW: posix_spawn()
is implemented on top of vfork()
, so vfork()
is not going to be removed from the OS. It has been mentioned that Linux does not use vfork()
for posix_spawn()
.
For the the stack, there is few documentation, here is what the Solaris man page says:
The vfork() and vforkx() functions can normally be used the
same way as fork() and forkx(), respectively. The calling
procedure, however, should not return while running in the
child's context, since the eventual return from vfork() or
vforkx() in the parent would be to a stack frame that no
longer exists.
So the implementation may do whatever it likes. The Solaris implementation uses shared memory for the stack frame of the function calling vfork()
. No implementation grants access to older parts of the stack from the parent.