vfork() vs fork() — Side by Side

vfork() vs fork() — Side by Side
Topic 5 → Subtopic 2  |  Detailed differences with t_vfork.c from TLPI
Topic 5
Subtopic 2
2
Syscalls Compared
3
Code Examples

Key Differences at a Glance

While fork() and vfork() have identical signatures and return values, their internal behavior is completely different. Knowing these differences is important for understanding legacy code, embedded systems, and POSIX compliance discussions.

Keywords:

vfork semantics parent suspended stack sharing t_vfork.c no page table copy sequential execution deterministic order

⚖ fork() vs vfork() — Complete Comparison
Property fork() vfork()
Address space Child gets own CoW copy Child shares parent’s space
Page table setup New page tables created No page tables created
Parent execution Runs concurrently with child Suspended until child exec/_exit
Child modifies vars Only affects child’s copy Modifies parent’s variables!
Child can call exit() Yes (but _exit() preferred) NO — must use _exit()
Child can return from main Yes NO — undefined behavior
Execution order Indeterminate Child always runs first
Speed (fork+exec) Fast with CoW Slightly faster (no PT setup)
Safety Safe for general use Dangerous, narrow use only
POSIX status Standard, required Obsolescent since SUSv4

💻 Code Example 1: t_vfork.c (from TLPI Chapter 24)

The textbook’s own vfork demo — shows parent suspended and stack sharing:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int istack = 222;  /* a local (stack) variable */

    switch (vfork()) {

    case -1:
        perror("vfork"); exit(EXIT_FAILURE);

    case 0:
        /* CHILD: running in parent's address space.
           Parent is suspended until we call exec or _exit. */

        sleep(3);   /* to demonstrate parent is suspended */

        /* DANGEROUS: modifies parent's stack variable */
        istack *= 2;   /* istack is now 444 in PARENT's stack */

        printf("[Child ] istack=%d\n", istack);

        /* Must use _exit(), never exit() */
        _exit(EXIT_SUCCESS);

    default:
        /* PARENT resumes here after child calls _exit */
        printf("[Parent] istack=%d\n", istack);
        /* istack is 444 here — child modified parent's var! */
        exit(EXIT_SUCCESS);
    }
}
Expected output:
[Child ] istack=444 (child modified it)
[Parent] istack=444 (parent sees the change!)

With fork(), parent would see istack=222 (independent copy).

💻 Code Example 2: Proving the Parent is Suspended During vfork()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>

int main(void)
{
    time_t t;

    printf("[%ld] Parent: about to vfork\n", (long)time(NULL));

    pid_t pid = vfork();
    if (pid == -1) { perror("vfork"); exit(1); }

    if (pid == 0) {
        printf("[%ld] Child: running (parent suspended)\n",
               (long)time(NULL));
        sleep(3);   /* parent is blocked during this sleep */
        printf("[%ld] Child: about to _exit\n",
               (long)time(NULL));
        _exit(0);
    }

    /* Parent only reaches here AFTER child's _exit */
    printf("[%ld] Parent: resumed after child's _exit\n",
           (long)time(NULL));
    wait(NULL);
    return 0;
}
Observation: The parent’s “resumed” message appears exactly 3 seconds after the child started. Parent was completely blocked during the child’s sleep. With fork() instead, parent and child would print concurrently.

💻 Code Example 3: fork() vs vfork() Timing Benchmark
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>

#define ITER 1000

double bench_fork(size_t mem_mb)
{
    char *buf = malloc(mem_mb * 1024 * 1024);
    memset(buf, 'A', mem_mb * 1024 * 1024);

    struct timespec s, e;
    clock_gettime(CLOCK_MONOTONIC, &s);

    for (int i = 0; i < ITER; i++) {
        pid_t pid = fork();
        if (pid == 0) { _exit(0); }
        waitpid(pid, NULL, 0);
    }

    clock_gettime(CLOCK_MONOTONIC, &e);
    free(buf);
    return ((e.tv_sec - s.tv_sec)*1e6 +
            (e.tv_nsec - s.tv_nsec)/1e3) / ITER;
}

double bench_vfork(size_t mem_mb)
{
    char *buf = malloc(mem_mb * 1024 * 1024);
    memset(buf, 'A', mem_mb * 1024 * 1024);

    struct timespec s, e;
    clock_gettime(CLOCK_MONOTONIC, &s);

    for (int i = 0; i < ITER; i++) {
        pid_t pid = vfork();
        if (pid == 0) { _exit(0); }
        waitpid(pid, NULL, 0);
    }

    clock_gettime(CLOCK_MONOTONIC, &e);
    free(buf);
    return ((e.tv_sec - s.tv_sec)*1e6 +
            (e.tv_nsec - s.tv_nsec)/1e3) / ITER;
}

int main(void)
{
    size_t sizes[] = { 1, 10, 50 };
    printf("%-8s  %-18s  %-18s\n",
           "Mem(MB)", "fork() us/call", "vfork() us/call");
    printf("%-8s  %-18s  %-18s\n", "------", "--------------", "---------------");

    for (int i = 0; i < 3; i++) {
        double tf = bench_fork(sizes[i]);
        double tv = bench_vfork(sizes[i]);
        printf("%-8zu  %-18.2f  %-18.2f\n", sizes[i], tf, tv);
    }

    printf("\nNote: With CoW, fork() and vfork() speeds are similar.\n");
    printf("vfork() advantage is marginal on modern Linux.\n");
    return 0;
}

🅾 Interview Questions
Q1: In vfork(), does the parent run concurrently with the child?

No. The parent is completely suspended after vfork() until the child calls exec() or _exit(). This is fundamentally different from fork(), where both parent and child run concurrently. vfork() execution is sequential: child runs first, parent resumes after.

Q2: Can a vfork() child modify local variables freely?

No! The child shares the parent’s address space, so modifying a local variable in the child modifies the parent’s stack frame. This is one of the major dangers of vfork(). The t_vfork.c example shows this explicitly. The child should do nothing except call exec() or _exit().

Q3: What happens if a vfork() child returns from main() instead of calling _exit()?

Undefined behavior. Returning from main() in the child would unwind the parent’s stack frame, corrupt the parent’s call stack, and likely crash both parent and child. Always use _exit() in a vfork() child. This is one of the most dangerous mistakes with vfork().

Series Navigation
Topic 5 → Subtopic 2 of 3

← Previous Next: vfork() Dangers → Index

Leave a Reply

Your email address will not be published. Required fields are marked *