12

Operating System Concepts and APUE say

With vfork(), the parent process is suspended, and the child process uses the address space of the parent. Because vfork() does not use copy-on-write, if the child process changes any pages of the parent’s address space, the altered pages will be visible to the parent once it resumes. Therefore, vfork() must be used with caution to ensure that the child process does not modify the address space of the parent.

vfork() is intended to be used when the child process calls exec() or exit() immediately after creation.

How shall I understand the last sentence?

When a child process created by vfork() calls exec(), doesn't exec() modify the address space of the parent process, by loading the new program?

When a child process created by vfork() calls exit(), does exit() not modify the address space of the parent process when terminating the child?

I have preference to Linux.

Thanks.

2 Answers 2

17

When a child process created by vfork() calls exec(), doesn't exec() 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 an exec(3) is done. Thus, for greater efficiency, BSD introduced the vfork() 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 to execve(2) or an exit occurred. The parent process was suspended while the child was using its resources. The use of vfork() was tricky: for example, not modifying data in the parent process depended on knowing which variables were held in a register.

26
  • Thanks. "exec() provides a new address space for the new program;" Is the normal behavior of exec() to load a program into the address space of the process? I didn't find in the two links where it creates a new address space either normally or particularly for vfork().
    – Tim
    Oct 15, 2018 at 16:30
  • 2
    What's funny is vfork() is winning against just about everything else now. It's ridiculously faster than fork() when you have a gigabyte of writable memory.
    – Joshua
    Oct 15, 2018 at 17:55
  • 2
    Please don't tell people to use posix_spawn. It is significantly harder to write correct code using posix_spawn than with plain old fork, and if you try, you may run into the brick wall of there not being a file action or attribute that does the thing you need done in between fork and exec. And it is not guaranteed to have vfork-like efficiency, so it doesn't even solve the problem people want it to solve.
    – zwol
    Oct 15, 2018 at 19:58
  • 1
    @zwol: That's really bad advice. While posix_spawn might lack functionality you want (you can solve this via an intermediary helper program, written in C or inline-on-cmdline shell script), any attempt to achieve what you want with vfork invokes dangerous undefined behavior. The specification for vfork does not allow calling random functions to setup the state for the child to inherit before execve, and attempts to do so may corrupt the parent's state. Oct 15, 2018 at 20:10
  • 1
    @Joshua: A modern implementation of posix_spawn performs roughly the same as vfork under most conditions. The ones where there's a difference tend to be exactly the cases where vfork is highly unsafe: where there are signal handlers installed that posix_spawn has to suppress from running in the child before exec. Oct 15, 2018 at 20:12
3

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.

21
  • 5
    Neither the GNU C library nor the musl C library implements posix_spawn() on top of Linux vfork(). They both implement it on top of __clone().
    – JdeBP
    Oct 15, 2018 at 17:27
  • 1
    @JdeBP: You know vfork() just calls clone() right? It's literally a one-liner in kernel.
    – Joshua
    Oct 15, 2018 at 18:00
  • 2
    "Important: On Linux, there is no real vfork() implementation." <-- This is not true and has not been true for at least a decade. If your shell benchmark is not observing any performance difference between vfork and fork on Linux, it is doing something wrong.
    – zwol
    Oct 15, 2018 at 19:49
  • 2
    The second half of this answer beginning with "Important: On Linux, there is no real vfork() implementation" is mostly or entirely wrong. Oct 15, 2018 at 20:21
  • 2
    Please do not make claims without verifying. The current Bourne Shell can be compiled with and without vfork support, so even if you believe that the debugging features from Linux are unable to give reliable results, you could compare the execution times for a configure call with and with vfork in the shell. I use a configure script with 800 tests. On Solaris, the Bourne Shell using vfork needs 30% less system cpu time in total compared to the fork case. On Linux, the same test results in less than 10% less system cpu time. On Solaris it is not 3x as there are many compiler calls included.
    – schily
    Oct 15, 2018 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.