Fundamentals 30 min read

How Linux Handles Hardware Interrupts: From Signal to Task Recovery

This article explains the complete Linux interrupt mechanism—from hardware signal generation, through the programmable interrupt controller and CPU response, to the split top‑half and bottom‑half handling and driver registration—providing developers and operators with a clear understanding of how microsecond‑level hardware events are processed efficiently.

Deepin Linux
Deepin Linux
Deepin Linux
How Linux Handles Hardware Interrupts: From Signal to Task Recovery

When you type ls, copy a 10 GB file to an SSD, or a network card receives a packet, the system replies within milliseconds; behind this instant response is a microsecond‑level emergency call between hardware and the kernel, realized by the hardware interrupt reception and response mechanism.

Many Linux developers only know that an interrupt makes the CPU pause its current work, but the reason a network‑card interrupt can pre‑empt a running application lies in the full chain from signal triggering to task completion, which we will explain without diving into obscure kernel source.

1. Interrupt Basics

1.1 What Is an Interrupt?

An interrupt is a “urgent signal” sent by hardware or software to the CPU, requiring the CPU to suspend the current task and handle the event, similar to a phone ringing while you are reading a book.

Interrupts are mainly divided into hardware interrupts and software interrupts.

Hardware interrupt: triggered by external devices such as keyboard input, disk I/O completion, or network packet arrival. For example, pressing a key causes the keyboard controller to send an interrupt to the CPU.

Software interrupt: triggered by software, usually system calls or timer interrupts. For example, a read/write system call generates a software interrupt to request file I/O services.

1.2 Interrupts vs. Polling: The Efficiency Divide

Polling makes the CPU periodically check device status, wasting CPU cycles when the device is idle; interrupts let the device actively notify the CPU, greatly improving efficiency.

Thus, interrupts are an asynchronous mechanism, while polling is synchronous, and interrupts significantly increase system resource utilization.

1.3 Linux Interrupt Classification

Linux distinguishes hardware interrupts and software interrupts.

Hardware interrupts are asynchronous, have high priority, and immediately pre‑empt the CPU (e.g., a network card notifying the CPU of new data). They are widely used in real‑time control systems.

Software interrupts are used for system calls, task scheduling, and timer handling; they have lower priority and are invoked by the kernel (e.g., int 0x80 or syscall on x86).

1.4 Programmable Interrupt Controllers (PIC, APIC)

The interrupt controller bridges IRQ lines and CPU cores; each CPU has its own controller. The controller can mask specific IRQs via registers.

When an external interrupt occurs (e.g., keyboard IRQ1), the controller and CPU interact as follows:

IRQ1 arrives; the controller checks the mask register and may ignore it.

If not masked, the controller sets the request bit for IRQ1.

The controller raises the INT line to inform the CPU.

The CPU checks the INTR pin after each instruction and sees it high.

The CPU verifies the IF flag in EFLAGS is 1, then acknowledges with INTA.

The controller sends the IRQ1 vector number on the data bus; the CPU reads it.

If an EOI signal is needed, the CPU sends it; otherwise the controller lowers INT and clears the request bit.

In the Linux kernel, the struct irq_chip describes a programmable interrupt controller:

struct irq_chip {
    const char    *name;
    unsigned int   (*irq_startup)(struct irq_data *data);
    void           (*irq_shutdown)(struct irq_data *data);
    void           (*irq_enable)(struct irq_data *data);
    void           (*irq_disable)(struct irq_data *data);
    void           (*irq_ack)(struct irq_data *data);
    void           (*irq_mask)(struct irq_data *data);
    void           (*irq_mask_ack)(struct irq_data *data);
    void           (*irq_unmask)(struct irq_data *data);
    void           (*irq_eoi)(struct irq_data *data);
    int            (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
    int            (*irq_retrigger)(struct irq_data *data);
    int            (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
    int            (*irq_set_wake)(struct irq_data *data, unsigned int on);
    void           (*irq_bus_lock)(struct irq_data *data);
    void           (*irq_bus_sync_unlock)(struct irq_data *data);
    void           (*irq_cpu_online)(struct irq_data *data);
    void           (*irq_cpu_offline)(struct irq_data *data);
    void           (*irq_suspend)(struct irq_data *data);
    void           (*irq_resume)(struct irq_data *data);
    void           (*irq_pm_shutdown)(struct irq_data *data);
    void           (*irq_calc_mask)(struct irq_data *data);
    void           (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
    int            (*irq_request_resources)(struct irq_data *data);
    void           (*irq_release_resources)(struct irq_data *data);
    unsigned long   flags;
};

2. Full Interrupt Handling Process: From Signal to Task Recovery

2.1 Hardware Trigger: Birth of the Interrupt Signal

When a device needs CPU attention, it sends an interrupt request. For example, a key press causes the keyboard controller to generate an interrupt signal, which is first sent to the interrupt controller.

Early PCs used the 8259A PIC (max 8 IRQs); modern multi‑core CPUs use the Advanced Programmable Interrupt Controller (APIC), which can handle many IRQs and supports load balancing. ARM systems often use the Generic Interrupt Controller (GIC).

The controller collects IRQs, performs priority arbitration, and forwards the highest‑priority request to the CPU.

2.2 CPU Response: Saving Context and Vector Lookup

Upon receiving an interrupt, the CPU pauses the current task and saves registers and the program counter onto the stack.

Then the CPU looks up the interrupt vector table (IVT) using the interrupt number to find the address of the corresponding handler.

2.3 Interrupt Handling: Split Design

Linux splits handling into a top‑half (hard interrupt) and a bottom‑half (soft interrupt/tasklet).

Top‑half runs in interrupt context, must finish quickly (microseconds), clears the interrupt flag and reads device status.

Bottom‑half runs in process context, can sleep, and performs non‑urgent work such as packet parsing or disk I/O.

For a network card, the top‑half copies data to a kernel buffer and triggers a soft interrupt; the bottom‑half later processes the packet through the network stack.

3. Core Components of Interrupt Management

3.1 Interrupt Vector Table: The Navigation Map

The vector table maps interrupt numbers to handler addresses. On x86 it is stored in the IDT (Interrupt Descriptor Table). The request_irq function registers a handler by allocating an entry in this table.

3.2 Interrupt Controller: The Scheduling Center

The controller collects IRQs, prioritizes them, and sends the highest‑priority request to the CPU. Early systems used the PIC (e.g., 8259A) with fixed priority; modern systems use APIC, which supports dynamic priority and load balancing across cores.

In a 4‑core system, APIC can distribute frequent network‑card interrupts to the least‑loaded core.

3.3 Interrupt Context: Special Kernel Execution Environment

Interrupt context lacks a process control block and cannot call blocking functions or perform page‑out operations; it must complete quickly to avoid degrading system responsiveness.

4. Registering and Optimizing Interrupt Handlers

4.1 Interrupt Registration in Driver Development

Example device‑tree node for a button:

button {
    compatible = "my_button";
    interrupt-parent = <&gpio0>;
    interrupts = <10 IRQ_TYPE_EDGE_FALLING>; // IRQ 10, falling edge
    gpios = <&gpio0 10 GPIO_ACTIVE_LOW>;
};

Driver code to register the handler:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

static irqreturn_t button_interrupt(int irq, void *dev_id) {
    printk(KERN_INFO "Button interrupt received
");
    return IRQ_HANDLED;
}

static int __init button_driver_init(void) {
    int irq;
    struct device_node *np = of_find_node_by_path("/button");
    if (!np) {
        printk(KERN_ERR "Button device node not found
");
        return -ENODEV;
    }
    irq = irq_of_parse_and_map(np, 0);
    if (!irq) {
        printk(KERN_ERR "Failed to get IRQ number
");
        of_node_put(np);
        return -EINVAL;
    }
    if (request_irq(irq, button_interrupt, IRQF_TRIGGER_FALLING, "my_button", NULL)) {
        printk(KERN_ERR "Failed to request IRQ %d
", irq);
        of_node_put(np);
        return -EFAULT;
    }
    of_node_put(np);
    return 0;
}

static void __exit button_driver_exit(void) {
    free_irq(irq, NULL);
}

module_init(button_driver_init);
module_exit(button_driver_exit);
MODULE_LICENSE("GPL");

The first argument to request_irq is the IRQ number parsed from the device tree; the second is the handler; IRQF_TRIGGER_FALLING sets edge‑falling trigger; the name "my_button" appears in /proc/interrupts.

4.2 Performance Optimization Strategies

Shorten top‑half: keep it minimal, defer heavy work to the bottom‑half.

Interrupt affinity: bind frequent IRQs to a specific CPU core using /proc/irq/<num>/smp_affinity or taskset to reduce cache‑miss penalties.

Interrupt sharing: use IRQF_SHARED and provide a unique dev_id when multiple devices share an IRQ.

5. Common Issues and Debugging Tools

5.1 Interrupt Nesting in Modern Linux

Older kernels allowed nested interrupts (fast vs. slow). Modern kernels disable nesting by default to simplify handling and improve stability, though real‑time patches can re‑enable it.

5.2 Viewing Interrupt Statistics

Hard interrupt stats: cat /proc/interrupts shows per‑CPU counts, types, and device names.

CPU0       CPU1
0:         123       0   IO-APIC-edge   timer
1:         5         3   IO-APIC-edge   i8042
6:         0         0   IO-APIC-edge   floppy
8:         1         0   IO-APIC-edge   rtc0

Soft interrupt stats: cat /proc/softirqs lists types such as TIMER, NET_RX, etc.

CPU0       CPU1
HI:        0         0
TIMER:     123456   123450
NET_TX:    123       125
NET_RX:    54321    54325
BLOCK:     0         0

5.3 Tools for Analyzing Interrupts

sar -u

shows CPU usage, including %hi (hard‑interrupt) and %si (soft‑interrupt) percentages. sar -I ALL displays detailed interrupt information. tcpdump can capture network traffic that may correlate with network‑card interrupts.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

KernelLinuxInterruptsdriver development
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.