A strange way to make a process
You already know what a process is, where it lives, and how the kernel shuffles it between Ready, Running, and Waiting. One question we have only hinted at remains: where does a brand-new process actually come from? You might guess the OS reads a program off disk and conjures a fresh process around it. On Unix-like systems the real answer is stranger and far more elegant: a process is born by cloning an existing one. There is no spell that makes a process from nothing — every process is descended from another process that asked to be copied.
The cloning call is named fork. When a running process calls fork(), the kernel makes a near-perfect duplicate of it: the same code, the same heap and stack contents, the same open files, even the same place in the program — the duplicate resumes at the exact line after the fork. The original is the parent and the copy is the child, and this parent-child relationship is the thread that ties the whole system together. Picture a cook who, mid-recipe, suddenly splits into two identical cooks standing in two identical kitchens, each holding a finger on the very same step.
One call, two returns
Here is the part that bends the mind on first contact: fork() is called once but returns twice — once in the parent and once in the child. They are running identical code, so how does either one know which it is? The kernel answers by handing back a different return value to each. In the child, fork returns 0. In the parent, fork returns the child's process ID (its PID, a positive number). And if the kernel could not create the child at all, fork returns -1 in the parent only. So a single if-statement on the return value cleanly splits the two paths.
pid = fork(); // ONE call, TWO returns
if (pid < 0) {
// fork failed: no child was created
} else if (pid == 0) {
// we are the CHILD (fork returned 0)
} else {
// we are the PARENT (fork returned the child's PID = pid)
}One more honest subtlety: after fork there are now two Ready processes, and which one the CPU runs first is up to the scheduler — it is not guaranteed. A beginner who assumes the parent always runs before the child (or vice versa) will eventually be bitten by a program that works ninety-nine times and fails the hundredth. If the order actually matters, you must make it explicit, which is exactly what the next pieces — exec and wait — let you do.
exec: handing the clone a new recipe
Cloning is only half the story. Cloning alone would give you endless copies of the same program — useful sometimes, but when you type a command in a shell, you want it to run a different program, not another shell. That is the job of exec. A call like exec("/bin/ls") tells the kernel: keep this process — same PID, same parent, same place in the process tree — but throw away its current program image and load a brand-new one in its place. The text, data, heap and stack of the old program are wiped and replaced, and execution restarts at the new program's entry point.
The standard recipe combines the two: fork then exec. The parent forks a child; the child immediately calls exec to become the program you actually wanted to run; the parent carries on as itself. This is precisely how your shell launches a command. It clones itself, and the clone — instead of staying a copy of the shell — transforms into ls, or your editor, or a browser. Splitting creation (fork) from program-loading (exec) is a deliberate and powerful design: in the tiny window between the two, the child is still itself, so it can quietly rearrange its own open files and settings before becoming the new program.
Copying without copying: copy-on-write
There is an obvious worry about all this cloning. A process might be using gigabytes of memory. If fork truly duplicated every byte, forking a big process would be agonisingly slow — and worse, in the common fork-then-exec pattern, that whole expensive copy is thrown away microseconds later when exec wipes the program image. Copying it all just to discard it would be absurd. Real kernels dodge this with a beautiful trick called copy-on-write.
- At the moment of fork, the kernel does NOT copy the memory. Instead it lets parent and child SHARE the very same physical pages, and marks every shared page read-only in both processes.
- As long as both processes only READ those pages, nothing is duplicated — they happily share, and fork is essentially free no matter how big the process is.
- The first time either process tries to WRITE to a shared page, the hardware traps to the kernel (a write to a read-only page). Only THEN does the kernel make a private copy of just that one page for the writer, and let the write proceed.
- Pages that are never written are never copied. In the fork-then-exec case, exec replaces the image almost immediately, so in practice scarcely any pages get copied at all — exactly the win we wanted.
The everyday picture: instead of photocopying a thick shared manual the instant a second cook arrives, you let both cooks read the one manual together, and you only run to the copier for a page if and when someone actually needs to scribble a change on it. Copy-on-write turns fork from a heavy, dread-it operation into a cheap one, and it is the quiet reason the clone-then-replace design is practical at all. It is a lovely example of a recurring OS idea: be lazy, and do expensive work only at the last possible moment, only if it is truly needed.
Everyone has a parent: the process tree
Because every process is forked by some other process, and that one was forked by yet another, all the processes on a machine link up into a single family tree — the process tree. Each process has exactly one parent (the one that forked it) and may have many children. Follow any process up through its parent, and its grandparent, and so on; the chain never branches upward, and it always converges on one root. That root is a special process with PID 1: the init process (on modern Linux, often systemd). The kernel starts it by hand as the very first user process at boot, and every other process on the system descends from it.
Think of init as the building manager who was there before any tenant moved in, and from whom every later resident can trace their lease. It never exits while the system is running — if it died, the whole tree would have no root. And init quietly does one more crucial job: when a parent dies while its children are still running, those children would be left rootless, so the kernel re-parents them onto init, which adopts them and tidies up after them when they finish.
Born together, parted at the end
Creation has a mirror image: a parent often wants to wait for the child it created. After forking, a parent can call wait, which blocks the parent until that child finishes, then hands back the child's exit status — a small number reporting whether it succeeded. This is how a shell knows your command is done before printing the next prompt: it forks, the child execs your command, and the shell sits in wait until the child exits. Here, at last, is the explicit ordering that fork alone could not promise.
But here is an honest caveat about the pairing. If a child finishes and the parent never calls wait, the child cannot fully vanish: the kernel must keep a scrap of its record around so the exit status is still collectable — a finished-but-uncollected child, still listed on the books even though it is done cooking. That, plus what happens to a parentless child and the full menu of ways two separate processes can talk to each other, is exactly where the next and final guide of this rung picks up.