fork() Return Values & the Switch Idiom

fork() Return Values & the Switch Idiom
Topic 2 → Subtopic 2  |  Understanding what fork() returns and the classic coding pattern
Topic 2
Subtopic 2
3 Cases
-1 / 0 / +PID
3
Code Examples

The Three Return Cases of fork()

The most confusing thing about fork() for beginners is that it returns different values in different processes. The same function call produces two return values at the same time — one for the parent and one for the child. Mastering this is key to writing correct process code.

Keywords:

pid_t return value -1 return value 0 child PID switch idiom error handling EAGAIN ENOMEM

🔎 The Three Return Values of fork()
Returns -1
fork() FAILED
No child was created.
Only the parent sees this.

Reasons:
• User process limit reached (RLIMIT_NPROC)
• System-wide process limit hit
• Out of kernel memory

errno set to EAGAIN or ENOMEM

Returns 0
You are the CHILD
fork() succeeded.
This code runs in the child.

What to do:
• Do child-specific work
• Call exec() if needed
• Call _exit() when done

Use getppid() for parent’s PID

Returns > 0
You are the PARENT
fork() succeeded.
Return value = child’s PID.

What to do:
• Continue parent work
• Call wait() to reap child
• Track multiple children

Store return for later wait()

🔨 The Classic switch/case Fork Idiom

Professional Linux code uses a switch statement on fork()’s return value. This maps naturally to the three cases and is cleaner than nested if-else:

pid_t child_pid;

switch (child_pid = fork()) {

case -1:
    /* fork() FAILED - handle error and exit */
    perror("fork");
    exit(EXIT_FAILURE);

case 0:
    /* CHILD process runs here */
    /* Do child work... */
    _exit(EXIT_SUCCESS);   /* use _exit(), not exit() */

default:
    /* PARENT process runs here */
    /* child_pid holds the child's PID */
    /* Do parent work, then wait... */
    wait(NULL);
    break;
}
Why default: for parent? Because the parent’s return value is the child’s PID — any positive number. Using default: catches all positive values cleanly without needing to compare to a specific number.

💻 Code Example 1: The switch Idiom in Full
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>

int main(void)
{
    pid_t child_pid;
    int status;

    switch (child_pid = fork()) {

    case -1:
        /* Error: check errno for reason */
        if (errno == EAGAIN)
            fprintf(stderr, "Fork failed: too many processes\n");
        else
            fprintf(stderr, "Fork failed: out of memory\n");
        exit(EXIT_FAILURE);

    case 0:
        /* CHILD */
        printf("[CHILD ] My PID    = %d\n", (int)getpid());
        printf("[CHILD ] Parent PID= %d\n", (int)getppid());
        printf("[CHILD ] Doing work...\n");
        sleep(1);
        printf("[CHILD ] Exiting with code 7\n");
        _exit(7);    /* Use _exit in child! */

    default:
        /* PARENT */
        printf("[PARENT] My PID   = %d\n", (int)getpid());
        printf("[PARENT] Child PID= %d\n", (int)child_pid);
        printf("[PARENT] Waiting for child...\n");

        if (waitpid(child_pid, &status, 0) == -1) {
            perror("waitpid");
            exit(EXIT_FAILURE);
        }

        if (WIFEXITED(status))
            printf("[PARENT] Child exited: code = %d\n",
                   WEXITSTATUS(status));
        break;
    }

    printf("[PID=%d] Continuing after switch...\n",
           (int)getpid());
    return 0;
}
Notice: The line after the switch prints for both parent and child (child after _exit won’t reach it, but parent will). Using waitpid(child_pid,...) is better than wait() when you want to wait for a specific child.

💻 Code Example 2: Tracking Multiple Children by PID
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX_CHILDREN 4

int main(void)
{
    pid_t pids[MAX_CHILDREN];  /* store each child's PID */
    int i, status;
    pid_t finished;

    /* Create MAX_CHILDREN children */
    for (i = 0; i < MAX_CHILDREN; i++) {

        switch (pids[i] = fork()) {

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

        case 0:
            /* CHILD i */
            printf("[Child %d PID=%d] Starting, sleep=%ds\n",
                   i, getpid(), (MAX_CHILDREN - i));
            sleep(MAX_CHILDREN - i);  /* different durations */
            printf("[Child %d PID=%d] Done.\n", i, getpid());
            _exit(i * 10);  /* exit code = i * 10 */

        default:
            /* PARENT: record child PID and loop */
            printf("[Parent] Created child %d with PID=%d\n",
                   i, pids[i]);
            break;
        }
    }

    /* Wait for all children (they finish in different order) */
    for (i = 0; i < MAX_CHILDREN; i++) {
        finished = wait(&status);
        printf("[Parent] Child PID=%d finished, exit code=%d\n",
               finished, WEXITSTATUS(status));
    }

    printf("[Parent] All %d children finished.\n", MAX_CHILDREN);
    return 0;
}
Observation: Children finish in reverse creation order (child 3 sleeps 1s, child 0 sleeps 4s). wait() returns whichever finishes first. To wait for a specific child use waitpid(pids[i], &status, 0).

💻 Code Example 3: Robust fork() with Error Handling
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

/* Safe fork wrapper: retries on EAGAIN */
pid_t safe_fork(int max_retries)
{
    pid_t pid;
    int attempts = 0;

    while (attempts < max_retries) {
        pid = fork();

        if (pid != -1)
            return pid;   /* success */

        if (errno == EAGAIN) {
            /* Temporary: too many processes, retry */
            fprintf(stderr,
                "[Attempt %d] fork() EAGAIN, retrying...\n",
                attempts + 1);
            sleep(1);
            attempts++;
        } else {
            /* ENOMEM or other fatal error, don't retry */
            fprintf(stderr, "fork() fatal error: %s\n",
                    strerror(errno));
            return -1;
        }
    }

    fprintf(stderr, "fork() failed after %d retries\n",
            max_retries);
    return -1;
}

int main(void)
{
    pid_t pid = safe_fork(3);  /* up to 3 retries */

    if (pid == -1) {
        fprintf(stderr, "Could not create child process\n");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        printf("[Child PID=%d] Running.\n", getpid());
        _exit(0);
    } else {
        wait(NULL);
        printf("[Parent] Child done.\n");
    }

    return 0;
}

🅾 Interview Questions
Q1: What does fork() return and in which process?

fork() returns three possible values: -1 in the parent when creation fails; 0 in the newly created child process; and a positive PID (child’s PID) in the parent when creation succeeds.

Q2: Why does the switch idiom use “default:” for the parent case?

Because in the parent, fork() returns the child’s PID — which is a positive integer of unknown value. You can’t use a specific number as the case. default: naturally handles all values not explicitly listed (i.e., all positive PIDs).

Q3: What is the difference between wait() and waitpid()?

wait() blocks until any child terminates and returns that child’s PID. waitpid(pid, &status, options) waits for a specific child (by PID) and supports non-blocking mode (WNOHANG) via the options argument. waitpid is preferred in programs with multiple children.

Q4: What errno values can fork() set on failure?

EAGAIN: The caller has reached its per-user process limit (RLIMIT_NPROC) or the system-wide limit was reached. This may be temporary. ENOMEM: Insufficient kernel memory to create the new process structure. This is usually a more serious/permanent failure.

Q5: Can a child find out its own PID without the parent telling it?

Yes. The child can always call getpid() to get its own PID at any time. It does not need the parent to communicate this. Similarly, getppid() gives the parent’s PID. fork()’s return value of 0 is just a signal to the child that it is the child — not the child’s actual PID.

Series Navigation
Topic 2 → Subtopic 2 of 4

← Previous Next: Memory Segments → Index

Leave a Reply

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