Creating Processes: fork, exec, and Process Trees
What This Concept Is
Unix intentionally splits "make a new process" from "replace what a process is running". The split gives you two primitives:
fork()-- duplicate the calling process. Both the parent and the freshly cloned child continue executing from the same point.forkreturns the child's PID in the parent, and0in the child.execve(path, argv, envp)-- replace the caller's current program image. The PID stays the same; the code, data, heap, and stack are torn down and rebuilt from the new binary.
wait / waitpid lets a parent block until a child exits and collect its exit status; otherwise the child stays a zombie.
Because every process (except PID 1) has a parent, the set of all live processes forms the process tree.
Why It Matters Here
The fork / exec split is the reason Unix shells are tiny. Redirection, piping, setting environment variables, changing working directory -- all of those happen in the child between fork and exec, without touching the parent and without baking flags into a single "spawn" call. Once you see this, most system-programming design makes sense.
This concept is also the doorway to process tree reasoning: who reaps whom, orphan adoption by PID 1, and process groups.
Concrete Example
A minimal shell that runs ls -l:
pid_t pid = fork();
if (pid == 0) {
// child
execlp("ls", "ls", "-l", (char *)NULL);
_exit(1); // only reached if exec fails
} else {
// parent
int status;
waitpid(pid, &status, 0);
}
The process tree after fork but before exec:
shell (pid=100)
└── shell-copy (pid=101) <- will soon exec into /bin/ls
After exec in the child, the PID 101 is unchanged but the code is now ls. After ls exits and the parent's waitpid returns, the tree returns to just the shell.
Adding a pipe ls -l | wc -l:
shell (100)
├── ls (101) writes fd=1 to pipe
└── wc (102) reads fd=0 from pipe
The shell forks twice, wires up pipe file descriptors in each child before exec, then waits for both.
Common Confusion / Misconception
"fork is slow because it copies the whole address space." Modern kernels use copy-on-write: the child gets the same physical page-table mappings marked read-only; real copies happen only when a page is written. This is why fork followed immediately by exec is cheap -- nothing was actually copied before the new image replaced it all.
"exec creates a new process." No. exec replaces the running program inside the same process. Same PID, new code. This matters in tracing tools that key on PID.
How To Use It
When reading a shell or init system, ask:
- Where is the
fork? That marks the point where the tree branches. - Which side, parent or child, is the path with
pid == 0? - What happens between
forkandexecin the child? Those lines are "configure the child's environment." - Who calls
wait? An unwaited-for child becomes a zombie.
Check Yourself
- Why does
forkreturn twice? - What would happen if a shell called
execwithoutforkfirst? - Why does a pipeline of two commands require two
forks?
Mini Drill or Application
Predict the process tree after each of:
./arunsfork(); fork();with noexecorwait. How many processes exist?- A shell runs
sleep 100 &thenexits without waiting. What adopts the sleeper? strace -f -e trace=fork,execve,wait4 bash -c "ls | wc -l"-- list every expected line.