A modern operating system must respond to events that occur outside the normal sequential execution of a user program.
For example:
A program requests a service from the kernel.
A program performs an invalid memory access.
A timer expires.
A keyboard or network device completes an operation.
A hardware device raises an interrupt.
The CPU provides mechanisms that allow these events to transfer control from the currently executing code to a kernel-defined handler.
Three closely related terms are:
Trap
Exception
Interrupt
They are related, but they are not exactly the same.
A useful starting point is:
System call → intentional transition into the kernel
Exception → CPU detects a condition associated with instruction execution
Interrupt → external hardware/device requests CPU attention
A modern operating system normally separates execution into privilege levels.
Conceptually:
+----------------------------------+
| User Space |
| |
| Applications |
| Shell |
| Browser |
+----------------------------------+
|
| system call / exception
v
+----------------------------------+
| Kernel Space |
| |
| System calls |
| Device drivers |
| Memory management |
| Scheduler |
+----------------------------------+
On x86, the commonly used privilege levels are called rings.
Linux primarily uses:
Ring 3 → user mode
Ring 0 → kernel mode
A user program normally executes at a lower privilege level and cannot directly perform privileged operations.
The CPU provides mechanisms to transition into kernel mode in a controlled way.
A useful conceptual classification is:
CPU executing
|
+--------------+--------------+
| | |
v v v
System Call Exception Interrupt
(software) (CPU) (hardware)
| | |
+--------------+--------------+
|
v
Kernel Handler
These mechanisms can eventually cause the CPU to execute kernel code.
A system call is the mechanism through which a user-space program requests a service from the operating system kernel.
For example:
read(fd, buffer, size);
write(fd, buffer, size);
open(...);
fork();
The application normally does not directly execute the kernel implementation of these operations.
Instead, it uses a system-call interface.
Conceptually:
User program
|
| system call
v
CPU switches to kernel execution
|
v
System-call entry code
|
v
Kernel system-call implementation
|
v
Return to user program
Consider:
read(0, buf, 100);
The high-level flow is:
Application
|
| read()
v
C library / system-call interface
|
v
CPU system-call instruction
|
v
Kernel entry
|
v
System-call handler
|
v
TTY / file / socket / device subsystem
|
v
Return value
|
v
User program
The important point is that the CPU must perform a controlled transition from user mode to kernel mode.
A system call is intentionally generated by software.
For example:
Application
|
| "I need the kernel to read this file."
v
System-call instruction
|
v
Kernel
A hardware interrupt is generated because an external device or hardware component needs attention.
For example:
Network card
|
| packet arrived
v
Interrupt
|
v
CPU
|
v
Kernel interrupt handling
So:
System call → intentional software-generated entry
Hardware interrupt → externally generated hardware event
An exception is a CPU-generated event associated with execution of an instruction.
Examples include:
divide by zero
invalid instruction
page fault
general protection fault
breakpoint
debug exception
For example:
int x = 10 / 0;
The CPU detects the invalid operation.
Conceptually:
User program
|
v
CPU executes instruction
|
| detects fault
v
Exception
|
v
Kernel exception handler
The important distinction is that an exception is generated by the CPU as a consequence of executing an instruction.
A page fault is one of the most important exceptions to understand.
Suppose a program accesses memory:
Virtual address
|
v
MMU / page-table translation
|
v
Page not present / permission problem
|
v
Page-fault exception
|
v
CPU enters kernel
The CPU detects the problem during address translation or access permission checking.
Linux then examines the fault and decides what to do.
For example, the page might simply not have been loaded into physical memory yet.
The kernel can potentially resolve the fault and allow the instruction to be restarted.
Alternatively, the access may be invalid, in which case the process may receive a signal such as SIGSEGV.
An interrupt is generally an event generated independently of the instruction currently being executed.
Typical examples:
keyboard input
network packet arrival
timer interrupt
disk completion
USB transfer completion
For example:
CPU executing process A
|
| meanwhile...
|
Network card receives packet
|
v
Network card raises interrupt
|
v
CPU
|
v
Kernel interrupt handler
The process that was running did not explicitly ask for the interrupt.
The hardware generated it.
The terminology can vary between architectures and operating systems.
A useful conceptual distinction is:
Mechanism Typical source Example
System call Intentional software request read()
Exception CPU detects condition during instruction execution Page fault
Interrupt External hardware event Network packet
Trap Often refers to deliberate synchronous entry or a broader class of CPU events Breakpoint/system-call mechanisms depending on architecture
Although system calls, exceptions, and interrupts have different causes, they share an important idea:
The CPU needs to determine which kernel code should handle the event.
Conceptually:
Event occurs
|
v
Event number / vector
|
v
Interrupt/exception descriptor table
|
v
Handler address
|
v
Kernel handler
This is called vectoring.
An interrupt vector is essentially a number used to identify an interrupt or exception entry.
Conceptually:
Vector number
|
v
+------------------+
| Vector 0 |
| Vector 1 |
| Vector 2 |
| ... |
| Vector 14 |
| ... |
| Vector 128 |
| ... |
| Vector 255 |
+------------------+
The vector tells the CPU which entry in the interrupt/exception descriptor structure should be used.
It is important not to confuse several different things.
The important x86 structure is the:
IDT — Interrupt Descriptor Table
And the CPU has a special register called:
IDTR — Interrupt Descriptor Table Register
The IDTR tells the CPU where the IDT is located and its size.
Conceptually:
CPU
|
IDTR
|
v
+---------------+
| IDT |
| |
| Entry 0 |
| Entry 1 |
| Entry 2 |
| ... |
| Entry 255 |
+---------------+
So:
IDT = table
IDTR = CPU register pointing to the IDT
Vector = index identifying an entry
On modern x86 protected-mode operating systems such as Linux:
Interrupt Descriptor Table (IDT)
The IDT contains descriptors that tell the CPU how to enter the appropriate handler.
Conceptually:
Vector
|
v
+-----------------------------+
| IDT |
| |
| 0 → Divide Error handler |
| 1 → Debug handler |
| 2 → NMI handler |
| 3 → Breakpoint handler |
| 4 → Overflow handler |
| ... |
| 14 → Page Fault handler |
| ... |
| hardware IRQ vectors |
| ... |
+-----------------------------+
The exact assignment of interrupt vectors can vary, especially for hardware interrupts and Linux configuration.
An x86 IDT entry contains information that allows the CPU to transfer control to the appropriate handler.
Conceptually, an entry provides information such as:
handler address
code-segment information
gate type
privilege-related information
present/valid information
A simplified conceptual entry is:
+------------------------------+
| Handler address |
+------------------------------+
| Segment / code information |
+------------------------------+
| Gate type |
+------------------------------+
| Privilege information |
+------------------------------+
| Present bit |
+------------------------------+
The exact structure differs between x86 modes and descriptor types.
The CPU needs to know where the IDT is located.
The IDTR register provides this information.
Conceptually:
IDTR
|
+---- Base address → IDT in memory
|
+---- Limit → Size of IDT
So:
CPU
|
| IDTR
v
Memory
|
v
IDT
|
+--> Entry 0
+--> Entry 1
+--> Entry 2
...
+--> Entry 255
The operating system initializes the IDT and loads its address into the CPU's IDTR.
Suppose a page fault occurs.
Conceptually:
CPU executing user process
|
v
Memory access
|
v
MMU detects fault
|
v
Page-fault exception
|
v
Exception vector
|
v
IDT entry
|
v
Kernel page-fault entry code
|
v
Linux page-fault handling
The CPU uses the appropriate exception vector to locate the corresponding IDT entry.
The x86 CR2 register is particularly important for page faults.
When a page fault occurs, CR2 contains the linear address that caused the page fault.
For example:
Program accesses:
0x7ABC1234
|
v
Page fault occurs
|
v
CR2 = 0x7ABC1234
The kernel can examine CR2 to determine which virtual/linear address caused the fault.
This is extremely useful because the CPU needs to tell the operating system:
"This particular memory address caused the fault."
This distinction is important.
CR2 = faulting linear address
It does not contain the page-table address.
For example:
CR2
|
+----> 0x7ABC1234
|
+----> address that caused the page fault
The kernel uses this information when handling the fault.
The x86 CR3 register has a different purpose.
CR3 is associated with the current address-space/page-table hierarchy.
Conceptually:
CR3
|
v
Page-table hierarchy
|
v
Virtual address translation
|
v
Physical address
For example:
Virtual Address
|
v
Page tables
|
v
Physical Address
The page tables tell the MMU how virtual addresses map to physical memory.
CR3 identifies the relevant top-level page-table structure for the current address space, subject to the specific paging mode.
These registers are easy to confuse.
Register
Main purpose
CR2
Contains the linear address that caused the most recent page fault
CR3
Identifies the current paging structure/page-table hierarchy
A useful memory trick:
CR2 → "Where did the fault happen?"
CR3 → "Which address-space page tables are being used?"
Suppose a process accesses:
Virtual address = 0x7ABC1234
The CPU uses the page tables associated with CR3:
CR3
|
v
Page Tables
|
v
Translate 0x7ABC1234
|
v
Fault
The CPU records the faulting address in CR2:
CR2 = 0x7ABC1234
Then the CPU transfers control to the page-fault handler.
Conceptually:
Process
|
| access virtual address
v
MMU
|
| uses CR3
v
Page-table translation
|
| fault
v
CR2 ← faulting address
|
v
Page-fault exception
|
v
IDT
|
v
Kernel page-fault handler
The kernel can therefore use:
CR2 → which address faulted
and the process's address-space/page-table information associated with:
CR3 → how that address space is mapped
A page fault does not automatically mean that the program is invalid.
For example, a process may access a virtual page that is valid but not currently present in physical RAM.
The kernel can potentially handle the fault by bringing the page into memory and then allowing execution to continue.
Conceptually:
Program
|
v
Access virtual page
|
v
Page fault
|
v
Kernel
|
+---- page is valid but not resident
| |
| v
| Load page
| |
| v
| Update page tables
| |
| v
+------ restart instruction
However, if the access violates the process's memory permissions or refers to an invalid address, Linux may terminate the process or deliver an appropriate signal such as SIGSEGV.
Consider:
read(fd, buf, 100);
At the high level:
User program
|
v
C library / system-call interface
|
v
System-call instruction
|
v
CPU enters kernel
|
v
Kernel system-call entry
|
v
System-call dispatcher
|
v
Requested system-call implementation
|
v
Return to user mode
On modern x86-64 Linux, the commonly used system-call instruction is:
syscall
The older x86 mechanism included:
int 0x80
but modern 64-bit Linux normally uses syscall for the standard system-call entry path.
This is a terminology issue worth understanding.
The syscall instruction intentionally causes a transition into the kernel, but modern x86-64 Linux does not normally implement it as an ordinary hardware IRQ delivered through the IDT in the same way as a device interrupt.
So it is better to think:
syscall → dedicated CPU mechanism for entering the kernel
rather than:
syscall → ordinary hardware interrupt
Older mechanisms such as int 0x80 use the x86 software-interrupt mechanism and involve an IDT vector.
Historically, Linux on x86 used:
int $0x80
This causes a software interrupt/trap through an interrupt vector.
Conceptually:
Application
|
| int 0x80
v
Vector 0x80
|
v
IDT entry
|
v
Kernel system-call entry
Modern x86-64 Linux generally uses:
syscall
Conceptually:
Application
|
| syscall
v
Dedicated CPU system-call entry mechanism
|
v
Kernel system-call entry
Therefore, when teaching system calls, it is useful to distinguish the historical int 0x80 path from the modern syscall path.
Suppose a network card receives a packet.
Conceptually:
Network Card
|
| DMA
v
RAM
|
| interrupt
v
Interrupt Controller
|
v
CPU
|
v
Interrupt Vector
|
v
IDT
|
v
Kernel Interrupt Handler
|
v
Network Driver
The CPU was potentially executing a user process when this happened.
The CPU temporarily transfers control to the kernel's interrupt-handling path.
After the interrupt is handled, execution can eventually return to the interrupted context.
The device does not necessarily directly determine which CPU executes the interrupt handler.
Modern systems have interrupt controllers.
A simplified model is:
Device
|
v
Interrupt Controller
|
v
CPU
|
v
Interrupt Vector
|
v
Kernel Handler
On x86 systems, the APIC family of interrupt controllers is important.
Modern PCIe devices commonly use MSI/MSI-X, where the device generates a specially formatted interrupt message rather than relying solely on older physical interrupt lines.
The important conceptual idea is:
Device event → interrupt delivery mechanism → CPU → vector → kernel handler
The IDT contains entries corresponding to different vectors.
Some vectors are associated with CPU-defined exceptions.
Examples include:
Vector Example
0 Divide Error
1 Debug
2 Non-Maskable Interrupt
3 Breakpoint
4 Overflow
5 Bound Range Exceeded
6 Invalid Opcode
7 Device Not Available
8 Double Fault
10 Invalid TSS
11 Segment Not Present
12 Stack-Segment Fault
13 General Protection Fault
14 Page Fault
16 x87 Floating-Point Exception
17 Alignment Check
18 Machine Check
19 SIMD Floating-Point Exception
The exact architecture defines these exception vectors.
Other vectors are available for external interrupts and operating-system use.
The precise mapping of hardware interrupts is determined by the operating system and interrupt-controller configuration.
One particularly important entry is:
Vector 14 → Page Fault
Conceptually:
CPU
|
| page fault
v
Vector 14
|
v
IDT[14]
|
v
Page-fault handler
At the time of the fault:
CR2 = faulting linear address
The CPU also provides information describing the nature of the page fault, which the kernel uses to determine what happened.
For example, the fault information can distinguish situations such as:
read vs write
user vs supervisor access
page not present vs protection violation
Vector 3 corresponds to a breakpoint exception.
Debuggers can use breakpoint mechanisms to stop a program at a particular instruction.
Conceptually:
Program
|
v
Breakpoint
|
v
Vector 3
|
v
IDT[3]
|
v
Kernel debug handling
A debugger can then inspect the state of the process.
Suppose a program performs an invalid division operation.
Conceptually:
Program
|
v
Division instruction
|
v
CPU detects divide error
|
v
Vector 0
|
v
IDT[0]
|
v
Kernel exception handling
This is a CPU-generated exception.
One useful distinction is whether the event is synchronous with the currently executing instruction.
Usually synchronous.
The exception is associated with the instruction currently being executed.
Instruction
|
v
CPU detects problem
|
v
Exception
Examples:
Page fault
Divide error
Invalid opcode
Generally asynchronous.
The event can occur while the CPU is executing unrelated code.
CPU executing program
|
| meanwhile
|
v
Device generates interrupt
|
v
CPU handles interrupt
This is one of the most useful distinctions between exceptions and interrupts.
On x86 terminology, exceptions are often further classified into types such as:
Faults
Traps
Aborts
A fault is generally an exception that can potentially be corrected and the instruction restarted.
A page fault is the classic example.
Conceptually:
Instruction
|
v
Fault
|
v
Kernel fixes condition
|
v
Instruction restarted
A trap is generally reported after the instruction that caused it has executed, making it useful for debugging and controlled events.
A breakpoint is a classic example.
The exact terminology and behavior depend on the architecture.
When transferring control to a handler, the CPU must preserve enough information to eventually return to the interrupted execution context.
Conceptually, some processor state is saved:
Saved execution state
---------------------
Instruction pointer
Code segment / privilege information
Flags
Stack information
Other architecture-specific information
The exact state automatically saved by the CPU depends on the exception/interrupt mechanism and architecture.
Linux then saves additional processor state as needed.
Conceptually:
User process
|
v
Exception / interrupt
|
v
CPU saves entry state
|
v
Kernel entry code
|
v
More registers/state saved
|
v
Handler
Eventually the kernel needs to return to the interrupted user-space execution.
For x86, special return mechanisms are used.
For example, interrupt/exception returns use:
iret / iretq
System calls using the modern syscall mechanism use the corresponding system-call return mechanism:
sysret
The conceptual idea is:
Kernel handler
|
v
Restore saved state
|
v
CPU return instruction
|
v
User program continues
The process can continue from where it was interrupted or from an appropriate point determined by the kernel.
Suppose a user program accesses an address that is not currently mapped.
User Program
|
| memory access
v
CPU / MMU
|
| uses page tables associated with CR3
v
Address translation
|
| fault
v
CR2 ← faulting linear address
|
v
Page Fault Exception
|
v
Vector 14
|
v
IDT[14]
|
v
Kernel page-fault entry
|
v
Linux page-fault handler
|
+------------------------------+
| |
v v
Valid recoverable fault Invalid access
| |
v v
Fix mapping / load page Signal / process action
|
v
Return / restart
This single example brings together:
CR2
CR3
Exception
Vector
IDT
Kernel handler
Return to user space
Consider:
read(fd, buffer, 100);
The conceptual flow is:
User Program
|
| read()
v
C Library / System Call Interface
|
v
syscall instruction
|
v
CPU enters kernel
|
v
Kernel System-Call Entry
|
v
System-Call Number
|
v
System-Call Dispatcher
|
v
read() implementation
|
v
Object identified by fd
|
+-------------------+
| |
v v
TTY Socket/File
| |
v v
Data obtained from appropriate subsystem
|
v
Copy result to user buffer
|
v
Return to user mode
This illustrates again:
The same read() interface can reach completely different kernel subsystems.
Suppose a network card receives a packet.
Network Card
|
| DMA
v
Kernel/RAM buffer
|
| interrupt
v
Interrupt Controller
|
v
CPU
|
v
Interrupt Vector
|
v
IDT entry
|
v
Linux interrupt entry
|
v
Network driver
|
v
Network stack
|
v
Socket buffer
|
v
Application
Later, the application might execute:
read(socket_fd, buffer, size);
Now the application's read() accesses a socket, not a TTY.
The terminal keyboard path can now be described more precisely.
Keyboard
|
v
USB Host Controller
|
| DMA
v
Kernel memory
|
| interrupt
v
CPU
|
v
USB interrupt handling
|
v
USB/HID driver
|
v
Linux Input Subsystem
|
+------------------------+
| |
v v
TTY Wayland Compositor
| |
v v
TTY buffer Wayland protocol
| |
v v
read() Unix-domain socket
| |
v v
Shell/program GUI application
This shows why the same physical keyboard can ultimately feed completely different applications.
It is useful to remember these relationships:
Interrupt vector → identifies an interrupt/exception entry
IDT → contains the descriptors for those entries
IDTR → tells the x86 CPU where the IDT is located
CR2 → contains the linear address that caused a page fault
CR3 → identifies the current paging structure/page-table hierarchy
System call → controlled software entry into the kernel
Exception → CPU-generated event associated with instruction execution
Hardware interrupt → external/asynchronous hardware event
Several CPU registers and data structures are particularly important when studying system calls, interrupts, exceptions, and virtual memory on x86.
IDTR — Interrupt Descriptor Table Register
IDTR is a special CPU register that tells the processor where the Interrupt Descriptor Table (IDT) is located in memory and how large it is.
The relationship is:
IDTR → IDT → Interrupt/Exception Handler
The operating system initializes the IDT during kernel initialization and loads its address into the CPU's IDTR.
IDT — Interrupt Descriptor Table
The IDT is a table containing entries for different interrupts and CPU exceptions. Each entry provides the information required for the CPU to transfer control to the appropriate kernel handler.
For example:
Vector 0 → Divide Error
Vector 3 → Breakpoint
Vector 14 → Page Fault
Thus, when an exception or interrupt occurs, the vector identifies the relevant IDT entry, and that entry leads the CPU to the appropriate handler.
CR2 — Page-Fault Address
CR2 is used specifically during page-fault handling. When a page fault occurs, the CPU places the linear address that caused the fault into CR2.
For example, if a program accesses an address that causes a page fault:
Faulting address → CR2
The kernel can then examine CR2 to determine which address caused the problem.
CR3 — Page-Table Information
CR3 is associated with the page-table hierarchy used for the current address space. The CPU uses the paging structures identified through CR3 when translating virtual addresses into physical addresses.
Therefore, during a page fault:
CR2 tells the kernel which address caused the fault.
CR3 identifies the paging structure used to translate addresses for that address space.
RIP — Instruction Pointer
RIP contains the address of the instruction that the CPU is executing or is about to execute.
When an interrupt or exception transfers control to the kernel, the processor must preserve enough execution state to eventually return to the appropriate point in the interrupted program.
RIP is therefore an important part of the execution context.
RFLAGS — Processor Flags
RFLAGS contains processor status and control flags. These flags describe conditions resulting from operations and control aspects of CPU execution.
When the CPU enters an interrupt or exception handler, the relevant processor state, including flags, is preserved as part of the transition mechanism.
Putting the Important Registers Together
A useful way to remember their roles is:
IDTR → Where is the IDT?
IDT → Which handler corresponds to this vector?
CR2 → Which address caused the page fault?
CR3 → Which paging structure is being used?
RIP → Where was execution occurring?
RFLAGS → What was the processor state?
A useful conceptual diagram is:
CPU
|
+----------+----------+
| |
IDTR CR3
| |
v v
IDT Page Tables
| |
| |
Interrupt / Virtual
Exception Address
| |
v v
Handler Physical
Address
Page Fault
|
v
CR2
|
v
Faulting Address
A useful way to remember the entire topic is:
CPU
|
+-----------+-----------+
| | |
v v v
System Call Exception Interrupt
| | |
| | |
+-----------+-----------+
|
v
Kernel Entry
|
v
Appropriate
Handler
For x86 exceptions and interrupts:
Event
|
v
Vector
|
v
IDT
|
v
Handler
The CPU knows where the IDT is because:
IDTR → IDT
For a page fault:
CR3 → page-table hierarchy
CR2 → faulting linear address
For a system call:
Application
|
v
syscall
|
v
Kernel system-call entry
|
v
System-call dispatcher
|
v
Requested kernel service
For a hardware interrupt:
Device
|
v
Interrupt delivery
|
v
CPU
|
v
Vector
|
v
Kernel interrupt handler
A system call is a controlled request from user space to the kernel.
An exception is generally generated by the CPU because of the instruction being executed.
A hardware interrupt is generally generated by an external device or hardware component.
Exceptions are usually synchronous with instruction execution, while hardware interrupts are generally asynchronous.
A vector is a number used to identify an interrupt or exception entry.
On x86, the IDT (Interrupt Descriptor Table) contains descriptors for exception and interrupt handlers.
IDTR is the CPU register that tells the CPU where the IDT is located and how large it is.
The IDT contains many entries, including entries for CPU-defined exceptions and entries used for external interrupts.
Vector 14 corresponds to the x86 page-fault exception.
CR2 contains the linear address that caused a page fault.
CR3 identifies the paging structure used for the current address space.
Therefore, during a page fault, a useful mental model is:
CR3 → how the virtual address space is translated
CR2 → which virtual/linear address caused the fault
A page fault is not necessarily an error. The kernel can sometimes resolve it and restart the instruction.
A system call such as read() does not inherently mean "read from the TTY." The file descriptor determines whether the kernel accesses a TTY, socket, regular file, or another object.
Modern x86-64 Linux normally uses the syscall instruction for system calls.
The older int 0x80 mechanism used an x86 software-interrupt vector and is important historically, but it is not the normal system-call mechanism for modern 64-bit Linux applications.
Hardware interrupts can occur while a user program is executing. The CPU temporarily enters kernel interrupt handling and can later return to the interrupted execution context.
The general conceptual sequence is:
Event → Vector → Descriptor/IDT → Kernel entry → Handler → Return
For a page fault, this becomes:
Memory access → MMU detects fault → CR2 records faulting address → Page-fault vector → IDT → Kernel page-fault handler
For a hardware device, this becomes:
Device event → Interrupt delivery → CPU → Interrupt vector → IDT → Device interrupt handler
For a system call, modern x86-64 Linux follows a dedicated system-call entry mechanism:
Application → syscall → Kernel system-call entry → System-call dispatcher → Kernel service → Return
USER PROGRAM
|
+---------------+---------------+
| | |
| | |
system call exception hardware
| | interrupt
| | |
v v v
syscall CPU detects device raises
mechanism condition interrupt
| | |
+---------------+---------------+
|
v
KERNEL ENTRY
|
v
Vector / Entry Point
|
v
IDT on x86
↑
|
IDTR
|
v
Appropriate Handler
|
+-------------+-------------+
| | |
v v v
Syscall Exception Interrupt
Handler Handler Handler
| | |
| | |
| CR2 for |
| page fault |
| | |
| CR3 provides |
| paging context |
| | |
+-------------+-------------+
|
v
Kernel processing
|
v
Return to execution
The central idea is that traps, exceptions, interrupts, and system calls are mechanisms by which execution enters privileged kernel code. The CPU and operating system use vectors and handler tables to determine where to enter, while registers such as CR2 and CR3 provide essential information for memory-management exceptions such as page faults.