JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

A Process Is Not a Program

A program is a dead file on disk; a process is that file brought to life and running. This guide draws the line between the two, gives the running thing a name and a place in a family tree, and lays out the small set of moves — fork, exec, wait — that the rest of this rung will take apart one at a time.

The file and the thing that runs

You already know how a program is born: you write C source, the toolchain compiles and links it, and out falls an executable file sitting on disk — a few kilobytes of machine code, some initial data, and a header telling the system how to lay it all out. That file is a program. Here is the quiet, important truth this whole rung turns on: that file is not running anything. It is inert. It is a recipe written down, a score on paper, a set of blueprints in a drawer. You can copy it, email it, leave it untouched for a year, and nothing happens, because a program is just bytes at rest.

A process is what you get when the kernel takes that dead file and brings it to life. To start one, the kernel reads the program's bytes off disk, lays them out into a fresh address space — code here, the global data there, a heap to grow upward, a stack to grow downward — opens the standard streams for it, sets the program counter to the program's entry point, and lets the CPU loose on it. The bytes start moving. That live, executing instance, with its own private memory and its own current position in the code, is a process. The program is the noun; the process is the verb in motion.

Every process has a name: the PID and its parent

If processes are living things the kernel manages, it needs a way to refer to each one. So at the moment a process is born, the kernel stamps it with a unique number called the process ID, or PID — just a positive integer, like 4127. The PID is the handle the whole system uses for that one running instance: when you type `kill 4127`, when the kernel schedules it, when a debugger attaches to it, the PID is how it is named. Run the same program again tomorrow and it gets a different PID, because the PID names this particular run, not the program. The PID is to a process what an address is to a byte: the system's way of pointing at exactly one of them.

But a PID does not arrive alone. Every process also carries the PID of the process that created it — its parent process ID, or PPID. This matters because of the single most surprising fact in this whole rung, the one the next guide is entirely about: a brand-new process does not spring from nowhere. The only way to make one is for an existing process to clone itself with fork(), producing a near-identical child. So every process except the very first one has exactly one parent, and that parent's PID is recorded right there in the child. The PPID is the thread that ties the whole system together into a family, which is exactly where we are going next.

The family tree, and the ancestor at its root

Follow those parent links and a shape appears. Your shell was forked by the terminal program; the terminal was forked by your desktop session; that session traces back, parent by parent, toward a single original process. Because each process has exactly one parent but can have many children, the links never form a loop — they form a tree. This is the process tree, and at any instant it is the true map of everything running on your machine: who launched whom, all the way up. The command `pstree` will draw it for you, and `ps -ef` will list it as a flat table with a PID and PPID column you can trace by hand.

Every tree has a root, and this one is special. When the kernel finishes booting, it hand-crafts the very first process — the one process that was never forked from anything — and gives it PID 1. This is init (on most modern Linux systems, a program called systemd). Every other process on the machine is a descendant of PID 1; it is the common ancestor of the whole tree. PID 1 has a second job we will need at the end of this rung: it is the designated guardian for orphaned processes, the ones whose parent dies before they do. When a parent exits and leaves children behind, the kernel quietly re-parents those children onto PID 1, so no running process is ever left without a parent.

The three moves: fork, exec, wait

Here is the part that surprises almost everyone the first time, so meet it gently now and we will earn it properly in the guides that follow. You might expect a single call named something like "run this program" — hand it a filename, get back a new process. Unix does not work that way. Instead it splits the job in two. First, fork() duplicates the current process: one process calls it and, astonishingly, two processes return from it — a parent and a near-identical child, same code, same data, same open files, differing only in which one is told it is the child. fork() makes a copy of you; it does not, by itself, run any new program.

The second move is exec(). Where fork() makes a copy, exec() performs a transformation: it takes the process that calls it and replaces its entire contents — code, data, heap, stack, everything — with a different program loaded fresh from disk. The PID does not change; the same living process keeps its identity and its open files, but the body inside is now a completely different program, running from that program's first instruction. exec() does not create a process; it overwrites the one already there. Put the two together and you get the fork-then-exec pattern, the single move at the heart of how every command you have ever typed gets run:

pid_t pid = fork();              /* one process becomes two */
if (pid == -1) {                 /* fork failed: no child was made */
    perror("fork");
    return 1;
}
if (pid == 0) {                  /* this branch runs in the CHILD */
    execlp("ls", "ls", "-l", (char *)0);  /* become a different program */
    perror("execlp");            /* only reached if exec FAILED */
    _exit(127);
}
/* this branch runs in the PARENT; pid holds the child's PID */
int status;
waitpid(pid, &status, 0);        /* parent waits for the child to finish */
fork-then-exec: the shell forks a copy of itself, the child turns into ls, and the parent waits. Note fork() returns 0 in the child and the child's PID in the parent — that single difference is how each copy knows which one it is. Every call is checked.

The third move closes the loop. When the child finishes its work and calls exit(), it does not simply vanish — it leaves behind a small slip of paper, an exit status, a single number saying how it went (0 for success, non-zero for a problem). The parent collects that slip by calling wait(), which both hands back the child's status and tells the kernel it is finally safe to throw away the last traces of that child. This collecting is called reaping, and skipping it has consequences: a finished-but-unreaped child becomes a zombie, a hollow entry lingering in the process table. fork to split, exec to transform, wait to reap — that is the whole life cycle, and each move gets its own guide ahead.

What the child inherits, and what it carries away

A new process is not a blank slate. When fork() makes the child, the child inherits a copy of nearly everything the parent had: the same memory contents, the same open file descriptors (so child and parent share the same standard output, which is why a forked child can print to your terminal without doing anything special), the same current working directory, and one more thing worth naming on its own — the environment. The environment is a list of NAME=VALUE strings, like PATH=/usr/bin:/bin or HOME=/home/you, that travels alongside the process and is handed down to its children. It is the quiet side channel a parent uses to configure a child without passing arguments.

This inheritance is also exactly why the fork-then-exec split is useful rather than clumsy. In the gap between forking and exec-ing — those few lines that run only in the child — the child can adjust what it inherited before becoming the new program: close a file descriptor the new program should not see, redirect its standard output into a file, change its working directory, tweak an environment variable. The shell does precisely this every time you type a command with a redirection like `ls > out.txt`: fork, then in the child re-point descriptor 1 at out.txt, then exec ls. One call that did fork and exec together would leave no room for that surgery. The seam between the two moves is the whole point.

Holding the whole shape before we zoom in

Step back and the rung is one clean picture. A program is a file at rest; a process is that file running, with its own private memory and its own PID. Processes never appear from nowhere — each is forked from a parent, so they all hang together in one tree rooted at PID 1. To actually run a different program, a process forks a child and the child exec()s into the new program; the parent later wait()s to collect the child's exit status and reap it. Everything inherits the parent's memory, files, and environment at the moment of the fork, copied, never shared backward. That is the entire model.

From here the rung simply zooms into each move in turn. "fork(): One Process Becomes Two" sits inside that impossible-looking moment where one call returns twice. "exec(): Becoming a Different Program" follows the transformation that keeps the PID but swaps the body. "wait(), Exit Status, and Reaping Children" is about collecting that slip of paper and why you must. And "Zombies, Orphans, and the Process Tree" faces what goes wrong when the family bookkeeping is neglected — children left unreaped, or parents that die first. You now hold the map; the next four guides walk its roads.