Close-up server motherboard with branching processor pathways symbolizing fork and vfork process creation
Linux System Programming
William  

Fork vs. Vfork: How Memory Sharing Changes Process Safety

fork() creates a child with a separate logical address space, usually set up with copy-on-write pages. vfork() temporarily shares the address space and suspends the parent until the child calls an exec function or exits. That makes fork() the normal choice for process creation. Use vfork() only for a tightly controlled exec path where its contract is understood and verified against the platform and libc.

The difference between vfork() and fork() is not just speed. It changes what the child is allowed to do before it replaces itself with another program. With fork(), the child can run ordinary code. With vfork(), ordinary code can corrupt the suspended parent or trigger undefined behavior.

What are fork() and vfork() actually designed to do?

fork() is the general process-creation system call. It creates a child process that starts with a copy of the parent's process state, including memory mappings, open file descriptors, signal dispositions, and working directory.

The child and parent then run independently. The child can calculate, allocate memory, close descriptors, write files, or call exec. None of that requires the parent to wait.

vfork() is a specialized shortcut for a narrower sequence:

  1. Create a child.
  2. Let the child adjust a small amount of process state.
  3. Replace the child with another program using exec.
  4. Or terminate with _exit().

The parent is suspended during this interval. The child temporarily runs in the parent's address-space environment, so it must not behave like an ordinary independent process.

That distinction matters more than the name. vfork() isn't a faster general-purpose fork(). It's an optimization for a child that won't remain a child for long.

For the broader process states, signals, and parent-child relationships, see the Linux process life cycle.

What is the difference between vfork() and fork()?

Behaviorfork()vfork()
Address spaceLogically separate from the startTemporarily shared with the parent
Parent schedulingMay run before, after, or alongside the childSuspended until the child execs or exits
Child can run normal codeYesNo, not safely
Child memory writesIsolated by copy-on-writeMay affect the parent
Typical purposeGeneral process creationImmediate exec or _exit()
Failure modeUsually an ordinary process bugCan corrupt parent state or invoke undefined behavior

The practical rule is plain: use fork() unless you have a specific reason to use vfork().

When does the parent run relative to the child?

After fork(), neither process gets a guaranteed first turn. The scheduler can run the parent first, the child first, or switch between them. Code that depends on one process printing or changing state before the other has a race unless it uses an explicit synchronization method.

For example, this is not a synchronization mechanism:

pid_t pid = fork();

if (pid == 0) {
 puts("child");
} else {
 puts("parent");
}

The output order can change between runs. A scheduler decision is not a protocol. If order matters, use a pipe, waitpid(), a signal, or another deliberate form of interprocess communication. Linux IPC mechanisms cover those choices.

With vfork(), the parent is suspended while the child runs. The child must call an exec function or _exit() before the parent can continue. The point is to avoid running both processes through the expensive setup and scheduling path when the child is about to become a different program.

There is a trap here. The parent being suspended doesn't make the child safe. It makes the child's mistakes more dangerous because the child is operating beside the parent's live state.

Does fork() copy the entire address space?

Server memory modules with separate and shared pathways representing fork isolation and vfork memory sharing

No. fork() gives the child a logically separate address space, but the kernel normally avoids copying every physical memory page immediately.

Instead, the parent and child initially map the same physical pages as read-only copy-on-write pages. Their page-table structures are prepared so each process has its own virtual address space. When either process writes to a shared page, the kernel handles a page fault, allocates a private page, copies the old contents, and resumes the write.

This means a large parent process doesn't immediately require an equally large physical memory copy. Pages that neither process changes can remain shared.

The word "copy" in fork() therefore describes the child’s memory view, not an eager byte-for-byte duplication of all RAM. The child sees the same initial values, but later writes become private.

The setup still has a cost. The kernel must create the child, duplicate relevant memory-management metadata, copy page-table information, and establish the child's process state. Copy-on-write removes most wasted page copying, not all fork() overhead.

How does vfork() share memory with the parent?

vfork() temporarily lets the child use the parent's address-space state while the parent is stopped. This avoids much of the setup needed to give the child an independent memory view.

That sharing is also the hazard. A child that changes an ordinary local variable may change the value the parent sees when it resumes. A child that changes memory through a pointer can alter shared data just as easily. The stack is part of this problem, so returning through normal function calls is unsafe.

Consider the shape of this code:

pid_t pid = vfork();

if (pid == 0) {
 child_setup();
 execl("/bin/sh", "sh", (char *)NULL);
 _exit(127);
}

The function call itself is suspicious. child_setup() might use malloc(), touch shared globals, acquire a libc lock, modify stack data, or call code that was never written for a suspended parent. If execl() fails, only _exit() is safe for termination. Do not use exit(), which runs user-space cleanup handlers and flushes standard I/O state.

The child should do as little as possible between vfork() and exec. In practice, that means carefully controlled descriptor changes and a direct exec path. Even then, check the platform rules. Compiler assumptions, libc internals, and kernel behavior all matter.

What happens if the child modifies memory after fork()?

After fork(), a child write triggers copy-on-write. The child gets a private physical page, and the parent's version remains unchanged.

A child read is different. It can safely read the inherited contents while both processes still refer to the same physical page. The kernel only needs to split the page when one process writes.

This is why ordinary code after fork() is safe from the parent’s point of view:

int count = 10;
pid_t pid = fork();

if (pid == 0) {
 count = 20;
 _exit(0);
}

waitpid(pid, NULL, 0);
printf("%d\n", count);

The parent still sees its own value. The child’s assignment does not reach back through the copy-on-write boundary.

That isolation applies to memory, not every resource. File descriptors inherited across fork() refer to open file descriptions shared by both processes. Their file offsets and status flags can interact. If descriptor behavior matters, read how Linux file descriptors work before assuming every inherited thing is private.

Why can vfork() be more efficient than fork()?

vfork() can avoid work that would be wasted when the child immediately calls exec. There is no reason to build a fully independent memory view for a child that will discard that view moments later.

The savings can include less page-table setup and less process-memory bookkeeping. The parent also remains stopped, so the kernel doesn't need to support two independently running processes during the pre-exec interval.

However, fork() already uses copy-on-write. It doesn't copy every user-space page before returning. That removes the old, obvious reason to prefer vfork() and makes the performance gap workload-dependent.

A child that performs real work after fork() can benefit from its independent address space. A child that immediately execs may make vfork() or a spawn interface attractive. The only useful measurement is the path your program actually runs, not an old claim that one call is always faster.

The cost of debugging an invalid vfork() child is also part of the trade-off. A small speed gain is a poor bargain if the parent later fails because the child changed shared state.

When does vfork() become dangerous or undefined?

Treat the child after vfork() as trapped inside a narrow corridor. It should quickly call an exec function or _exit(). Do not let it return from the function that called vfork().

Avoid these actions in the child:

  • Calling exit() instead of _exit()
  • Allocating or freeing memory
  • Calling general-purpose libc functions
  • Modifying ordinary variables or shared buffers
  • Acquiring or releasing locks
  • Returning through normal application code
  • Calling code that may run callbacks or signal handlers
  • Performing work that assumes the parent can continue
  • Jumping into another part of the application

The failure may not appear at the vfork() call. The child can change a stack variable, return through a damaged stack, or leave a libc lock held. The parent then resumes and fails somewhere unrelated. That is the kind of bug that survives testing because the scheduler happened to be kind.

POSIX places strict limits on what a vfork() child may do. Read the POSIX vfork() specification when portability matters. Linux also documents its system-call behavior in vfork(2). Those rules are not suggestions for making risky code look respectable.

Are the parent and child’s pages isolated after process creation?

With fork(), the isolation boundary appears through copy-on-write. Both processes start with matching memory contents, then each process receives private pages as it writes. A memory update in one process does not silently change the other process.

With vfork(), that boundary is temporarily absent. The child and parent share the address-space environment until the child execs or exits. The parent is stopped, but its memory is still the parent’s memory. The child is not holding a safe private copy.

This affects correctness first. It also affects security. A child with an accidental pointer write can alter credentials, configuration state, allocator metadata, or control data that the parent trusts after resuming.

fork() doesn't isolate every kernel object. File descriptors, signal state, process groups, and other resources have their own inheritance rules. Check each resource instead of treating "separate process" as "separate everything."

Which system call should you use in real programs?

System programmer beside server chassis illustrating safe exec handoff after vfork without visible screens

Use fork() for a child that will run ordinary application code. Use a suitable spawn interface when the real goal is to start another executable with controlled file actions and environment setup. Reserve vfork() for a small, reviewed exec path where the child does almost nothing else.

On Linux, glibc's Linux implementation of posix_spawn() is in sysdeps/unix/sysv/linux/spawni.c. That implementation uses the clone system call directly with CLONE_VM and CLONE_VFORK flags and an allocated stack. The library is handling a narrow process-launch design, not giving your application permission to run arbitrary code after vfork().

That distinction is easy to miss. A library can arrange its internal sequence so the shared-memory window is controlled. Your application may not have the same guarantees after adding logging, allocation, callbacks, or error handling.

When behavior looks wrong, inspect the actual path:

strace -f -e trace=process,execve,exit_group ./your-program

This shows process creation, exec attempts, and exits. If the child never reaches execve(), or an exec fails and the process exits with an unexpected code, that narrows the problem immediately.

Then read the logs and the libc implementation involved. If the question reaches the kernel boundary, inspect the kernel source and the relevant system-call documentation. Copy-pasting a vfork() example from a forum is how a controlled optimization becomes a process-state bug.

For a wider introduction to syscall boundaries, see the Linux system calls tutorial.

FAQ

Can the child call printf() after vfork()?

Don't treat printf() as safe after vfork(). It can touch stdio buffers, locks, and shared libc state. Have the child exec immediately or call _exit() on failure.

Does vfork() always make program startup faster?

No. Its benefit depends on the process-launch path, kernel, libc, and workload. Benchmark the complete launch operation, including setup and exec, instead of timing the system call in isolation.

Is posix_spawn() safer than calling vfork() directly?

Usually, yes, for programs that need to launch another executable without running a general child workload. posix_spawn() puts the launch sequence behind a defined interface and lets libc handle platform-specific details.

Can a vfork() child call execve() directly?

Yes, a direct execve() call is the intended shape of the operation. The child must still handle failure with _exit(), and any setup before the call must obey the platform's restrictions.

Should I replace every fork() with vfork() in a server?

No. A server child often needs independent memory, normal library code, and predictable cleanup. Replacing fork() without proving that the child immediately execs creates risk without a reliable gain.