What a process is
A program is a file on disk. A process is that program loaded into memory and actively executing. The OS tracks each process with a Process Control Block (PCB): the current register values, the page table, the open fd table, signal masks, the working directory, the user and group IDs, and more. Each process gets a unique Process ID (PID).
On Linux, process 1 (init or systemd) is the ancestor of every other process.
Every process except process 1 has a parent. This parent-child relationship forms
a tree — and it has practical consequences you'll feel immediately when working
with fork.
fork: one call, two returns
fork() is the Unix mechanism for creating a new process.
It clones the current process — copying its address space, fd table, signal state —
and returns twice: once in the parent (returning the child's PID), and once in the
child (returning 0). The two processes then execute independently from the point
of the fork call.
fork cheap even for large processes —
you only pay for the pages you actually modify after the fork.
exec: replacing the process image
fork creates a child that runs the same program. Usually you want
the child to run a different program. That's what exec does:
it replaces the current process's address space with a new program loaded from
a file. The PID stays the same; everything else (text, data, stack, heap)
is replaced.
exec does not return on success — there's nothing to return
to, since the calling program's code no longer exists. It only returns (with -1)
if the exec fails.
This fork-exec pattern is how every shell runs a command. The shell forks itself,
the child sets up any redirections (using dup2), then calls exec.
The parent waits. The child's program runs as a fresh process image but inherits
the carefully prepared fd table.
wait: reaping children
When a child process exits, it doesn't fully disappear. Its PID and exit status
remain in the kernel process table — a zombie — until the parent collects
them with wait or waitpid. This lets the parent check
whether the child succeeded.
wait, its children stay in zombie state
indefinitely, consuming a PID slot. On a server that forks thousands of workers,
failing to reap them eventually exhausts the PID namespace.
Servers handle this by waiting in a SIGCHLD signal handler
or using waitpid(-1, ..., WNOHANG) to reap non-blockingly.
_exit vs exit
In the child process after fork, use _exit() instead of exit().
exit() flushes stdio buffers and runs atexit handlers.
Since the child shares the same buffers as the parent at the time of fork,
calling exit() in the child can flush and duplicate output that
the parent will also write. _exit() terminates immediately without
touching stdio.
fork() clones the process, exec() replaces it with a new program, and wait() reaps the result — these three syscalls are the entire mechanism behind how a shell runs commands.