GoDm@'s Blog

linux中断系统基础使用

版权信息

warning

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。


在 Linux 内核开发中,中断(Interrupt) 是 CPU 与硬件通信的“急救热线”。如果让 CPU 不断轮询(Polling)硬件状态,效率极低;而中断机制允许硬件在需要时“打断” CPU,极大提升了系统的并发处理能力。

本文将帮助您理解 Linux 中断处理的顶半部/底半部机制,掌握核心 API。

1. “顶半部”和“底半部”

Linux 中断处理的一个黄金法则 是:快进快出

当硬件触发中断时,CPU 会暂停当前任务,跳转到中断处理函数。如果这个函数执行时间过长(例如读取大量数据或进行复杂计算),系统就会卡顿,甚至丢失其他重要的中断信号。

为了解决这个问题,Linux 将中断处理分为两个部分:

1.1. 顶半部 (Top Half / Hard IRQ)

1.2. 底半部 (Bottom Half)

1.2.1. 常见的底半部机制

选择哪种底半部机制取决于您的具体需求:

机制 运行上下文 是否允许睡眠 适用场景
Softirq (软中断) 中断上下文 极高并发需求(如网卡驱动)。一般驱动很少直接用。
Tasklet 中断上下文 (旧) 轻量级任务。但在新内核中逐渐被 Workqueue 取代。
Workqueue (工作队列) 进程上下文 (推荐) 需要耗时操作、需要睡眠、需要申请大量内存时。
Threaded IRQ 进程上下文 (推荐) 现代驱动的首选,将中断处理直接线程化。

2. IRQ 资源编号与设备树

在现代 Linux 内核中,绝大多数 SoC 的中断都不再通过固定的“linux 内部 IRQ 号”访问,而是通过 设备树(Device Tree)描述硬件 → 内核解析 → 统一抽象(irq_domain) → 转换成 Linux IRQ 号 的方式使用。

这一层抽象非常重要,因为:

2.1. 设备树中的中断描述

在 DTS 中,中断通常由两个属性提供:

  1. interrupt-parent:指定使用哪个中断控制器
  2. interrupts:描述设备的中断号和触发方式

例如:

mydev: my-device@0 {
    compatible = "gdm,mydev";
    reg = <0x01c20000 0x1000>;

    interrupt-parent = <&gic>;
    interrupts = <33 IRQ_TYPE_LEVEL_LOW>;
};

“interrupts” 单元的含义依赖于中断控制器定义的 #interrupt-cells 属性。

2.1.1. “#interrupt-cells” 的定义

每个中断控制器节点都定义自己的中断参数格式。

例如 ARM GIC:

gic: interrupt-controller@1e001000 {
    compatible = "arm,cortex-a9-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
};

表示这个中断控制器的 interrupts = <a b c>3 个单元
常见格式:

单元序号 含义
0 中断类型(0: SPI, 1: PPI)
1 硬件中断号
2 触发类型(edge/level)

2.1.2. GPIO 触发的中断

很多 GPIO 控制器也提供中断功能:

my_button {
    gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
    interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
    interrupt-parent = <&gpio1>;
};

注意:
gpiosinterrupts 是两回事。
gpios表示驱动该设备的 GPIO 引脚,
interrupts表示触发中断的中断号。

2.2. 内核如何解析设备树中的中断

中断解析的三层结构:

DTS → irq_domain → Linux IRQ number

解析过程:

  1. 解析 interrupt-parent 属性
    内核找到你的设备是由哪个中断控制器管理。

  2. 将 interrupts 中的原生硬件 IRQ 参数交给 irq_domain
    中断控制器在初始化时,会创建对应的 irq_domain 对象,用于映射:

硬件中断号 (hwirq)
        ↓
Linux 虚拟 IRQ(virq)
  1. 返回驱动可用的 Linux IRQ
    这个 IRQ 是你在 request_irq() 中使用的。

  2. 你在驱动中拿到的 IRQ 号 不是硬件 IRQ,而是 Linux 分配的虚拟 IRQ(virq)
    所以不能写死 IRQ 数字,必须从 DTS 获取。

2.3. 在驱动中获取设备树中的 IRQ 号

2.3.1. 常用方式:platform_get_irq()

最推荐、最通用:

int irq = platform_get_irq(pdev, 0);
if (irq < 0)
    return irq;

对应 DTS:

interrupts = <33 IRQ_TYPE_LEVEL_LOW>;

如果有多个中断:

interrupts = <33 IRQ_TYPE_LEVEL_LOW>,
             <34 IRQ_TYPE_EDGE_RISING>;

驱动:

irq0 = platform_get_irq(pdev, 0);
irq1 = platform_get_irq(pdev, 1);

2.3.2. 一般 GPIO → IRQ:gpiod_to_irq()

如果是 GPIO:

irq-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;

驱动:

gpio = gpiod_get(&pdev->dev, "irq", GPIOD_IN);
irq = gpiod_to_irq(gpio);

2.3.3. 旧方法(不推荐):irq_of_parse_and_map()

较低级 API,如今主要用于没有 platform_device 的情况。

irq = irq_of_parse_and_map(node, 0);

3. 常见API

3.1. request_irq():注册中断处理程序

int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev);

参数说明:

参数 说明
irq 中断号
handler 中断处理函数
flags 触发方式,如 IRQF_TRIGGER_FALLING
name /proc/interrupts 显示的名称
dev 传给 handler 的参数(通常设备结构体)

3.2. free_irq():释放中断

void free_irq(unsigned int irq, void *dev);

3.3. 中断处理函数(ISR)

irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct mydev *dev = dev_id;

    /* 处理硬件中断:读取状态寄存器、清中断标志… */
    ...

    return IRQ_HANDLED;
}

注意:

返回值:

3.4. enable_irq() / disable_irq()

用于在软件中暂时禁用/启用某个 IRQ:

disable_irq(irq);       // 会等待硬件中断执行完毕
disable_irq_nosync(irq); // 不等待
enable_irq(irq);

4. 底半部机制

4.1. tasklet(轻量底半部)

创建:

void my_tasklet_func(unsigned long data);  DECLARE_TASKLET(my_tasklet, my_tasklet_func, 0);

调度:

tasklet_schedule(&my_tasklet);

4.2. workqueue(更灵活)

相比 tasklet,它可以睡眠,也更能处理复杂任务。

创建工作:

static void my_work_func(struct work_struct *work)
{
    struct mydev *dev = container_of(work, struct mydev, work);
    /* 可以执行耗时任务 */
}

初始化:

INIT_WORK(&dev->work, my_work_func);

在中断里调度:

schedule_work(&dev->work);

这是最常用的底半部方式。

5. 中断处理中需要注意的事项

5.1. 不要睡眠

ISR 禁止使用:

5.2. 尽可能短

5.3. 触发方式匹配硬件

常见 flags:

6. Threaded IRQ(线程化中断)

Linux 支持把中断处理放到线程里(可睡眠)。适合驱动复杂逻辑。

request_threaded_irq(irq, NULL,
                     my_thread_fn,
                     IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
                     "mydev", dev);

thread_fn 可以睡眠,非常灵活:

static irqreturn_t my_thread_fn(int irq, void *dev_id)
{
    msleep(20);   // 可以
    return IRQ_HANDLED;
}

7. 查看中断情况

执行:

cat /proc/interrupts

可以看到:

例如:

33:      1023   GIC   edge   mydev_irq

共计约1.9k字。于2025/12/08首次发布,最后更新于2025/12/10。

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

AI辅助创作:本文部分内容由 ChatGPT 5 生成,最终版本由作者审核与修改。了解该AI模型

  1. 1. “顶半部”和“底半部”
    1. 1.1. 顶半部 (Top Half / Hard IRQ)
    2. 1.2. 底半部 (Bottom Half)
  2. 2. IRQ 资源编号与设备树
    1. 2.1. 设备树中的中断描述
    2. 2.2. 内核如何解析设备树中的中断
    3. 2.3. 在驱动中获取设备树中的 IRQ 号
  3. 3. 常见API
    1. 3.1. request_irq():注册中断处理程序
    2. 3.2. free_irq():释放中断
    3. 3.3. 中断处理函数(ISR)
    4. 3.4. enable_irq() / disable_irq()
  4. 4. 底半部机制
    1. 4.1. tasklet(轻量底半部)
    2. 4.2. workqueue(更灵活)
  5. 5. 中断处理中需要注意的事项
    1. 5.1. 不要睡眠
    2. 5.2. 尽可能短
    3. 5.3. 触发方式匹配硬件
  6. 6. Threaded IRQ(线程化中断)
  7. 7. 查看中断情况