Interrupts are signals sent from hardware to the CPU, causing it to stop its current execution and jump to an interrupt handler in the kernel. Interrupts are asynchronous – they happen at any time, independent of the running thread.
An interrupt controller is a hardware component that manages interrupt signals from peripherals, prioritizing them and presenting them to the processor to enable efficient, non-polling, real-time multitasking. It acts as an intermediary, routing multiple device requests to the CPU and identifying the appropriate Interrupt Service Routine (ISR) via a vector table.
In short, the interrupt controller:
- Collects interrupts from devices (timer, keyboard, disk, network).
- Prioritises them.
- Delivers them to the appropriate CPU core.
Handling Interrupts
When an interrupt arrives:
- The CPU finishes the current instruction (or stops at a safe boundary).
- It flushes the pipeline (discards partially executed instructions).
- It saves a minimal state (often just the program counter and some flags) onto the kernel stack.
- It jumps to the interrupt handler in the kernel (entering kernel mode). This is effectively a forced context switch, but it’s not a full thread switch yet.
- The interrupt handler does its work (e.g., marks a thread as ready).
- After the handler finishes, the kernel may decide to call the scheduler. The kernel can then context switch to a different thread.
Crucially, the interrupt handler itself runs in kernel mode, but it is not a thread – it’s a small, fast piece of code that either does minimal work or delegates longer work to kernel threads (bottom halves / tasklets).
Task State Segment (TSS)
The Task State Segment (TSS) in x86 is a specialized hardware-supported structure that stores task context and manages stack switching during interrupts or privilege changes. While modern operating systems often bypass hardware task switching in favor of software-based approaches, the TSS remains crucial for handling privilege-level transitions and certain fault conditions.
The TSS itself is a privileged data structure in x86 architecture that stores the complete state of a task (registers, stack pointers, segment selectors, flags, etc.).
There are different types of task switching we may see:
- Hardware Task Switching (Protected Mode)
- Each task has its own TSS.
- On a switch (via
CALL,JMP, or interrupt gates), the CPU saves the current task’s state into its TSS and loads the new task’s state from another TSS. - This includes general-purpose registers, instruction pointer (EIP), flags, and stack pointers.
- Privilege-Level Stack Switching
- The TSS contains separate stack pointers (SS0:ESP0, SS1:ESP1, SS2:ESP2) for each privilege level.
- When an interrupt or system call moves execution from user mode (Ring 3) to kernel mode (Ring 0), the CPU automatically switches to the kernel stack defined in the TSS.
- This prevents user-mode code from corrupting kernel stacks and enforces isolation.
- Long Mode (x86-64)
- Hardware task switching is not supported.
- The TSS instead provides an Interrupt Stack Table (IST), which defines alternate stacks for specific exceptions (e.g., double faults).
- This ensures stability when the normal stack is corrupted.
The design of the TSS ensures the following:
- Automatic Context Management: Early OS designs relied on hardware to simplify multitasking. The TSS allowed the CPU to handle context switches without complex software routines.
- Security: By embedding privilege-level stack pointers, the TSS enforces strict separation between user and kernel stacks.
- Fault Handling: The IST in x86-64 ensures reliable recovery from catastrophic errors like double faults.
However, modern OSes (Linux, Windows) avoid hardware task switching due to inefficiency and lack of flexibility. Instead, they implement software-based context switching, but still rely on the TSS for:
- Privilege-level stack switching
- Handling double faults and critical exceptions