Interrupt Handling
When a network card receives a packet or a disk finishes a write, it signals the CPU via a hardware interrupt. The CPU stops what it's doing, saves state, and runs the interrupt handler. But interrupt handlers must be fast — they run with interrupts disabled. Linux splits interrupt handling into fast "top halves" and deferred "bottom halves" to solve this.
Hardware Interrupts (IRQs)
# View IRQ assignments:
cat /proc/interrupts
# CPU0 CPU1 CPU2 CPU3
# 1: 1234 0 0 0 IR-IO-APIC 1-edge i8042 (keyboard)
# 9: 0 0 0 567 IR-IO-APIC 9-fasteoi ACPI
# 40: 0 12345 0 0 PCI-MSI 40-edge xhci_hcd (USB)
# 42: 987654 876543 765432 654321 PCI-MSI 42-edge eth0
# Columns: IRQ number, count per CPU, type, name
# Top IRQs (most active):
watch -n1 "cat /proc/interrupts | sort -k2 -rn | head -10"
# IRQ affinity — which CPUs handle which IRQs:
cat /proc/irq/42/smp_affinity
# f (hex bitmask: 0xf = CPUs 0-3 all handle IRQ 42)
# Pin IRQ to specific CPU (reduce interruption of real work):
echo 2 > /proc/irq/42/smp_affinity # CPU 1 only (0x2 = CPU1)
Top Half — The Interrupt Handler
Why can't interrupt handlers do much work?
Interrupt handlers run with interrupts disabled on that CPU. During the handler, no other interrupt can be processed (including timer interrupts that drive scheduling). If the handler takes too long, the system becomes unresponsive. Latency increases, packets get dropped, keystrokes are lost. The rule: do only the minimum in the top half — acknowledge the interrupt, schedule deferred work, return.
Top half (interrupt handler) — must be fast:
- Acknowledge the interrupt to the device
- Read data from device registers into DMA buffer
- Wake up a waiting process if needed
- Schedule a softirq/tasklet/workqueue for the rest
- Return (re-enable interrupts)
Bottom half (deferred) — can take more time:
- Process the DMA'd data
- Parse network packets
- Update statistics
- Wake up user processes that were blocking
Bottom Halves — softirq, tasklet, workqueue
| softirq | tasklet | workqueue | |
|---|---|---|---|
| Context | Interrupt context | Interrupt context (built on softirq) | Process context (kernel thread) |
| Concurrency | Same softirq on multiple CPUs simultaneously | Only one instance at a time | Can sleep, can block |
| Can sleep? | No | No | Yes |
| Number | Fixed (32 max) | Dynamic | Dynamic |
| Used for | Networking (NET_TX, NET_RX), block I/O | General deferred work | Work needing sleeping/locking |
# See softirq statistics:
cat /proc/softirqs
# CPU0 CPU1 CPU2 CPU3
# HI: 0 0 0 0
# TIMER: 1234567 987654 876543 765432
# NET_TX: 12345 8765 7654 6543
# NET_RX: 2345678 1876543 1765432 1654321
# BLOCK: 345678 276543 265432 254321
# SCHED: 456789 387654 376543 365432
# NET_RX high = receiving lots of network traffic
# BLOCK high = lots of disk I/O completions
# TIMER high = timer-driven work (normal)
IRQ Balancing
# irqbalance daemon distributes IRQs across CPUs:
systemctl status irqbalance
# Without irqbalance: all IRQs go to CPU 0 by default
# This creates CPU 0 as a bottleneck on high-traffic systems
# For network performance: manual IRQ affinity
# One IRQ per CPU, each CPU handles one NIC queue
# NIC must support multi-queue (RSS - Receive Side Scaling)
ethtool -l eth0
# Current hardware settings for eth0:
# Combined: 4 ← 4 queues (one per CPU)
# Manually assign:
echo 1 > /proc/irq/40/smp_affinity # queue 0 → CPU 0
echo 2 > /proc/irq/41/smp_affinity # queue 1 → CPU 1
echo 4 > /proc/irq/42/smp_affinity # queue 2 → CPU 2
echo 8 > /proc/irq/43/smp_affinity # queue 3 → CPU 3
Frequently Asked Questions
What will I learn here?
This page covers the core concepts and techniques you need to understand the topic and progress confidently to the next lesson.
How should I use this page?
Start with the overview, then follow the section links to deepen your understanding. Use the table of contents on the right to jump to specific sections.
What should I read next?
Use the navigation below to continue to the next lesson or explore related topics.