vfork()
of 3
Code Examples
What is vfork()?
vfork() is a variant of fork() designed for the specific case where the child process will immediately call exec() or _exit(). It was created in the days before copy-on-write existed, when fork() literally copied all of the parent’s memory — which was extremely slow. Today, with CoW, vfork() is largely obsolete but still appears in legacy code and embedded systems.
| Era | fork() behavior | Problem | Solution |
|---|---|---|---|
| Pre-CoW (1970s) | Copied ALL parent pages immediately | Expensive if immediately followed by exec() | vfork() created to avoid copy entirely |
| With CoW (1980s+) | Defers copy until write (fast) | vfork() still slightly faster (no page table setup) | vfork() still used in some embedded code |
| Modern Linux | fork() is very fast with CoW | vfork() dangerous, rarely useful | POSIX marks vfork() obsolescent |
#include <unistd.h>
pid_t vfork(void);
/* Same return values as fork():
Parent: returns child's PID (after child calls exec or _exit)
Child : returns 0
Error : -1 */
What vfork() guarantees (3 unique behaviors):
Child runs in the SAME virtual address space as the parent. No page tables copied, no CoW setup. Zero memory overhead at creation time.
The parent process is suspended (blocked) until the child calls exec() or _exit(). They cannot run simultaneously — there is only one address space.
Any other action (modifying variables, calling most functions, returning from the function that called vfork) causes undefined behavior.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
printf("[Parent PID=%d] Before vfork\n", getpid());
pid_t pid = vfork();
if (pid == -1) { perror("vfork"); exit(1); }
if (pid == 0) {
/* CHILD: runs in parent's address space.
Parent is suspended until we exec or _exit. */
printf("[Child PID=%d] In child, about to exec\n",
getpid());
/* MUST call exec() or _exit() — nothing else! */
char *argv[] = { "echo", "Hello from exec'd process", NULL };
execvp("echo", argv);
/* Only reached if execvp fails */
perror("execvp");
_exit(EXIT_FAILURE); /* MUST use _exit(), NOT exit() */
}
/* Parent resumes here AFTER child calls exec or _exit */
printf("[Parent] Child done (PID=%d). Parent resumed.\n",
(int)pid);
wait(NULL);
return 0;
}
1. Call
execvp() (or any exec variant) — or2. Call
_exit() (NOT exit(), NOT return)Any other action = undefined behavior, likely crashes both processes.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
int shared_value = 100; /* on parent's stack */
pid_t pid = vfork();
if (pid == -1) { perror("vfork"); exit(1); }
if (pid == 0) {
/* WARNING: modifying shared_value here modifies
the PARENT's variable (same address space)! */
/* This is dangerous — shown only for education */
shared_value = 999; /* modifies parent's stack! */
printf("[Child] shared_value = %d (PARENT's var!)\n",
shared_value);
/* _exit() resumes the parent WITHOUT calling
atexit handlers or flushing stdio */
_exit(42);
}
/* Parent resumes. shared_value was modified by child! */
int status;
wait(&status);
printf("[Parent] shared_value = %d (was modified by child!)\n",
shared_value);
printf("[Parent] Child exit status = %d\n",
WEXITSTATUS(status));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
/* The ONLY safe vfork() pattern:
1. vfork()
2. In child: exec() immediately
3. On exec failure: _exit() only
4. No local variable modification
5. No function calls that use stack */
int run_program(const char *path, char *const argv[])
{
pid_t pid;
int status;
pid = vfork();
if (pid == -1) return -1;
if (pid == 0) {
/* Child: exec immediately, nothing else */
execv(path, argv);
/* exec failed */
_exit(127); /* 127 = "command not found" convention */
}
/* Parent: wait for child */
if (waitpid(pid, &status, 0) == -1) return -1;
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}
int main(void)
{
char *argv[] = { "date", NULL };
int ret = run_program("/bin/date", argv);
printf("date exited with: %d\n", ret);
char *argv2[] = { "ls", "/tmp", NULL };
ret = run_program("/bin/ls", argv2);
printf("ls exited with: %d\n", ret);
return 0;
}
fork() creates a child with its own copy-on-write virtual address space; both processes run concurrently. vfork() creates a child that shares the parent’s address space directly; the parent is suspended until the child calls exec() or _exit(). vfork() has no CoW setup overhead but is extremely dangerous if misused.
exit() runs atexit() handlers and flushes stdio buffers. Since the child shares the parent’s address space, running atexit handlers would execute the parent’s cleanup code. Flushing stdio buffers would duplicate output. _exit() skips all cleanup and terminates immediately, leaving the parent’s state intact.
vfork() was invented before copy-on-write, when fork() copied all memory — making fork+exec expensive. Today, with CoW fork(), vfork() offers minimal advantage on modern Linux. It remains useful mainly in embedded systems without MMU (where fork() is impossible) and in performance-critical code measured to benefit from it. POSIX marks it obsolescent.
