In computer systems, interrupts are signals that temporarily halt the CPU's current operations to attend to a specific task. Interrupts can be broadly categorized into several types based on their sources and purposes. Understanding these different types of interrupts is crucial for effective system and application programming.
1 Types of Interrupts
1.1 Hardware Interrupts
a. Maskable Interrupts (IRQs)
Maskable interrupts are hardware interrupts that can be ignored or "masked" by setting a specific bit in a mask register. These interrupts are usually generated by peripheral devices such as keyboards, mice, and network cards. The Programmable Interrupt Controller (PIC) or Advanced Programmable Interrupt Controller (APIC) manages these interrupts.
Examples:
- Keyboard interrupt
- Mouse interrupt
- Network card interrupt
b. Non-Maskable Interrupts (NMIs)
Non-maskable interrupts are high-priority hardware interrupts that cannot be ignored by the CPU. NMIs are typically used for critical events that require immediate attention, such as hardware failures.
Examples:
- Memory parity error
- Power failure
1.2 Software Interrupts
Software interrupts are triggered by software instructions rather than hardware signals. They are used for various purposes, including system calls and debugging.
a. INT Instructions
The INT
instruction is used to generate a software interrupt. This instruction is commonly used to invoke system calls in operating systems.
Example:
INT 0x80
in Linux for system calls
b. Trap Instructions
Trap instructions are used to create a controlled transition to an interrupt handler. They are often used for debugging purposes.
Examples:
INT3
for breakpoints in debugging
1.3 Processor Exceptions
Processor exceptions are interrupts generated by the CPU in response to errors or specific conditions during program execution. These exceptions indicate various error states or special conditions that the CPU encounters.
a. Faults
Faults are exceptions that occur during instruction execution and usually leave the program counter pointing to the instruction that caused the fault. The instruction can be retried after handling the fault.
Examples:
- Page fault (when a program accesses a non-resident page in memory)
- Divide-by-zero fault
b. Traps
Traps are exceptions that occur after the execution of the instruction that caused them. They are used to signal conditions that do not require the instruction to be re-executed.
Examples:
- System calls
- Breakpoint traps
c. Aborts
Aborts are serious exceptions that indicate critical errors, typically related to hardware failures. Aborts do not allow the program to continue execution.
Examples:
- Double fault (when a second exception occurs while trying to handle the first exception)
- Machine check
1.4 Inter-Processor Interrupts (IPIs)
Inter-Processor Interrupts are used in multiprocessor systems to send interrupts from one processor to another. They are commonly used for tasks such as signaling the need for task scheduling, updating system state, or handling certain types of system calls.
Examples:
- Task rescheduling signals
- TLB (Translation Lookaside Buffer) shootdown requests
2 Steps to Implement the Interrupts
- Define the IDT and IDT pointer Structure
- Initialize IDT entries
- Write ISR
- Register the ISR with the IDT
- Load the IDT
- Enable Interrupts
- Configure the PIC (for handling hardware interrupts)
3 Step1 - Define the IDT and IDT Pointer Structure
First, define the IDT entry structure and the IDT pointer. This structure should include the necessary fields to store the ISR address and associated metadata.
3.1 What is the IDT in x86_64?
The IDT in x86_64 is a table that the CPU uses to determine how to handle interrupts and exceptions. It contains 256 entries, each corresponding to a different interrupt vector. Each entry in the IDT points to an interrupt service routine (ISR) or an exception handler.
3.1 Structure of an IDT Entry
Each entry in the IDT is 16 bytes long and has the following structure in x86_64:
- Offset (Bits 0-15): Lower part of the ISR address.
- Selector (Bits 16-31): Segment selector.
- IST (Bits 32-34): Interrupt Stack Table offset (not commonly used).
- Type and Attributes (Bits 35-47): Type, attributes, and privilege level.
- Offset (Bits 48-63): Middle part of the ISR address.
- Offset (Bits 64-127): Higher part of the ISR address.
- Zero (Bits 128-159): Reserved, must be zero.
IDT Entry Layout
Bits 0-15 : Offset bits 0-15
Bits 16-31 : Segment Selector
Bits 32-34 : IST (Interrupt Stack Table)
Bits 35-39 : Type and Attributes
Bits 40-47 : Type and Attributes continued
Bits 48-63 : Offset bits 16-31
Bits 64-95 : Offset bits 32-63
Bits 96-127 : Reserved, must be zero
IDT Structure Definition:
// IDT entry structure for x86_64
struct idt_entry {
uint16_t offset_low; // Offset bits 0-15
uint16_t selector; // Selector
uint8_t ist; // Interrupt Stack Table offset
uint8_t type_attr; // Type and attributes
uint16_t offset_mid; // Offset bits 16-31
uint32_t offset_high; // Offset bits 32-63
uint32_t zero; // Reserved, set to zero
} __attribute__((packed));
IDT Pointer Definition:
// IDT pointer structure
struct idt_ptr {
uint16_t limit; // Size of the IDT - 1
uint64_t base; // Base address of the IDT
} __attribute__((packed));
// Define an IDT with 256 entries
#define IDT_ENTRIES 256
struct idt_entry idt[IDT_ENTRIES];
struct idt_ptr idtp;
4 Step 2 - Initialize IDT Entries
Initialize each entry in the IDT with the address of the ISR, the segment selector, and the appropriate attributes. For this, create a function to set up each entry in the IDT with the ISR address, segment selector, and attributes.
void set_idt_entry(int num, uint64_t base, uint16_t selector, uint8_t flags) {
idt[num].offset_low = base & 0xFFFF;
idt[num].selector = selector;
idt[num].ist = 0;
idt[num].type_attr = flags;
idt[num].offset_mid = (base >> 16) & 0xFFFF;
idt[num].offset_high = (base >> 32) & 0xFFFFFFFF;
idt[num].zero = 0;
}
5 Step 3: Write ISRs
ISRs must be written for each interrupt and exception. Here's an example of a simple ISR for a keyboard interrupt in x86_64 assembly.
global isr_keyboard
section .text
isr_keyboard:
cli ; Clear interrupts
push rax ; Save registers
push rbx
push rcx
push rdx
; ISR code to handle keyboard interrupt
pop rdx ; Restore registers
pop rcx
pop rbx
pop rax
sti ; Set interrupts
iretq ; Return from interrupt
6 Step 4: Register the ISR with the IDT
Register the ISR with the IDT using the set_idt_entry
function.
extern void isr_keyboard(); // Declare the ISR function
void init_idt() {
set_idt_entry(33, (uint64_t)isr_keyboard, 0x08, 0x8E); // Keyboard interrupt vector is 33
load_idt();
}
7 Step 5: Load the IDT
Load the IDT using the lidt
instruction.
void load_idt() {
idtp.limit = (sizeof(struct idt_entry) * 256) - 1;
idtp.base = (uint64_t)&idt;
__asm__ __volatile__("lidt %0" : : "m" (idtp));
}
8 Step 6: Enable Interrupts
Finally, enable interrupts so that the CPU can start handling them.
sti ; Set the interrupt flag