Part IIInstruction Set Architecture

Exceptions and System Calls

May 16, 2026·29 min read·intermediate

The discussion of an ISA so far has assumed that the processor executes instructions one after another, in the order the program lists them, performing each one to completion. Real processors must…

The discussion of an ISA so far has assumed that the processor executes instructions one after another, in the order the program lists them, performing each one to completion. Real processors must also handle events that disrupt this neat picture: an instruction that divides by zero, a memory access that fails because the page is not present, a hardware interrupt from a device that wants attention, an explicit request from a user program for an operating-system service. All of these are forms of exception, and the mechanism by which the processor handles them is one of the most important parts of any ISA.

Exception handling is also the place where the ISA stops being purely about instructions and starts being about the relationship between hardware, the operating system, and user programs. The exception model is the gateway through which the operating system gains control whenever something unusual happens. It is, in a real sense, the seam between the user-mode world we have been describing and the kernel-mode world that supervises it. This chapter walks through that gateway.

01. Three Kinds of Disruption

Computer architects distinguish three kinds of events that interrupt the normal flow of instruction execution. The vocabulary varies between ISAs (x86 and ARM disagree about which name applies to what), but the underlying concepts are the same.

Synchronous exceptions are caused by the currently executing instruction. The instruction is doing something that the hardware cannot complete in the normal way: dividing by zero, accessing an invalid memory address, executing a privileged instruction in user mode, accessing a page that has been swapped out. The exception is a direct consequence of what the instruction tried to do, and it occurs at a precisely defined point in the program — the same place every time the program runs with the same data.

Asynchronous interrupts are caused by external events unrelated to the current instruction. A timer fires; a device finishes a transfer; a network card receives a packet; another core sends an interprocessor interrupt. The CPU is interrupted at whatever point it happens to be executing, with no relationship to what the program was doing. The same program might be interrupted at different points on different runs.

Software-generated traps are explicit requests from the program for the kernel's attention. The most common is the system call, in which a user program asks the operating system to perform a service (open a file, allocate memory, send data on a socket). Software traps look mechanically like synchronous exceptions — they happen when a specific instruction executes — but they are deliberate rather than accidental.

The three categories share enough machinery that ISAs typically handle them through a single unified mechanism, with a code or vector number identifying the specific cause. Different ISAs use different umbrella terms: x86 calls everything an "interrupt or exception" with various sub-classes; ARM uses "exception"; RISC-V uses "trap." For this chapter we will mostly use exception for synchronous events, interrupt for asynchronous ones, and trap for the general unified mechanism.

02. What Happens When an Exception Occurs

The mechanism by which a processor handles an exception is broadly similar across all modern ISAs. The details differ, but the essential steps are these.

The CPU notices the exception. This may be the result of an instruction completing with an error condition, an external interrupt line being asserted, or a software-trap instruction being executed.

The CPU saves enough state to resume later. Typically this means saving the program counter (so the handler knows where the interrupted program was), the processor flags (so the handler knows the state of the condition codes), and an indication of what caused the exception. The state may be saved on the stack (x86), in dedicated registers (ARM, RISC-V), or in some combination.

The CPU switches to a higher privilege level. The handler will run in kernel (supervisor, machine, or whatever the ISA calls it) mode, which allows access to privileged instructions and resources. We will return to privilege levels later in this chapter.

The CPU looks up the handler's address. A table somewhere in memory or in a register holds the addresses of the handlers for various kinds of exceptions. The CPU uses the cause to index into the table.

The CPU jumps to the handler. Execution proceeds in kernel mode at the handler address. The handler reads any cause information the hardware has provided, performs whatever recovery or service is needed, and then returns to the interrupted program.

The CPU returns from the exception. A special return-from-exception instruction restores the saved state and resumes execution at the point where the exception occurred (or, for some kinds of exception, at the next instruction after).

A small example is useful. Suppose a user program executes an instruction that causes a page fault — an attempt to read a memory address that is not currently mapped to physical memory. The hardware:

  1. Notices that the page-table walk failed.
  2. Saves the current PC (pointing to the faulting instruction) and the faulting address.
  3. Switches to kernel mode.
  4. Looks up the page-fault handler's address.
  5. Jumps to the handler.

The kernel's page-fault handler then examines the faulting address, decides whether the page can be brought in (perhaps it was swapped to disk; perhaps this is a stack-growth fault that needs a new page; perhaps it is an honest bug), allocates and maps a page if appropriate, and returns. The hardware reloads the saved state and re-executes the faulting instruction, which now succeeds because the page is present.

Notice how much architectural infrastructure is implied in this single example. The hardware has to know how to detect a page fault (and which kinds of faults are recoverable). It has to be able to save state precisely enough that the instruction can be re-executed without observable change. It has to switch privilege levels safely. It has to vector the handler call correctly. The exception model is one of the most carefully specified parts of any ISA, because any inconsistency between hardware and operating-system expectations causes catastrophic, hard-to-debug failures.

03. Precise Exceptions

A property of the exception model that has loomed very large in the history of computer architecture is precise exceptions. An exception is precise if, when the handler runs, the saved state describes the program exactly as it would look if every instruction up to (and not including) some specific instruction had completed and no later instruction had any effect. In other words, the exception "appears" to occur cleanly between two instructions, with no traces of partially-completed work.

Precise exceptions are valuable because they make the handler's job feasible. A page-fault handler that wants to re-execute the faulting instruction needs all the architectural state to look like it did just before the instruction started. A debugger that wants to single-step a program needs the program state to be observable at instruction boundaries. An operating system that wants to context-switch to another process needs to be able to checkpoint and resume a process at the same point.

In a simple non-pipelined processor, precise exceptions come almost for free: instructions execute one at a time to completion, and exceptions can only happen at instruction boundaries. In any pipelined or out-of-order processor — that is, in every modern processor — precise exceptions are surprisingly hard. At the moment an exception is detected, many instructions before the faulting one may not yet have committed their results, while instructions after it may have already executed speculatively. The hardware has to undo the speculative work and complete the in-flight non-speculative work before invoking the handler.

Modern processors achieve this through a structure called the reorder buffer, which we will examine in Chapter 25. Briefly, every instruction is assigned a slot in the reorder buffer when it enters the pipeline; the slot tracks whether the instruction has executed and whether it has any side effects pending. Instructions retire in program order; when an instruction with an exception reaches the front of the buffer, the hardware flushes everything behind it and dispatches to the exception handler. The cost is real (a substantial chunk of the processor's transistors goes to making this work) but the benefit is that operating-system-visible behavior remains clean.

A few ISAs and a few exception types do not provide precise exceptions. Imprecise exceptions exist mainly for kinds of error that cannot be cleanly attributed to a single instruction — for example, a memory error reported asynchronously by hardware after the read that caused it has already retired. Imprecise exceptions are usually fatal; the operating system cannot recover, and the best it can do is terminate the process or panic.

The user-facing rule is: the ISA must promise precise exceptions for all exception types that the operating system might want to recover from. Every modern ISA does so for page faults, integer-arithmetic exceptions, illegal-instruction exceptions, and software traps. Some ISAs allow imprecise floating-point exceptions, which is occasionally an issue for high-precision numerical software but mostly invisible.

04. The Vector Table

The mechanism for finding the right handler is some form of vector table: a table, in memory or in dedicated registers, indexed by the cause of the exception, holding the address of the corresponding handler.

On x86, this is the Interrupt Descriptor Table (IDT). It contains 256 entries, one for each possible interrupt vector. The CPU register IDTR holds the table's base address. When an exception occurs, the CPU computes its vector number (from the cause), reads the corresponding IDT entry, and jumps to the handler whose address that entry contains.

On ARM, the vector base address register (VBAR_EL1 or similar) points to a small table at a known offset; each entry is up to 32 bytes of code, not just an address. When an exception occurs, the CPU jumps to the corresponding entry, which typically contains a short stub that branches to the real handler.

On RISC-V, the machine trap-vector base-address register (mtvec) holds the base address. RISC-V supports two modes: in direct mode, all traps go to a single address (the handler must inspect the cause itself); in vectored mode, the address is computed as mtvec + 4 * cause, and each cause has its own entry point. Most operating systems use direct mode for simplicity; embedded systems sometimes use vectored mode for lower latency.

The vector table is a piece of trusted infrastructure: corrupting it lets an attacker redirect any exception to arbitrary code. Operating systems set it up carefully at boot time, mark its memory pages read-only, and arrange that user programs cannot modify it.

05. Privilege Levels

The exception mechanism is deeply tied to privilege levels: distinct modes in which the processor can run, each with different access rights to resources. The fundamental distinction is between user mode, in which application code runs, and kernel mode (also called supervisor or privileged mode), in which the operating system runs.

User mode restricts what code can do: it cannot execute privileged instructions (those that configure page tables, manage interrupts, halt the CPU, or access certain registers); it can only access memory pages mapped to it; it cannot change its own privilege level except through the trap mechanism. Kernel mode lifts those restrictions.

The crucial property is that the only path from user mode to kernel mode is through an exception or interrupt. A user program cannot simply switch into kernel mode; it must trap, at which point the hardware automatically elevates the privilege level and transfers control to a kernel-mode handler at a well-defined address. This is what makes the trap mechanism a security boundary: the kernel chooses what addresses are valid handler entry points, and a user program cannot enter the kernel anywhere else.

Different ISAs have different numbers of privilege levels. x86 originally had four (rings 0 through 3); only rings 0 and 3 are widely used. ARMv8-A defines four "Exception Levels": EL0 (user), EL1 (kernel), EL2 (hypervisor), EL3 (secure monitor). RISC-V defines U (user), S (supervisor), and M (machine) modes. The richer level structures support virtualization (a hypervisor running in EL2 manages multiple guest kernels in EL1) and trusted execution environments (a secure monitor in EL3 manages secure-world software).

For a typical operating system, only two levels matter: kernel and user. The other levels are used for virtualization and security features that we will not address in detail.

06. System Calls

The most common deliberate trap is the system call: a request from a user program to the operating system for a service. The mechanism is the same as for any other trap, but the cause is an instruction the program executed on purpose.

Different ISAs have different system-call instructions:

  • x86-64 uses syscall, which switches to kernel mode and jumps to a handler whose address is in the model-specific register LSTAR. (Older x86 used int 0x80 or sysenter.)
  • AArch64 uses svc #imm, which raises an exception of type "supervisor call." A small immediate identifies the call class.
  • RISC-V uses ecall, which raises a trap from whatever mode is currently active (U-mode programs trap to S-mode; S-mode kernels trap to M-mode firmware).

The convention by which a user program tells the kernel which service it wants is called the system-call ABI. Typically the program puts a number identifying the desired service into a register, puts arguments into other registers, and executes the trap instruction. The kernel reads the number, dispatches to the appropriate service routine, performs the work, places the result in a return register, and returns from the trap. The user program continues, sees the result, and proceeds.

A simplified Linux x86-64 example: writing the string "hello\n" to standard output through the write system call.

Assembly
mov rax, 1 ; system-call number 1 = write
mov rdi, 1 ; first argument: file descriptor 1 = stdout
mov rsi, msg ; second argument: pointer to data
mov rdx, 6 ; third argument: length in bytes
syscall ; trap into the kernel; result is left in rax

The kernel's write implementation reads the registers, validates the file descriptor, copies the data from user memory to the appropriate destination, and returns the number of bytes written.

System-call ABIs are highly stable: programs compiled against a Linux kernel from years ago still work on current kernels because the system-call numbers and conventions are preserved. This is a deliberate stability promise that mirrors the ISA's stability.

A subtlety worth noting: when the kernel handles a system call, it needs to access user-mode memory (for example, the msg buffer above). In modern systems the kernel has its own virtual address space — typically a single shared space with mappings for both kernel and user pages — and accessing user memory requires care. There are usually helper functions (copy_from_user, copy_to_user in Linux) that handle the access safely, including the case where the user pointer is invalid or the user page has been swapped out (which causes a page fault during the kernel's access, handled specially).

07. Interrupts and Their Latency

Asynchronous interrupts use the same trap mechanism as synchronous exceptions, but they raise different concerns.

The most pressing is interrupt latency: the time between the hardware asserting the interrupt and the handler beginning execution. This includes:

  • The time for the CPU to finish the current instruction (or pipeline stage) at which it can take the interrupt.
  • The time to save state and switch privilege levels.
  • The time to look up the handler address.
  • The time to fetch and begin executing the handler.

For a typical desktop or server, interrupt latency of a few microseconds is acceptable. For real-time systems — industrial control, audio processing, certain robotics — latency in the tens of microseconds is too high. Real-time operating systems and ISAs designed for embedded use go to substantial trouble to keep interrupt latency low and predictable. Techniques include avoiding deep pipeline drains, using vectored interrupts to skip handler-dispatch software, and architecting the kernel so that interrupts can be taken even while the kernel is busy.

A second concern is nested interrupts. While one handler is running, another (perhaps higher-priority) interrupt may arrive. The CPU may be configured to allow this — the new interrupt preempts the current handler — or to disallow it, queuing the new interrupt until the current one returns. Operating systems balance these choices carefully: too much queuing causes high latency for important interrupts; too much preemption complicates the interrupt code, which already has to be carefully written.

A third concern is interrupt masking. The CPU has bits that disable interrupts entirely (or selectively), used by code that must run atomically with respect to interrupts. The classical example is the kernel's own data structures: a kernel that is updating a linked list cannot afford to be interrupted halfway through, because the interrupt handler might examine the list and find it inconsistent. Disabling interrupts around the critical section solves the problem; the cost is added latency for any interrupt that happens to arrive during the critical section.

Modern operating systems try to keep interrupt-disabled regions short, both for latency and for correctness on multi-core systems (where masking on one core does not prevent another core from seeing the inconsistent state). The technique of deferred procedure calls — handler does only the minimum work in interrupt context, queuing the rest for execution outside interrupt context — is universal in modern kernels.

08. Exception Causes: A Catalog

A non-exhaustive list of exception types that real ISAs define gives a feel for the variety:

  • Page fault — a memory access whose virtual address could not be translated.
  • Protection fault — a memory access that violated the protection bits (e.g., write to a read-only page).
  • Illegal instruction — the instruction's bit pattern is undefined.
  • Privileged instruction — a privileged instruction was executed in user mode.
  • Divide by zero — integer division with a zero divisor.
  • Floating-point exception — overflow, underflow, invalid operation, or inexact result, depending on what the FPU is configured to trap on.
  • Misaligned access — a load or store with an address not aligned to the natural boundary, on architectures that require alignment.
  • Breakpoint — a debug trap, typically triggered by a special instruction or by hardware breakpoint registers.
  • System call — an explicit request for kernel services.
  • External interrupt — an asynchronous request from a device.
  • Inter-processor interrupt — an asynchronous request from another CPU core.
  • Timer interrupt — periodic interrupt used by the kernel for scheduling.
  • Machine check — a hardware error detected by the CPU (uncorrectable ECC error, internal parity failure, etc.).

Each of these has its own handler conventions, its own recovery semantics, and its own implications for the kernel. The breadth of the list is part of why operating-system kernels are so complex: they must handle correctly every kind of exception the hardware can raise.

09. Faults, Traps, and Aborts

A finer taxonomy distinguishes three classes of synchronous exceptions by where the saved PC points and what the handler is expected to do.

A fault is an exception that is reported before the offending instruction has its effect. The saved PC points at the instruction itself, and the handler is expected to fix the underlying problem and return; the hardware then re-executes the same instruction, which now succeeds. Page faults are the canonical example: the kernel maps the missing page and the load that caused the fault is retried. Most arithmetic exceptions on most ISAs are also faults, because the program may want to repair the operands and re-run.

A trap is an exception that is reported after the instruction has its effect. The saved PC points at the next instruction, and the handler is expected to do its work and return without re-executing the trapping one. Software-breakpoint instructions (int 3 on x86, brk on AArch64, ebreak on RISC-V) are traps, as are system-call instructions: the user program does not want syscall re-executed, it wants the next instruction.

An abort is an exception so severe that the offending instruction's location cannot be determined precisely or that recovery is not meaningful. Machine checks reporting uncorrectable hardware errors are aborts; the saved PC may not point usefully at anything in particular, and the handler typically tears the system down rather than attempting to resume.

The Intel manuals codify this terminology explicitly, and the AArch64 manual uses the related but distinct words synchronous exception, interrupt, and abort. RISC-V's specification uses exception for synchronous events and interrupt for asynchronous ones, and disambiguates faults from traps by the cause code rather than a separate name. Whatever the words, the underlying distinction is real: the handler's first job is to know which class the exception belongs to so that it returns to the right place.

A related notion is exception priority. When several exception conditions are detected on the same instruction — say, an instruction-fetch page fault on an instruction that would also have caused a privileged-instruction fault if fetched — the architecture specifies which one is reported. Every modern ISA fixes this priority order so that operating systems can rely on it. The order is usually some variant of: instruction-fetch faults > illegal/privileged-instruction > operand decode > address computation > data-side faults > arithmetic faults > debug breakpoints, with interrupts inserted at boundaries where they are permitted to preempt.

10. Signals: Exceptions in User Space

The hardware exception mechanism stops at the kernel; user programs do not see the trap directly. The way exception conditions are propagated into user space, on Unix-like systems, is through signals. A signal is an asynchronous notification delivered to a process: a software analogue of a hardware interrupt, used both to report exceptional conditions and to allow processes to communicate.

When the kernel handles a synchronous exception that originated in user mode and that does not need any kernel-level recovery (a SIGSEGV from a bad pointer, a SIGFPE from a floating-point exception, a SIGILL from an unrecognized instruction), it does not always terminate the process. Instead, it prepares a signal frame: it allocates a small region on the user stack (or on a separate signal stack the program has registered), copies the saved register state into it, and arranges to return not to the original faulting instruction but to the signal handler that the program has registered with sigaction. The handler runs in user mode, can examine the saved state, and can either repair the situation, log it, or terminate cleanly. When the handler returns, a small trampoline (__restore_rt on Linux) issues a sigreturn system call that asks the kernel to restore the saved state; the original instruction then re-executes.

The parallel between hardware exception handling and signal handling is exact. The hardware interrupts the program, saves state, and dispatches to a handler at a known address; the kernel does the same for signals, with the kernel itself playing the role the hardware played at the lower level. The result is that user-level programs can implement many of the same patterns the kernel implements: a JIT that handles segfaults from probes into unmapped memory; a garbage collector that uses page protection and a SIGSEGV handler to detect writes to old generations; a debugger that intercepts SIGTRAP from breakpoints. The signal API is, from the architectural point of view, the user-mode reflection of the trap API.

A few signals deserve special mention. SIGSEGV is the user-mode face of page faults that the kernel could not satisfy. SIGBUS corresponds to alignment or bus errors. SIGFPE carries floating-point and integer-arithmetic exceptions. SIGILL comes from illegal-instruction faults. SIGTRAP is delivered for breakpoints and single-stepping. SIGINT, SIGTERM, and SIGKILL are not exception-driven at all but are also part of the same mechanism. Every Unix programming reference covers them in detail; the architectural point worth keeping is that they share a single delivery infrastructure with the hardware exception model.

11. Interrupt Controllers and Delivery

The hardware-side picture so far treated interrupts as a single line that some external party asserts to get the CPU's attention. A real system has hundreds of devices and dozens of cores, and a dedicated piece of hardware — the interrupt controller — sits between them and the CPU to route, prioritize, and arbitrate.

On x86, the controller has been the APIC (Advanced Programmable Interrupt Controller) for thirty years, recently superseded by the x2APIC in extended systems. Each CPU has a local APIC with a vector table of its own; a system-wide I/O APIC (or its successor) routes device interrupts to the appropriate core. PCIe adds MSI and MSI-X, in which devices write a vector to a memory-mapped address rather than asserting a wire, allowing thousands of distinct interrupt sources without the wiring complexity.

On AArch64, the controller is the GIC (Generic Interrupt Controller), in versions GICv2 through GICv5, again with a per-CPU redistributor and a system-wide distributor. The architecture explicitly defines categories: SGIs (Software-Generated Interrupts, used between cores), PPIs (Private Peripheral Interrupts, per-core), SPIs (Shared Peripheral Interrupts, system-wide), and LPIs (Locality-specific Peripheral Interrupts, used for MSI-equivalent delivery).

On RISC-V, the controller is the PLIC (Platform-Level Interrupt Controller) for traditional wired interrupts and the AIA (Advanced Interrupt Architecture) for MSI-style delivery, with each core having a local CLINT for software and timer interrupts.

Four properties recur across all of them.

Priority. Each interrupt source has a programmable priority. The controller delivers only interrupts with priority higher than the currently running task's threshold, so a low-priority device cannot preempt time-critical work.

Masking and routing. Software can disable individual interrupts at the controller (separate from the CPU's global enable bit) and can specify which core should receive each one. A multi-core system spreads the interrupt load across cores by routing different sources to different targets, often dynamically.

Acknowledgement. When the CPU accepts an interrupt, the handler must explicitly acknowledge it (writing to an EOI, end-of-interrupt, register on the controller). Without acknowledgement, the controller assumes the interrupt is still pending and may not deliver further ones at the same priority.

Spurious and shared interrupts. Hardware sometimes asserts an interrupt line and de-asserts it before the controller has formally accepted it; the result is a spurious interrupt with no real source, and handlers must tolerate it. Multiple devices may also share a single interrupt line on legacy buses, so the handler must walk a list of registered drivers and ask each one whether the interrupt was theirs. Modern MSI eliminates both classes of problem by giving each source its own vector.

From the user-program point of view, none of this matters: the program sees only its scheduling latency. From the operating system's point of view, configuring the interrupt controller correctly is one of the most fragile parts of bringing up a new platform, and most of the boot-time delay before user code can run consists of probing buses, programming the controller, and verifying that interrupts are delivered as expected.

12. Boot, Reset, and Initial Exception Handling

A last topic that the exception model implies but rarely makes explicit is what happens at the very start of execution, before any operating system has set up the trap infrastructure.

When a CPU comes out of reset, almost everything we have described is missing. The vector table has not been populated; the privilege level is the highest one available; caches are usually disabled; the MMU is off; only a small bootstrap memory region is reachable. The CPU starts executing at a fixed reset address, which is hardwired in the silicon: 0xFFFFFFF0 on x86, an implementation-defined low address on AArch64 (set by RVBAR_ELx), 0x80000000 or similar on RISC-V.

The first instructions executed are part of the platform firmware (UEFI, U-Boot, OpenSBI, BIOS) running at the highest privilege. Their job, broadly, is to bring the system into a state where an operating system can run: configure DRAM, set up the initial exception vectors, populate the privileged control registers, set up a usable stack, and either hand off to a bootloader or jump directly to a kernel. The ARM and RISC-V worlds make the layering explicit: the firmware runs at the highest exception/privilege level (EL3 on AArch64, M-mode on RISC-V), drops to a lower level for the kernel, and remains available as a service via SMC calls or SBI calls.

During this early period, exceptions are particularly dangerous. A bug in the firmware that causes an unexpected fault has no place to go because the vector table is not yet valid; the CPU enters a recursive fault state and either resets or hangs. Real firmware therefore sets up a minimal exception vector very early in its execution, even before doing anything else useful, just so that diagnostic information can be produced if something goes wrong.

The kernel, when it takes over, has to re-do this work for its own privilege level: write the vector base register (IDTR, VBAR_EL1, mtvec), allocate kernel stacks for each exception type, set up the system-call entry, configure the interrupt controller, and only then enable interrupts and drop to user mode. The first user instruction — typically the entry point of init or systemd or the first userspace shell — thus runs against a fully initialized exception infrastructure that has been built up layer by layer, with every layer relying on the one below to handle its faults.

We will return to the boot process in detail in Chapter 47, and to the OS–firmware interface in Chapter 46. The architectural point worth keeping for now is that the elegant exception model of this chapter is something the system constructs as it boots; it does not exist for free, and the bootstrap dance to bring it into existence is one of the more delicate things the platform firmware does.

13. Why the Exception Model Matters

A clean, precise, well-specified exception model is one of the things that separates a usable ISA from an unusable one. Several practical implications follow.

Operating systems are written against the exception model. Every kernel has dozens of handlers, one for each kind of exception, written carefully to handle every case correctly. A poorly specified or quirky exception model — say, one that does not save enough state to resume the faulting instruction — makes the kernel impossible to write robustly.

Virtual memory depends on page-fault handling. Demand paging (loading a page from disk only when first accessed), copy-on-write (sharing pages between processes until one writes), memory-mapped files, and many other features all depend on the kernel being able to handle a page fault, fix up the mapping, and re-execute the faulting instruction transparently. If the architecture cannot do this, none of those features work.

Debugging depends on breakpoints. A debugger sets breakpoints by overwriting an instruction with a trap-causing pattern (typically a software-breakpoint instruction). When the program hits the breakpoint, the trap fires and the debugger gets control. This requires the trap to be precise and the instruction to be cleanly resumable.

Security depends on the privilege boundary. The integrity of the user-kernel boundary rests entirely on the trap mechanism. Any flaw in how the hardware switches modes, saves state, or vectors handlers can become a security vulnerability. There have been entire classes of CPU bugs (the Meltdown and Spectre families, for instance) that turned out to be subtle violations of the assumed privilege-boundary semantics, with serious consequences.

The exception model is, in short, where the architectural rubber meets the operating-system road. The rest of the ISA can be inelegant and software still works around it; an inelegant exception model breaks operating systems. This is why ISA specifications devote so many pages to exceptions, why ARM and RISC-V have entire separate volumes for the privileged ISA, and why architects spend years getting the details right.

14. Summary

Exceptions are the mechanism by which a processor handles events that disrupt the normal flow of execution: synchronous exceptions caused by an instruction, asynchronous interrupts caused by external events, and deliberate software traps used by user programs to request operating-system services. All three flow through a unified trap mechanism: the CPU saves enough state to resume, switches to a higher privilege level, looks up the handler's address in a vector table, and jumps. A return-from-exception instruction reverses the process. A finer taxonomy distinguishes faults (re-executable) from traps (non-re-executable) from aborts (non-recoverable), and every architecture fixes a priority order among simultaneously detected exceptions so that operating systems can reason about them. Signals are the user-space reflection of the same machinery, letting application code register handlers for the conditions — segmentation faults, floating-point exceptions, illegal instructions — that the kernel chose to forward rather than fatally terminate.

A precise exception model — in which the handler sees state that looks exactly as if the program had executed cleanly up to a single boundary — is essential for operating-system features like demand paging, debugging, and context switching. Achieving precise exceptions in a pipelined or out-of-order processor requires substantial hardware machinery, which we will examine in later chapters. Asynchronous interrupts flow through a dedicated interrupt controller — APIC on x86, GIC on AArch64, PLIC and AIA on RISC-V — that handles priority, masking, routing, acknowledgement, and the recent shift to message-signaled delivery. The trap mechanism is also the only path from user mode to kernel mode and so forms the security boundary that protects the operating system from user programs. Even the existence of this clean machinery is something the system has to construct during boot, layer by layer, starting from a CPU coming out of reset with no vector table and no stack.

This concludes Part III. We have covered what an ISA defines, the structure of its instruction set, the toolchain that produces and consumes machine code, the calling conventions and ABIs that let separately compiled code interoperate, and the exception model that handles everything that goes wrong. Part IV turns from the contract to the substrate: the memory hierarchy, caches, DRAM, virtual memory, and storage on which every modern processor relies to keep itself fed.

Book mode
computer-architectureisainstruction-set
Was this helpful?