Subtopic 2
-1 / 0 / +PID
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.
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;
}
default: catches all positive values cleanly without needing to compare to a specific number.#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;
}
_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.#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;
}
wait() returns whichever finishes first. To wait for a specific child use waitpid(pids[i], &status, 0).#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;
}
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.
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).
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.
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.
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.
