Processes Explained

September 11, 2026 • 9 min read

Processes Explained
Table of contents

Part of the series:Operating Systems Explained

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, the isolation that keeps one crash from taking down the machine, and how processes walled off from each other still find a way to talk.

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.

Talking across the fence: inter-process communication

But a fence keeps things out in both directions. A process cannot read another’s memory, cannot poke its variables, cannot even learn where that memory lives. The same wall that keeps one crash from taking down the machine keeps two cooperating programs from touching. Something has to carry data between them, and the only party that can see both sides is the kernel. That broker is inter-process communication7 (IPC), and the wall is what turns it into a category of its own.

The cheapest channel is the pipe8: a kernel buffer with two ends, one file descriptor to write into, another to read from. Fork hands both ends to parent and child, which is why a pipeline is three lines of plumbing:

int fd[2];
pipe(fd);                  // fd[0] = read end, fd[1] = write end
if (fork() == 0) {
    close(fd[1]);         // child keeps the read end
    execl("/usr/bin/wc", "wc", "-l", NULL);
} else {
    close(fd[0]);         // parent keeps the write end
    execl("/usr/bin/ls", "ls", NULL);
}

Neither program knows the other exists, and the two halves need not even run at the same time: the buffer lives in kernel memory and absorbs the speed mismatch. The waiting is the blocked state from earlier. Writing to a full pipe parks the writer, reading an empty one parks the reader, and whichever side still has work is the side the scheduler picks. What moves is a byte stream with no message boundaries and only one direction; want an answer back, build a second pipe pointing the other way. Two pipes are how a client talks to a server without either one holding an object the other can reach.

When two processes share no parent and must find each other by name, the same idea changes shape. A named pipe (FIFO)9 is a pipe with an entry in the filesystem, so unrelated programs agree on a path and open it. A message queue10 carries records instead of bytes: each write is a discrete message with its own length, delivered in order, so a receiver can take one message without parsing the stream around it. A socket goes furthest, becoming a bidirectional channel that works between two processes on the machine and, with a network address instead of a name, between two machines. A Unix domain socket11 is a TCP conversation with the network removed, and the same code serves both.

Shared memory12 is the exception that proves the rule. Rather than moving bytes through a kernel buffer, two processes map the same physical pages into their separate address spaces: the virtual memory post’s machinery, aimed at a shared target. No copies, no syscall per byte, and for bulk payloads dramatically faster than a pipe. But the kernel steps out of the loop entirely: it never sees the individual loads and stores, so it cannot stop one process from scribbling over the other’s data. Ordering has to come back explicitly, through semaphores, mutexes, or atomic instructions: the same bill the threads post charges for sharing memory inside a single process. Shared memory buys its speed with precisely the safety isolation was selling, which is the trade every IPC mechanism makes.

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. Cut off from each other’s memory, those processes still trade data through the kernel: pipes, message queues, sockets, or pages mapped in common, each channel paying in copies, in ordering, or in safety. 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

  1. Process (computing) - Wikipedia

  2. Process control block - Wikipedia

  3. Process identifier - Wikipedia

  4. Process state - Wikipedia

  5. Fork (system call) - Wikipedia

  6. Exec (system call) - Wikipedia

  7. Inter-process communication - Wikipedia

  8. Pipe (Unix) - Wikipedia

  9. Named pipe - Wikipedia

  10. Message queue - Wikipedia

  11. Unix domain socket - Wikipedia

  12. Shared memory - Wikipedia