没志青年
发布于 2026-01-26 / 14 阅读
0

Linux 内核中断处理

关于 Linux 的中断:

  • 进入中断服务程序时,硬件会自动关闭当前 CPU 的可屏蔽中断 IRQ,但不会影响其他 CPU 的中断处理。FIQ 中断不受影响,但是 Linux 内核不使用 FIQ。虽然可以在 ISR 中手动调用 local_irq_enable() 重新开启本地中断,但内核出于防止重入、栈溢出和竞态的考虑,不鼓励中断嵌套。

  • Linux 中断号是内核维护的虚拟中断号,与设备树中描述的硬件物理中断号不同,二者通过 irq_domain机制映射。

  • GIC 支持中断优先级设置,但标准 Linux 内核(非实时版本)不会让高优先级中断抢占正在执行的低优先级 ISR。中断处理是按顺序完成的,即使优先级更高,也必须等待当前 ISR 结束(除非显式启用中断嵌套或应用 RT-PREEMPT 等实时补丁)。

  • 同一中断号的中断不会在同一 CPU 上嵌套执行,即使手动开启了全局中断。这是内核通过中断屏蔽和引用计数机制强制保证的,用于防止重入和系统不稳定。

  • i.MX6ULL 基于 ARM Cortex-A7,CPU 层面不支持 NMI 异常,SoC 也未提供专用的 NMI 引脚或中断源。

linux_kernel_wiki/文章/你真的理解Linux中断机制嘛.md at main · 0voice/linux_kernel_wiki · GitHub

local_irq_disable();

只管 IRQ,不会关 FIQ

上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作。也就是我们常说的硬中断,特点是快速执行。
下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行。也就是我们常说的软中断,特点是延迟执行。

(3)proc 文件系统:是一种内核空间和用户空间进行通信的机制,可以用来查看内核的数据结构,或者用来动态修改内核的配置。
/proc/softirqs 提供了软中断的运行情况;
/proc/interrupts 提供了硬中断的运行情况。

(4)硬中断:硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上。硬中断可以直接中断CPU,引起内核中相关的代码被触发。

(5)软中断:软中断仅与内核相关,由当前正在运行的进程所产生。 通常,软中断是一些对I/O的请求,这些请求会调用内核中可以调度I/O发生的程序。 软中断并不会直接中断CPU,也只有当前正在运行的代码(或进程)才会产生软中断。这种中断是一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求。 除了iowait(等待I/O的CPU使用率)升高,软中断(softirq)CPU使用率升高也是最常见的一种性能问题。

  • 上半部:中断处理函数,完成一些紧急且不耗时的操作。

  • 下半部:中断函数中没完成的一些耗时操作。

比如网卡,上半部:从网卡的缓存复制到内存中,下半部:对数据包进行校验和拆包等处理。

硬件中断

查看硬中断运行情况

查看软中断运行情况

线程中断

// 必须与 IRQF_ONESHOT 一起使用

中断在设备树中的表示

中断相关函数

(1)中断申请/释放:

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

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

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

参数:

  • irq:要申请的中断号,这个不是硬件手册上查到的号,而是内核中的号

  • flags:中断的触发方式或处理方式

    • IRQF_SHARED:共享中断

    • IRQF_ONESHOT:

    • IRQF_TRIGGER_RISING:上升沿触发

    • IRQF_TRIGGER_FALLING:下降沿触发

    • IRQF_TRIGGER_HIGH:高电平触发

    • IRQF_TRIGGER_LOW:低电平触发

  • name:中断名,可在 /proc/interrupts 查看

  • dev:设备 ID,当使用共享中断时使用设备 ID 来区分是哪个设备。

返回值:

  • 0:成功

  • 负数:失败

还有个线程中断不知道啥意思

(2)中断控制:

void enable_irq(unsigned int irq);

void disable_irq(unsigned int irq);

void disable_irq_nosync(unsigned int irq);

void synchronize_irq(unsigned int irq);

设置触发类型:

int irq_set_irq_type(unsigned int irq,
                     unsigned int flow_type);

中断释放

void free_irq(unsigned int irq, void *dev_id);
/*
功能:释放中断号
参数:
    irq:设备号
    dev_id:共享中断时用于区分那个设备一般强转成设备号,无共享中断时写NULL
*/

gpio 到中断

int gpiod_to_irq(const struct gpio_desc *desc);

中断处理函数:

typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);

static irqreturn_t my_irq(int irq, void *data)
{
    struct mydev *d = data;

    /* 处理中断 */

    return IRQ_HANDLED;
}

参数:

  • irq:中断号

  • dev_id:

返回值:

  • IRQ_HANDLED:处理完成

  • IRQ_NONE:共享中断

  • IRQ_WAKE_THREAD:

gpio 中断

desc = devm_gpiod_get(dev, "irq", GPIOD_IN);

irq = gpiod_to_irq(desc);

devm_request_threaded_irq(dev,
                          irq,
                          NULL,
                          my_thread,
                          IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                          "my_irq",
                          data);

下半部机制

下半部在执行的过程中,。中断被重新使能,所以如果有新的硬件中断产生,将会停止执行下半部,转为执行上半部。

硬件中断发生
    ↓
CPU 自动关中断(硬件行为)
    ↓
Linux 上半部(hardirq)执行 ← 执行期间中断保持禁用
    ↓
★ 上半部结束前,Linux 会开中断 ← 关键点!
    ↓
如果有下半部(softirq/tasklet/workqueue)→ 异步执行
    ↓
返回用户空间时完全恢复中断状态

软中断

1.软中断一般是“可延迟函数”的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进城切 换,软中断不能被自己打断,只能被硬件中断打断(上半部),可以并发的运行在多个 CPU 上。 所以软中断必须设计成可重入的函数,因此也需要自旋锁来保护其数据结构。

2.工作队列中的函数处在进程上下文中,它可以睡眠,也能被阻塞,能够在不同的进程间切 换。已完成不同的工作。 可延迟函数和工作队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在 运行的进程,工作队列的函数有内核进程执行,他不能访问用户空间地址

tasklet

workqueue

工作队列。