---
title: Exceptional Control Flow
module: Exceptions & I/O
moduleNumber: 8
lessonNumber: 1
order: 801
summary: >
  Beyond the sequential, branch, and call flow a program controls itself, the
  hardware can divert the processor in response to events. We sort these into four
  classes — interrupts (asynchronous, from devices), traps (intentional syscalls),
  faults (recoverable, like a page fault), and aborts (unrecoverable) — then take
  the mechanism apart: exception numbers and the table dispatch, what the hardware
  pushes and why it differs from a procedure call, the divide-error / page-fault /
  general-protection trio on x86-64, the full syscall round trip with a worked
  write in assembly, and processes and signals as the abstractions ECF makes
  possible.
topics: [Exceptions & I/O]
sources:
  - book: Bryant & O'Hallaron
    ref: "CS:APP — §8 Exceptional Control Flow"
  - book: Bistriceanu
    ref: "Computer Architecture Notes — §6 Interrupts"
---

A program's control flow, as built in the [processor-design](/computer-architecture/processor-design/the-fetch-decode-execute-cycle)
module, is a smooth sequence: fetch the instruction at `%rip`, run it, advance —
with branches, calls, and returns letting the _program_ redirect itself. But the
system must also react to
things the running program did not ask for and cannot see coming: a packet
arrives, a timer expires, a divide-by-zero happens, a memory reference touches a
page that is on disk. These abrupt, event-driven changes of control are
**exceptional control flow (ECF)**, and they are the mechanism behind system
calls, page faults, the I/O system, and process switching. This lesson sorts ECF
into its four classes, then opens up the machinery: how the hardware finds the
handler, what state it saves and where, what the important x86-64 exceptions
actually do, and how the same mechanism carries every system call your programs
make.

## What an exception is

> **Definition (Exception).** An **exception** is an abrupt change in control
> flow in response to a change in the processor's state, where the processor
> transfers control from the running program to an OS routine — the **handler** —
> through a hardware-defined mechanism, rather than through a normal branch or
> call.

The defining contrast is with ordinary flow. A branch or `call` is _visible in the
program text_: the instruction itself names where control goes. An exception is
not: it is triggered by an **event** (a device signal, or a side effect of
executing an instruction), and control vectors to a handler the program never
named. The processor enters kernel mode, runs the handler, and, for most
exceptions, returns to the interrupted program as if nothing happened.

## The four classes

Exceptions divide along two axes: whether the event is **synchronous** (a direct
result of executing an instruction) or **asynchronous** (from outside the
processor core), and whether control **returns** afterward. The combination gives
four classes.

$$
% caption: The four classes of exceptions, by cause (asynchronous vs synchronous)
% caption: and outcome. Interrupts are asynchronous, from I/O devices. Traps are
% caption: intentional syscalls. Faults are recoverable. Aborts are unrecoverable.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  hd/.style={draw, fill=acc!8, minimum width=22mm, minimum height=8mm, inner sep=2pt, align=center},
  cell/.style={draw, minimum width=22mm, minimum height=13mm, inner sep=2pt, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  % column headers
  \node[hd] (h1) at (0,0) {class};
  \node[hd, anchor=west] (h2) at (h1.east) {cause};
  \node[hd, anchor=west] (h3) at (h2.east) {sync?};
  \node[hd, anchor=west] (h4) at (h3.east) {returns?};
  % rows
  \foreach \r/\name/\cause/\sy/\ret in {
    1/interrupt/{signal from\\I/O device}/async/yes (next),
    2/trap/{intentional\\(syscall)}/sync/yes (next),
    3/fault/{recoverable\\error}/sync/may retry,
    4/abort/{unrecoverable\\error}/sync/no} {
    \node[cell, anchor=north] (a\r) at ($(h1.south)+(0,-13*\r mm+13mm)$) {\name};
    \node[cell, anchor=west] (b\r) at (a\r.east) {\cause};
    \node[cell, anchor=west] (c\r) at (b\r.east) {\sy};
    \node[cell, anchor=west] (d\r) at (c\r.east) {\ret};
  }
\end{tikzpicture}
$$

Each class has a sharp definition worth stating in prose.

- **Interrupts** are **asynchronous**: they are raised by I/O devices outside the
  processor — a network card, a disk, the interval timer — by asserting an
  interrupt request line. They are not caused by any particular instruction, so
  the handler returns control to the _next_ instruction, the one that would have
  run anyway. Interrupts are how the machine notices the outside world; the
  [next lesson](/computer-architecture/exceptions-and-io/interrupts-and-the-kernel)
  is devoted to them.
- **Traps** are **intentional**, synchronous exceptions: the running program
  _deliberately_ triggers one with an instruction (`int` on x86, `syscall` on
  x86-64) to request a service from the kernel: open a file, allocate memory,
  write to a socket. A trap always returns, to the instruction after the
  trapping one, carrying the service's result.
- **Faults** are synchronous errors that _may be recoverable_. The handler either
  fixes the condition and **re-runs the very instruction that faulted**, or, if it
  cannot, escalates to an abort. The archetype is the [page fault](/computer-architecture/virtual-memory/page-tables-and-page-faults):
  the page was on disk, the handler loads it, and the instruction retries and
  succeeds.
- **Aborts** are synchronous and **unrecoverable**: a hardware error such as a
  parity failure in memory. There is nothing to retry; the handler does not
  return to the program but terminates it (or halts the system).

> **Definition (Trap / fault / abort / interrupt).** A **trap** is an intentional
> synchronous exception (a syscall) that returns to the next instruction. A
> **fault** is a synchronous error that, if handled, retries the faulting
> instruction. An **abort** is an unrecoverable synchronous error that does not
> return. An **interrupt** is an asynchronous exception from an I/O device,
> returning to the next instruction.

The return-behavior column deserves a second look, because it encodes the whole
semantics of each class. Interrupts and traps resume at the **next** instruction:
the current one either completed normally (interrupt) or _was_ the request
(trap), so there is nothing to redo. A fault resumes at the **same** instruction:
the instruction did not complete; it was stopped partway by a condition the
handler may be able to repair. If the repair succeeds, the only correct
continuation is to try it again. An abort resumes nowhere. A program that dies
with a "Floating point exception" or a "Segmentation fault" was killed by a
fault whose handler decided it could not repair the condition and converted it
into a process-terminating signal instead.

## Exception numbers and the exception table

The CPU cannot know what code handles each kind of event; that is the OS's
business. The link between the two is a number. Every distinct exception type is
assigned an **exception number** (also called a _vector_): some are fixed by the
processor architecture, the rest are given out by the operating system. On
x86-64, numbers 0 through 31 belong to the architecture (divide error is always
0, page fault is always 14), while numbers 32 through 255 are the OS's to assign
to device interrupts and software conventions.

| number | name | class | cause |
|---|---|---|---|
| 0 | divide error (`#DE`) | fault | division by zero, or quotient overflow |
| 3 | breakpoint (`#BP`) | trap | debugger's `int3` instruction |
| 6 | invalid opcode (`#UD`) | fault | undefined instruction encoding |
| 13 | general protection (`#GP`) | fault | privilege or protection violation |
| 14 | page fault (`#PF`) | fault | page not present, or access denied |
| 18 | machine check (`#MC`) | abort | fatal hardware error detected |
| 32–255 | OS-defined | interrupt/trap | device interrupts, legacy `int $0x80` |

At boot, the kernel builds the **exception table** (on x86-64, the _interrupt
descriptor table_, IDT): an array whose entry $k$ holds the address of the
handler for exception $k$, along with a few control bits. It then executes a
privileged instruction that loads the table's base address into a dedicated
register. From that point on, dispatch is pure hardware: when exception $k$
fires, the processor reads entry $k$ — base plus $k$ times the entry size, a
scaled index exactly like a [jump-table](/computer-architecture/machine-level-x86-64/control-flow)
or [page-table](/computer-architecture/virtual-memory/the-tlb-and-multi-level-page-tables)
access — and jumps to the address it finds there.

$$
% caption: Exception-table dispatch. The event supplies an exception number k;
% caption: the hardware adds the scaled index to the table base held in a
% caption: dedicated register, reads entry k, and jumps to the handler address
% caption: stored there. The table itself is built by the kernel at boot.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  box/.style={draw, minimum width=22mm, minimum height=9mm, inner sep=3pt, align=center},
  ent/.style={draw, minimum width=26mm, minimum height=6mm, inner sep=2pt, align=center, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  % inputs
  \node[box] (evt) at (0,1.1) {event:\\exception k};
  \node[box] (base) at (0,-1.3) {table base\\register};
  % index computation
  \node[box, fill=acc!8] (add) at (3.6,0) {base + k x 16};
  \draw[->] (evt.east) -- ($(add.west)+(0,0.25)$) node[midway,above,font=\scriptsize] {k};
  \draw[->] (base.east) -- ($(add.west)+(0,-0.25)$) node[midway,below,font=\scriptsize] {base};
  % the table
  \node[ent] (e0) at (7.6,1.5) {entry 0: handler addr};
  \node[ent] (e1) at (7.6,0.9) {entry 1: handler addr};
  \node[ent, font=\scriptsize] (edots) at (7.6,0.3) {...};
  \node[ent, fill=acc!8] (ek) at (7.6,-0.3) {entry k: handler addr};
  \node[ent, font=\scriptsize] (edots2) at (7.6,-0.9) {...};
  \node[ent] (e255) at (7.6,-1.5) {entry 255: handler addr};
  \node[anchor=south, font=\scriptsize, text=black] at (7.6,1.8) {exception table (in kernel memory)};
  \draw[->] (add.east) -- (ek.west) node[midway,above,font=\scriptsize] {read};
  % to handler
  \node[box] (hdl) at (11.6,-0.3) {handler k\\(kernel code)};
  \draw[->] (ek.east) -- (hdl.west) node[midway,above,font=\scriptsize] {jump};
\end{tikzpicture}
$$

> **Definition (Exception table).** A kernel-initialized array, indexed by
> **exception number**, whose entries are the addresses of the corresponding
> **handlers**. On an exception the hardware indexes this table by the event's
> number and transfers control to the handler there. A dedicated register,
> loaded by a privileged instruction at boot, holds the table's base address.

The table being in kernel memory, and the base register being loadable only in
kernel mode, is what keeps the mechanism trustworthy: a user program can neither
move the table nor rewrite an entry to point dispatch at its own code.

## What the hardware does

When an exception fires, the transition into the handler is performed by hardware,
because the running program cannot be trusted to do it (and may be the cause of the
problem). The sequence is fixed.

$$
% caption: The exception mechanism. (1) An event occurs while a program runs.
% caption: (2) Hardware indexes the exception table by the event number.
% caption: (3) Control jumps to the handler in kernel mode, prior state saved.
% caption: (4) The handler returns to the program (interrupt/trap/fault) or aborts.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  box/.style={draw, minimum width=20mm, minimum height=11mm, inner sep=3pt, align=center},
  num/.style={draw, circle, fill=acc!8, inner sep=1pt, minimum size=4.5mm, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box] (prog) at (0,0) {running\\program};
  \node[box, fill=acc!8] (tab) at (3.6,0) {exception\\table};
  \node[box] (hdl) at (7.4,0) {handler\\(kernel)};
  % event raises (1)
  \draw[->] (prog.east) -- (tab.west) node[midway,above] {event};
  \node[num] at (1.8,0.6) {1};
  % index table (2) -> handler (3)
  \draw[->] (tab.east) -- (hdl.west) node[midway,above] {index};
  \node[num] at (5.5,0.6) {2};
  \node[num] at (3.7,-0.78) {3};
  \node[anchor=west,font=\scriptsize] at (4.0,-0.78) {save state, enter kernel};
  % return (4)
  \draw[->] (hdl.south) |- ($(prog.south)+(0,-1.0)$) -- (prog.south);
  \node[anchor=north] at (4.4,-1.15) {iret: restore + return  (or abort)};
  \node[num] at (1.8,-1.0) {4};
\end{tikzpicture}
$$

Concretely, the hardware (1) **saves the processor state**, at minimum the
faulting/next `%rip` and condition flags, onto the kernel stack, so the program
can be resumed; (2) **switches to kernel mode**, granting the privileges the
handler needs; and (3) **jumps through the exception table** to the handler. The
handler runs, does its work, and finishes with a **return-from-interrupt**
instruction (`iret`), which **restores the saved state and switches back to user
mode**, resuming the interrupted program. The one detail that distinguishes the
classes is _which_ instruction it resumes at: the **next** one for interrupts and
traps, the **same** one for faults that recovered — and for an abort, it does not
return at all.

## Not a procedure call

The description above sounds like a `call` with extra ceremony, and the analogy
is useful: control transfers to a routine, state is pushed, a matching return
instruction comes back. But three differences separate an exception from a
procedure call, and each exists for a reason.

**More state is pushed.** A `call` pushes exactly one thing: the 8-byte return
address. An exception must reconstruct the processor _as it was_, so the
hardware pushes the return `%rip`, the flags register (a fault handler will
clobber the condition codes, but the interrupted program may be one instruction
away from a `jle` that depends on them), the stack pointer, and the segment
state that encodes the privilege level. Some exceptions also push an **error
code** describing the cause.

**The state goes on the kernel stack.** If control is transferring from a user
program into the kernel, everything is pushed onto a **kernel stack**, not the
user's. The user's `%rsp` cannot be trusted: it may point at an unmapped page,
or be deliberately aimed at kernel data as an attack. Each process has a small
dedicated kernel stack, known-good, on which its exceptions and syscalls run;
the hardware switches `%rsp` to it as part of taking the exception.

**The privilege level changes.** The handler starts executing in kernel mode
regardless of what mode the machine was in, and `iret` is what drops back. No
sequence of user-mode instructions can produce this transition with control of
_where_ it lands: the only way into kernel mode is through an entry the kernel
itself installed in the exception table.

$$
% caption: The stack transition on an exception from user mode. The user stack
% caption: is left untouched; the hardware switches to the process's kernel stack
% caption: and pushes the old stack pointer, the saved flags, the return
% caption: address, and (for some exceptions) an error code. iret pops them back.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  cell/.style={draw, minimum width=27mm, minimum height=6mm, inner sep=2pt, align=center, font=\scriptsize},
  lbl/.style={font=\scriptsize, text=black}]
  \definecolor{acc}{HTML}{2348F2}
  % user stack (left), untouched
  \node[lbl] at (0,2.2) {user stack};
  \node[cell] (u1) at (0,1.6) {caller frames};
  \node[cell] (u2) at (0,1.0) {lo\/cal data};
  \node[cell, fill=acc!8] (u3) at (0,0.4) {top of stack};
  \node[lbl, anchor=north, align=center] at (0,-0.1) {the exception\\leaves it alone};
  % kernel stack (right), filled by hardware
  \node[lbl] at (6.4,2.2) {kernel stack, one p\/er pro\/cess};
  \node[cell] (k1) at (6.4,1.6) {saved \texttt{\%ss}, \texttt{\%rsp}};
  \node[cell] (k2) at (6.4,1.0) {saved \texttt{\%rflags}};
  \node[cell] (k3) at (6.4,0.4) {saved \texttt{\%cs}, \texttt{\%rip}};
  \node[cell, fill=acc!8] (k4) at (6.4,-0.2) {error code (some)};
  \node[cell] (k5) at (6.4,-0.8) {handler's frames ...};
  % the switch arrow
  \draw[->, acc, thick] (1.7,0.4) -- (4.7,0.4)
    node[midway, above, font=\scriptsize, text=acc, align=center] {CPU swaps stacks\\+ pushes state};
  % rsp marker
  \draw[->] (8.6,-0.8) -- (k5.east) ;
  \node[anchor=west, font=\footnotesize] at (8.6,-0.8) {\texttt{\%rsp}};
\end{tikzpicture}
$$

The pushed record carries what `iret` needs: it pops the return address, the
flags, and the old stack pointer, and the privilege level comes back with the
segment state. Nothing about the interrupted computation escapes, which is why
a program can take thousands of interrupts per second without observing any of
them.

## Three exceptions on x86-64, concretely

Three x86-64 exceptions illustrate the taxonomy.

**Divide error (`#DE`, number 0)** fires when a divide instruction divides by
zero, or when the quotient overflows the destination: `idivq` with the most
negative 64-bit value divided by $-1$ produces a quotient that does not fit, and
faults identically. It is classified a fault, but no handler can produce a
correct quotient, so Unix systems do not attempt a repair: the kernel converts
it into a `SIGFPE` signal for the process, and the shell reports "Floating point
exception" (a historical misnomer; the exception is an integer one).

**General protection (`#GP`, number 13)** is the catch-all protection
violation: executing a privileged instruction in user mode (`hlt`, `cli`, a
write to a control register), or referencing a non-canonical address. There is
nothing to repair: the program attempted something the protection model
forbids. Linux delivers `SIGSEGV` and the default outcome is the familiar
"Segmentation fault".

**Page fault (`#PF`, number 14)** is the one fault that routinely _recovers_.
When an instruction references a virtual page
whose [PTE](/computer-architecture/virtual-memory/page-tables-and-page-faults)
says not-present, the hardware pushes an error code describing the access (read
or write, user or kernel), stores the faulting _address_ in a control register
where the handler can read it, and — the essential part — saves the address of
the **faulting instruction itself**, not the next one. The handler examines the
address: if it belongs to a valid region whose page is on disk, it reads the
page into a free frame, updates the PTE, and executes `iret` — which resumes at
the _same_ instruction. The load or store executes a second time, finds the page
present, and completes as if nothing had happened. If instead the address is
outside every valid region, the "fault" was a bug, and the handler delivers
`SIGSEGV`.

$$
% caption: Fault semantics: re-execution. Instruction I2 references a page that
% caption: is on disk and stops partway. The page-fault handler loads the page
% caption: and returns to I2 itself, which now succeeds; I3 follows. If the
% caption: address had been invalid, the handler would signal instead (abort path).
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  ins/.style={draw, minimum width=17mm, minimum height=8mm, inner sep=2pt, align=center},
  num/.style={draw, circle, fill=acc!8, inner sep=1pt, minimum size=4.5mm, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  % instruction stream
  \node[ins] (i1) at (-0.4,0) {I1};
  \node[ins, fill=acc!8] (i2) at (2.6,0) {I2: load\\(faults)};
  \node[ins] (i3) at (6.0,0) {I3};
  \draw[->] (i1.east) -- (i2.west);
  \draw[->] (i2.east) -- (i3.west) node[midway, above, font=\scriptsize] {retry ok};
  % handler below
  \node[ins, minimum width=30mm] (h) at (2.6,-2.0) {page-fault handler:\\load page, f\/ix PTE};
  \draw[->] (i2.south west) -- (h.north west)
    node[midway, left, font=\scriptsize, align=right] {fault};
  \draw[->] (h.north east) -- (i2.south east)
    node[midway, right, font=\scriptsize] {iret: same instruction};
  % abort path
  \node[ins, minimum width=24mm] (ab) at (8.4,-2.0) {invalid address:\\send SIGSEGV};
  \draw[->, black] (h.east) -- (ab.west);
\end{tikzpicture}
$$

Re-execution is why a page fault is invisible to the program. It is also why
fault handling must be _idempotent-friendly_: the faulting instruction ran zero
times as far as architectural state is concerned (the hardware guarantees no
partial effects survive), so running it "again" is really running it once.

## The system call, end to end

Traps deserve their own walkthrough, because the trap is the interface through
which every program uses the operating system: reading a file (`read`), creating
a process (`fork`), loading a program (`execve`), exiting (`exit`): each is a
kernel service with a number.

| `%rax` | syscall | purpose |
|---|---|---|
| 0 | `read` | read bytes from a file descriptor |
| 1 | `write` | write bytes to a file descriptor |
| 2 | `open` | open a file |
| 9 | `mmap` | map memory |
| 57 | `fork` | create a process |
| 59 | `execve` | load and run a program |
| 60 | `exit` | terminate the process |

The convention on x86-64 Linux mirrors the [procedure-call convention](/computer-architecture/machine-level-x86-64/procedures)
with one substitution: the syscall number goes in `%rax`, arguments go in
`%rdi`, `%rsi`, `%rdx`, `%r10`, `%r8`, `%r9`, and the result comes back in
`%rax`, where a value in the range $-4095..-1$ encodes a negated error number.
The fourth argument register is `%r10` rather than the usual `%rcx`, and the
reason is the mechanism itself: the `syscall` instruction _clobbers_ `%rcx` to
save the return address, as we are about to see.

Here is `write(1, "hello", 5)` with no library in the way:

```asm [hello.s]
.section .rodata
msg:
.ascii "hello"

.text
.globl _start
_start:
movq $1, %rax          # syscall number 1 = write
movq $1, %rdi          # arg 1: fd 1 (stdout)
leaq msg(%rip), %rsi   # arg 2: buffer address
movq $5, %rdx          # arg 3: byte count
syscall                # trap into the kernel
                       # back here with %rax = 5 (bytes written)
movq $60, %rax         # syscall number 60 = exit
xorq %rdi, %rdi        # arg 1: status 0
syscall                # does not return
```

What `syscall` itself does is small and fast: it copies the address of the next
instruction into `%rcx` and the flags into `%r11` (this is the clobber), switches
to kernel mode, and jumps to a kernel entry point whose address the kernel
installed in a machine register at boot. The kernel's entry code switches to the
process's kernel stack, saves the user registers, checks that `%rax` holds a
valid number, and indexes the **system-call table** (the same
number-to-handler-array pattern as the exception table) to reach `sys_write`.
That routine validates the arguments (is fd 1 open? are those 5 bytes readable
in the user's address space?), performs the write, and leaves the result in the
saved `%rax` slot. The exit path restores the user registers and executes
`sysret`, which jumps back to the address in `%rcx`, restores the flags from
`%r11`, and drops to user mode. The program continues at the instruction after
`syscall` with `%rax` holding 5.

$$
% caption: The syscall round trip for write(1, "hello", 5). User code loads the
% caption: number and arguments, syscall crosses into the kernel (return address
% caption: saved in rcx), the entry code dispatches through the syscall table to
% caption: sys-write, and sysret returns to user mode with the result in rax.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  stp/.style={draw, minimum width=24mm, minimum height=9mm, inner sep=2pt, align=center, font=\scriptsize},
  lbl/.style={font=\scriptsize, text=black}]
  \definecolor{acc}{HTML}{2348F2}
  % privilege line
  \draw[acc, thick, dashed] (-1.6,0) -- (9.8,0);
  \node[lbl, anchor=west] at (-1.6,0.3) {kernel mo\/de};
  \node[lbl, anchor=west] at (-1.6,-0.3) {user mo\/de};
  % user lane
  \node[stp] (setup) at (0.6,-1.3) {\texttt{\%rax}=1, args\\in \texttt{\%rdi}..\texttt{\%rdx}};
  \node[stp, fill=acc!8] (sc) at (3.6,-1.3) {\texttt{syscall}};
  \node[stp] (resume) at (8.0,-1.3) {next instruction,\\\texttt{\%rax} = 5};
  % kernel lane
  \node[stp] (entry) at (3.6,1.3) {entry: kernel stack,\\save registers};
  \node[stp, fill=acc!8] (sysw) at (8.0,1.3) {\texttt{sys\char95 write}\\runs};
  \draw[->] (setup.east) -- (sc.west);
  \draw[->] (sc.north) -- (entry.south)
    node[pos=0.3, right, font=\footnotesize, align=left] {rip saved\\in \texttt{\%rcx}};
  \draw[->] (entry.east) -- (sysw.west) node[midway, above, font=\scriptsize] {table[1]};
  \draw[->] (sysw.south) -- (resume.north)
    node[pos=0.3, right, font=\footnotesize] {\texttt{sysret}};
\end{tikzpicture}
$$

From the program's perspective a system call looks like a function call: set up
arguments, one instruction, result in `%rax`. The differences are everything
this lesson is about: it runs in kernel mode, on the kernel stack, and its
entry point is chosen by the kernel, not the caller. A user program cannot jump
to an arbitrary kernel address; it can only present a number, and the kernel
decides what that number means.

## Processes: the abstraction ECF builds

Step back from the mechanism and look at what it enables. The most consequential
application of ECF is the **process**: the illusion, granted to every running
program, that it has the processor to itself. A program sees its own
instructions executing one after another — a private, uninterrupted **logical
control flow** — while in physical fact the CPU is multiplexed among many
programs, tens of milliseconds at a time.

$$
% caption: Logical control flows. Each process observes an unbroken sequence of
% caption: its own instructions; physically the one CPU runs slices of A, B, and
% caption: C in turn. The gaps are invisible to each process because a context
% caption: switch saves and restores its complete state.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  lbl/.style={font=\scriptsize, text=black}]
  \definecolor{acc}{HTML}{2348F2}
  % time axis
  \draw[->] (0,-2.6) -- (9.6,-2.6) node[anchor=west, font=\scriptsize] {time};
  % three process rows
  \node[anchor=east] at (-0.3,0) {pro\/cess A};
  \node[anchor=east] at (-0.3,-0.8) {pro\/cess B};
  \node[anchor=east] at (-0.3,-1.6) {pro\/cess C};
  % slices: A runs 0-2, B 2-4, C 4-6, A 6-8
  \draw[acc, line width=1.6pt] (0,0) -- (2.2,0);
  \draw[acc, line width=1.6pt] (6.6,0) -- (8.8,0);
  \draw[black, dashed] (2.2,0) -- (6.6,0);
  \draw[acc, line width=1.6pt] (2.2,-0.8) -- (4.4,-0.8);
  \draw[black, dashed] (4.4,-0.8) -- (8.8,-0.8);
  \draw[black, dashed] (0,-0.8) -- (2.2,-0.8);
  \draw[acc, line width=1.6pt] (4.4,-1.6) -- (6.6,-1.6);
  \draw[black, dashed] (0,-1.6) -- (4.4,-1.6);
  \draw[black, dashed] (6.6,-1.6) -- (8.8,-1.6);
  % context switch markers
  \foreach \x in {2.2,4.4,6.6} {
    \draw[black] (\x,-2.45) -- (\x,-2.75);
  }
  \node[lbl, anchor=north, align=center] at (4.4,-2.85) {marks: context switc\/hes (timer in\/terrupts)};
  \node[lbl, anchor=west] at (0.1,0.4) {solid: running on the CPU};
\end{tikzpicture}
$$

The mechanism that sustains the illusion is a pairing of two things from this
lesson. The kernel programs an **interval timer** to raise an interrupt every
few milliseconds; each timer interrupt hands control to the kernel, through the
exception table with the running process's state saved, whether that process
likes it or not. The kernel may then perform a **context switch**: save the rest
of process A's state (its general-purpose registers, `%rip`, flags, stack
pointer, and its page-table base register), restore the previously saved state
of process B, and `iret` into B instead of A. Everything that made the CPU "be"
process A is on A's kernel-side record; when A's turn comes again, the same
restore replays it, and A resumes mid-computation with no way to tell it was ever
paused. The [next lesson](/computer-architecture/exceptions-and-io/interrupts-and-the-kernel)
walks the context switch in detail, and the
[multicore module](/computer-architecture/multithreading-and-multicore/processes-threads-and-parallelism)
builds on the process abstraction from there.

Without asynchronous ECF there is no preemption. A
cooperative system can share the CPU only if every program volunteers to yield.
The timer interrupt is what lets the kernel _take_ the CPU back, and it is the
difference between an operating system that hosts programs and one that is
hostage to them.

## Signals: ECF surfaced to user code

One rung further up, the same pattern — an event diverts control to a handler —
is offered _to programs themselves_ as **signals**. A signal is a small message
the kernel delivers to a process; delivery interrupts the process's normal flow
and runs a user-registered handler function, after which control returns to
wherever the process was. It is exception handling rebuilt in software, one
privilege level up.

The two layers connect directly. When the page-fault handler finds a genuinely
invalid address, its "abort" is not a machine halt: it is the delivery of
`SIGSEGV` to the offending process. The divide error becomes `SIGFPE`. Typing
control-C makes the terminal driver send `SIGINT`. In each case a
hardware-level exception (or interrupt) enters the kernel, and the kernel
re-expresses it as a user-level event that the process may catch with a handler,
ignore, or die from (`SIGSEGV`'s default). A program that installs a `SIGSEGV`
handler is registering an entry in its own private exception
table, with the kernel playing the role the hardware plays one level down.

## The software echoes of hardware ECF

CS:APP develops exceptional control flow as a hardware and kernel mechanism. The
same shape — divert control to a handler, then resume — reappears at every layer
above the kernel, and recognizing it makes a range of language features look like
one idea. The vocabulary is shared, but the enforcement gets weaker at each level
up.

At the bottom, a **hardware exception** is enforced by the CPU and is
non-optional: the divide-by-zero happens whether the program consents or not. One
level up, a **signal** is the kernel re-offering that event to the process; the
process may catch it, but the delivery is still the kernel's decision. Higher
still, purely in user space, a language runtime builds control-flow diversions
with no hardware help at all. C's `setjmp`/`longjmp` saves a snapshot of the
registers and stack pointer and later restores it, jumping non-locally back up the
call stack — a hand-rolled version of the state save-and-restore the hardware does
for an exception.[^setjmp] C++ and Rust build **exception handling** and
`panic`/`unwind` on top of that same stack-unwinding capability, walking the call
frames and running destructors as they go; Go's `panic`/`recover` and every
language's try/catch are the same pattern. The through-line is exact: an event
interrupts the normal sequence, control transfers to a handler chosen not by the
interrupted code but by a surrounding registration, and (sometimes) control
resumes.

$$
% caption: The ECF ladder. A hardware exception is enforced by the CPU; the kernel
% caption: re-expresses it as a signal to a process; a language runtime builds
% caption: non-local jumps and try/catch on the same save-restore idea, entirely in
% caption: user space. Enforcement weakens going up; the shape stays the same.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  layer/.style={draw, minimum width=52mm, minimum height=9mm, inner sep=2pt, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[layer, fill=acc!18] (hw) at (0,0) {hardware exception (CPU-enforced)};
  \node[layer, fill=acc!8] (sig) at (0,1.1) {signal (kernel to process)};
  \node[layer] (lang) at (0,2.2) {try/catch, longjmp, panic (user space)};
  \draw[<->, black] (hw.north) -- (sig.south);
  \draw[<->, black] (sig.north) -- (lang.south);
  \node[anchor=west, font=\scriptsize, text=black] at (2.9,2.2) {enforcement weakest};
  \node[anchor=west, font=\scriptsize, text=black] at (2.9,0) {enforcement strongest};
\end{tikzpicture}
$$

One practical wrinkle the hardware view predicts: mixing the layers is delicate.
Only a small set of functions are **async-signal-safe** — safe to call from
inside a signal handler — because a signal can arrive between _any_ two
instructions, including halfway through `malloc`'s update of its internal data
structures. A handler that calls a non-reentrant function can corrupt state the
interrupted code was midway through, the user-space version of exactly why the
kernel switches to a known-good stack and masks further interrupts on entry. The
mechanism at every level has to reckon with the same fact: an asynchronous event
can strike at an inconvenient moment, and safe handling means assuming it will.

> **Takeaway.** **Exceptional control flow** diverts the processor from its
> normal sequence in response to an event, vectoring by **exception number**
> through the kernel-built **exception table** to a **handler**. The four
> classes are **interrupts** (async, from devices), **traps** (intentional
> syscalls), **faults** (recoverable, retry the same instruction), and
> **aborts** (unrecoverable, no return). Unlike a procedure call, an exception
> pushes the flags and stack pointer as well as the return address, pushes them
> onto the **kernel stack**, and raises the privilege level. On x86-64, `#DE`
> becomes `SIGFPE`, `#GP` becomes `SIGSEGV`, and `#PF` either loads the page and
> **re-executes the faulting instruction** or becomes `SIGSEGV` too. The
> `syscall` instruction is a trap: number in `%rax`, arguments in registers,
> dispatch through the syscall table, `sysret` back. On these mechanisms the
> kernel builds **processes** (via timer interrupts and context switches) and
> **signals** (ECF re-offered to user code).

The most important asynchronous case — the interrupt — is also the gateway to the
I/O system and to switching between processes. The
[next lesson](/computer-architecture/exceptions-and-io/interrupts-and-the-kernel)
takes up interrupt controllers, the user/kernel boundary, DMA, and how a disk
read actually reaches memory.

[^setjmp]: **Bryant & O'Hallaron**, _CS:APP_, §8.6 — Nonlocal Jumps: `setjmp` saves the calling environment (registers and stack pointer) and `longjmp` restores it, transferring control non-locally back up the stack. The connection to hardware exception state save-and-restore, and to the stack-unwinding that C++ and Rust exceptions build on, is developed in the C standard library documentation for `<setjmp.h>` and in the Itanium C++ ABI's exception-handling specification.
