Processes Explained
September 11, 2026 • 6 min read

Table of Contents
Part of the series:Operating Systems
The OS overview promised an illusion: dozens of programs sharing one machine, each believing it owns the hardware. The mechanism behind that illusion is the process1. This post opens it up. We will look at what separates a program from a process, the record the kernel keeps for each one, the states a process moves through, how processes are born and reaped, and the isolation that keeps one crash from taking down the machine.
Programs vs processes: recipe vs cooking
A program is inert: bytes on disk, a recipe sitting in a drawer. A process is that program in motion: code loaded into memory, with an address space, open files, and a position in its execution. The same program can run as many processes at once (open two terminals, run the same shell twice); each gets its own memory and its own state, and neither knows about the other.
What turns the recipe into cooking is state. A process is its code plus everything needed to pause it and resume it later: register values, the program counter, the stack pointer, the page table describing its memory, the list of open files, and accounting (who owns it, how much CPU it has used). Stop saving that state exactly and the illusion of many machines collapses into one.
The PCB: the kernel’s index card
For every process the kernel keeps a process control block2 (PCB, often called the process descriptor or task struct): one record holding everything the OS needs to manage that process. The exact fields vary by system, but the shape is universal:
PCB for pid 4217 ("editor")
─────────────────────────────
pid, parent pid, owner (uid)
state: ready / running / blocked
CPU context: rip, rsp, general registers
memory: pointer to page table
files: table of open file descriptors
accounting: CPU time used, priority
signals and exit status
The process identifier (pid)3 names the process; 0, 1, and small numbers traditionally belong to the kernel and the first user process. The CPU context is the pause button: when the timer interrupt fires, the kernel copies the registers into the PCB; when the process runs again, it restores them. Switching that context is literally what a context switch is: save one PCB’s registers, load another’s, switch page tables, resume.
States: running, ready, blocked
A process is always in one state4, and the kernel moves it between states as events happen:
created
│
▼
┌───────┐ dispatch ┌─────────┐
│ ready │ ──────────▶ │ running │ ───▶ terminated
└───────┘ └─────────┘ ▲
▲ │ wait for I/O │ exit
│ I/O completes ▼ │
│ ┌─────────┐ │
└──────────────│ blocked │ ───────────┘
└─────────┘ (via zombie,
parent reaps)
Three states carry the whole story. Running means on a CPU right now (at most one process per core). Ready means runnable but waiting for a CPU. Blocked (waiting) means stuck on something else: disk read, network packet, keyboard input. A blocked process cannot use a CPU even if one is free, so the kernel parks it and runs someone ready instead. That single decision, never idle a CPU while work is ready, is why your machine stays busy during a slow download.
Two more states round out the picture. Created (new) covers the brief setup before a process is ready. Terminated covers death: on Unix a dead process lingers as a zombie until its parent collects the exit status with wait, a small bookkeeping debt the next section explains.
Born and reaped: fork, exec, exit, wait
On Unix, processes are born by cloning. The fork5 system call duplicates the calling process: parent and child emerge running the same code, differing only in the return value (0 for the child, the child’s pid for the parent). The child then typically calls exec6 to replace itself with a new program. Shells do exactly this dance for every command you type: fork, exec the command in the child, wait for it in the parent.
pid_t pid = fork();
if (pid == 0) {
// child: become the new program
execl("/bin/ls", "ls", NULL);
_exit(1); // exec only returns on failure
} else {
// parent: wait for the child to finish
int status;
waitpid(pid, &status, 0);
}
Death is symmetric. exit tears the process down (closing files, freeing user memory, keeping the exit status), and the parent’s wait collects that status and finally frees the PCB. A parent that never waits accumulates zombies; a child whose parent died first gets adopted by the first process (pid 1), which reaps dutifully. None of this is hardware. It is kernel bookkeeping, conventions enforced in C.
Isolation: why one crash kills one program
Sharing a machine is only half the job; the other half is keeping tenants apart. Isolation rests on two mechanisms from earlier posts.
First, virtual memory: each process gets its own page table, hence its own private address space. Process A cannot name process B’s memory because its addresses translate elsewhere (or to nothing, producing a segmentation fault instead of corruption). The memory post described the hardware; the process is its customer.
Second, privilege levels: user code cannot touch devices, reconfigure memory, or meddle with other PCBs. Anything dangerous requires a syscall, where the kernel validates first. A wild pointer in an editor kills the editor; the kernel, running isolated in its own mappings, survives to clean up.
process A kernel process B
private mappings → own mappings, ← private mappings
page table A full hardware page table B
access, guards
every crossing
Files and permissions complete the fence: every process runs as a user, every file carries an owner and mode bits, and the kernel checks on every open. Isolation is not one trick. It is address spaces plus privilege plus permissions, layered so that each covers the others’ gaps.
The big picture
A program is bytes; a process is a program plus the state to pause and resume it. The kernel tracks each one in a PCB, moves it between ready, running, and blocked, creates it with fork and exec, reaps it with exit and wait, and isolates it with private address spaces and privilege checks. One executor, a hundred tenants, each convinced the machine is theirs.
But a process is a heavy tenant: its own address space, its own page table, expensive to create and switch. Programs that need many parallel activities inside one tenant reach for something lighter, and that lighter thing shares memory instead of isolating it, with all the danger that implies. That is the next post’s subject.
Footnotes
Enjoyed this post? You can sponsor me and this site.
Related posts: