On this page
Interrupt Subsystem
Coverage: irq_desc/irq_chip/irq_domain → Interrupt handling flow (top/bottom half) → softirq/tasklet/workqueue → threaded IRQ → MSI/MSI-X → APIC/IOMMU interrupt remapping Kernel version: 2.6 ~ 6.x
Overview
The interrupt subsystem is the part of the kernel most tightly coupled with hardware. When a network card receives a packet, a disk completes a DMA operation, or a timer expires, the hardware sends an interrupt signal to the CPU via the interrupt controller (APIC/GIC). The kernel's interrupt subsystem is responsible for: routing interrupts to the correct CPU, dispatching them to the corresponding device driver handler, and deferring time-consuming work to the bottom half.
The core abstractions of the modern interrupt subsystem are three layers:
- irq_chip: Hardware interrupt controller (IO-APIC, GIC, etc.)
- irq_domain: Mapping from hardware interrupt numbers to Linux IRQ numbers
- irq_desc: Descriptor for each Linux IRQ (handler, statistics, affinity)
Hardware Interrupt vs. Linux IRQ
Hardware Interrupt Number (hwirq):
Interrupt line number inside the interrupt controller
Different controllers can have the same hwirq
Linux IRQ Number (virq):
Unified virtual interrupt number in the kernel
Mapped via irq_domain: virq = irq_domain->map(hwirq)
The numbers seen in /proc/interrupts are Linux IRQ numbers
irq_desc
// include/linux/irqdesc.h
;
irqaction: Handler registered by the driver
// include/linux/interrupt.h
;
Interrupt Handling Flow
Hardware to Kernel
handle_irq_desc() Path
Shared Interrupts
IRQF_SHARED: Multiple devices share the same interrupt line
→ All registered handlers are called in sequence
→ Each handler checks "is this interrupt from my device?"
(Read device register; if no match → return IRQ_NONE)
→ If all handlers return IRQ_NONE → Spurious interrupt
+ Increment spurious count → If it reaches 100k times → Disable this interrupt
Bottom Half
The interrupt handler only does the most urgent tasks (acknowledge interrupt, mask device, copy data). Time-consuming operations (processing protocol stack, writing back cache) are handed over to the bottom half.
softirq
// kernel/softirq.c
// Predefined softirq types (determined at compile time):
;
// softirq runs in interrupt context (but with interrupts enabled)
// Triggered by irq_exit() check:
// if (in_interrupt()) return; // Not in nested hardware interrupt
// do_softirq() → __do_softirq() → Traverse softirq bitmap
// ksoftirqd: Per-CPU kernel thread
// When softirq processing is too heavy (MAX_SOFTIRQ_RESTART) → Hand over to ksoftirqd
// → Yield CPU to user processes
tasklet (Deprecated, replaced by threaded IRQ)
// kernel/softirq.c
// tasklet is based on softirq (HI_SOFTIRQ or TASKLET_SOFTIRQ)
// Tasklets of the same type do not run simultaneously (simplifies synchronization)
// ⚠️ Deprecated in kernels 2022+: New code should use threaded IRQ
;
Threaded IRQ: Currently Recommended Approach
// kernel/irq/manage.c
// Each interrupt can have its own kernel thread
// Handler returns IRQ_WAKE_THREAD → Kernel schedules threaded_fn to run
;
// handler: Runs in hardware interrupt context (does minimal work)
// thread_fn: Runs in an independent kernel thread (can sleep, can take mutex)
//
// Advantages:
// - Reduces interrupt disable time
// - Bottom half can sleep (take mutex, perform IO)
// - Can be managed by real-time scheduling policies
Workqueue: Heavier Bottom Half
// kernel/workqueue.c
// Workqueue runs in process context (kernel thread kworker)
// Suitable for: Work that requires sleeping or long execution times
// Main differences from tasklet/softirq:
// workqueue: Process context, can sleep
// tasklet/softirq: Interrupt context, cannot sleep
; // System workqueue
; // Dedicated workqueue
MSI / MSI-X
// kernel/irq/msi.c
// Message Signaled Interrupts
// Device writes directly to a memory address → Interrupt Controller (not via interrupt line)
// Advantages:
// - No need to share (each MSI has a unique address)
// - Higher performance (PCIe posted write vs. slow INTx level signal)
// - MSI-X allows devices to have multiple interrupt vectors (e.g., one per queue for NVMe)
// Enable MSI for PCI device:
;
→ Device allocates 16 MSI vectors
→ Returns Linux IRQ
Interrupt Affinity and Load Balancing
# View interrupt distribution
# Set interrupt affinity (which CPU handles which interrupt)
# irqbalance daemon automatically balances
# Based on interrupt counts and CPU load
# Certain interrupts (e.g., NVMe) are directly bound to NUMA-local CPUs
References and Further Reading
- Kernel Documentation:
Documentation/core-api/irq/,Documentation/PCI/msi-howto.rst - LWN: "The design of the interrupt subsystem", "Threaded interrupt handlers"
- Source Code:
kernel/irq/— Interrupt core (handle, manage, chip, domain, msi)kernel/softirq.c— softirq and taskletkernel/workqueue.c— workqueuearch/x86/kernel/irq.c— x86 interrupt entryarch/x86/kernel/apic/— APIC driver
Keywords: irq_desc, irq_chip, irq_domain, softirq, tasklet, threaded IRQ, workqueue, MSI/MSI-X, interrupt affinity