Linux generic IRQ 框架详解:中断描述符、流控处理器与 irq_chip 三层抽象
2026/9/15 2:14:26 网站建设 项目流程

Linux generic IRQ 框架详解:中断描述符、流控处理器与 irq_chip 三层抽象

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

导读

本文以内核文档 Documentation/core-api/genericirq.rst 为骨架,结合本仓库kernel/irq/目录下的真实源码实现,系统讲解 Linux 通用中断处理层(generic IRQ handling)的设计与工作原理。内容覆盖中断子系统从“设备驱动高层 API”到“高层 IRQ 流控处理器(flow handler)”再到“芯片级硬件封装(irq_chip)”的三层抽象、各预置流控处理器的控制流细节、irq_chip 原始接口语义以及 Generic Interrupt Chip 的复用方法。读完本文,你将能够理解中断在 Linux 内核中的完整分发路径,并具备为自有架构实现中断子系统或为控制器编写 irq_chip 的实战能力。

引言:为什么需要通用中断处理层

Linux 通用中断处理层(generic IRQ handling layer)的设计目标,是为设备驱动提供一套完整且与硬件无关的中断处理抽象。它能够适配不同类型的中断控制器硬件,而设备驱动只需调用通用 API 来请求(request)、使能(enable)、禁用(disable)和释放(free)中断即可,完全不需要了解底层中断控制器的细节,因此驱动代码可以不加修改地在不同平台上运行。

从源码结构上看,这一层主要由 kernel/irq/ 目录下的这些核心文件承载:

  • kernel/irq/manage.c:高层驱动 API(request_irqfree_irqenable_irqdisable_irq等)的实现;
  • kernel/irq/chip.c:预置的 IRQ 流控处理器(handle_level_irqhandle_edge_irq等)与芯片回调调度;
  • kernel/irq/irqdesc.c:中断描述符(irq_desc)的管理;
  • kernel/irq/handle.c:handle_bad_irq等杂项处理;
  • kernel/irq/generic-chip.c:可配置的通用中断芯片实现。

原文档面向的对象,是那些希望借助通用 IRQ 层为自己的架构实现中断子系统的开发者。本文同样以此为主线展开。

设计动机:从 __do_IRQ() 超级处理器到分类流控

在通用 IRQ 层出现之前,Linux 的中断处理采用__do_IRQ()超级处理器(super-handler)模型:它试图用一套逻辑应对所有类型的中断。早期 ARM 中断子系统的实现者 Russell King 在 Linux 2.5/2.6 时代识别出三类典型处理器并构建了一套相当通用的处理集合:

  • Level type(电平触发型)
  • Edge type(边沿触发型)
  • Simple type(简单型)

在后续实现过程中,又识别出另一种类型:

  • Fast EOI type(快速 EOI 型)

进入 SMP 世界后,__do_IRQ()超级处理器中还识别出一种新类型:

  • Per CPU type(每 CPU 型)

将高层 IRQ 处理器按类型拆分,使得每个特定中断类型的处理流程都能被独立优化:特定代码路径的复杂度降低,处理路径也变得更短、更高效。

__do_IRQ()通用实现还存在一个结构性缺陷:它依赖hw_interrupt_type结构及其->ack->end等回调来区分流控,导致流控逻辑与底层硬件逻辑混在一起,并带来大量不必要的代码重复。原文档举了 i386 的例子:ioapic_level_irqioapic_edge_irq两个 IRQ 类型共享了大量底层细节,却因流控不同而不得不各自实现一份。

更自然的抽象方式,是把"IRQ 流控(irq flow)""芯片细节(chip details)"彻底分离。对多个架构的 IRQ 子系统实现分析后可以发现:绝大多数架构都能复用一套通用的 "irq flow" 方法,只需自行补充芯片级代码;对于需要在 IRQ 流控本身做特殊处理(quirks)而芯片细节无需改动(子)架构,这种分离也让 IRQ 子系统设计更加透明。

每个中断描述符都被分配自己的高层流控处理器,通常就是预置的通用实现之一。这种设计让"解复用处理器(demultiplexing handlers)"的实现变得简单——这在各架构的嵌入式平台上很常见(一个物理中断线解复用出多个虚拟中断)。

另外,为了平滑过渡、避免破坏已有实现,__do_IRQ()超级处理器在很长一段时间内仍然可用,与新模式并存(duality)。不过文档明确指出它已被弃用(deprecated)多年,处于即将被移除的状态——事实上在当前版本源码中该入口已不再存在(详见下文"__do_IRQ 入口点"一节)。

三层抽象架构

中断代码中存在三个主要抽象层级:

  1. 高层驱动 API(High-level driver API)
  2. 高层 IRQ 流控处理器(High-level IRQ flow handlers)
  3. 芯片级硬件封装(Chip-level hardware encapsulation)

中断控制流:irq_desc 描述符与 handle_irq 入口

每个中断都由一个中断描述符结构irq_desc描述,中断通过一个unsigned int数值引用,该数值用于在描述符结构数组中选中对应的描述符。描述符中包含状态信息,以及分配给该中断的流控方法指针handle_irq)和中断芯片结构指针irq_chip)。

每当一个中断触发时,底层架构代码通过调用desc->handle_irq()进入通用中断代码。这个高层 IRQ 处理函数只使用desc->irq_data.chip指向的芯片描述符所提供的原语。struct irq_datastruct irq_chip的定义位于 include/linux/irq.h,其中irq_desc的定义同样在该头文件中。

在 kernel/irq/irqdesc.c 中可以看到,新分配的描述符默认把handle_irq设置为handle_bad_irq(第 133 行附近),即"没有正确初始化流控处理器之前,任何中断都按坏中断处理",这是非常稳妥的安全默认值。

高层驱动 API

文档列出的高层驱动 API 如下:

  • request_irq()
  • request_threaded_irq()
  • free_irq()
  • disable_irq()
  • enable_irq()
  • disable_irq_nosync()(仅 SMP)
  • synchronize_irq()(仅 SMP)
  • irq_set_irq_type()
  • irq_set_irq_wake()
  • irq_set_handler_data()
  • irq_set_chip()
  • irq_set_chip_data()

这些函数主要实现在 kernel/irq/manage.c 与 kernel/irq/chip.c 中。以最常用的request_irq()为例:它定义在 include/linux/interrupt.h(第 173 行附近),是一个内联包装函数,最终调用request_threaded_irq(irq, handler, NULL, flags, name, dev)——即"只有主处理器、没有线程处理器"的线程化请求特例。

request_threaded_irq()的核心实现(kernel/irq/manage.c 第 2124 行起)揭示了几个重要约定:

  • 传入IRQ_NOTCONNECTED会返回-ENOTCONN
  • 共享中断(IRQF_SHARED)必须传入非 NULL 的dev_id,否则返回-EINVAL——因为释放中断时必须靠dev_id区分是哪个设备在占用该线;
  • 共享中断不允许与IRQF_NO_AUTOEN组合,IRQF_COND_SUSPEND只能用于共享中断且不能与IRQF_NO_SUSPEND同时设置;
  • 如果handler为 NULL 而thread_fn非 NULL,内核会安装默认主处理器irq_default_primary_handler
  • 分配struct irqaction记录 handler、thread_fn、flags、name 与 dev_id,再经__setup_irq()完成与描述符的绑定。

request_threaded_irq()的参数语义(摘自函数注释,可在 kernel/irq/manage.c 第 2085 行起查看):

  • irq:要分配的中断线;
  • handler:中断发生时被调用的主处理器,对线程化中断而言是主处理器。若为 NULL 且thread_fn非空,则安装默认主处理器;
  • thread_fn:从中断处理线程中调用的函数;若为 NULL 则不创建中断线程;
  • irqflags:中断类型标志,包括IRQF_SHARED(共享中断)、IRQF_TRIGGER_*(指定有效沿或电平)、IRQF_ONESHOT(线程执行期间保持中断线被掩蔽)等;
  • devname:声明设备的 ASCII 名称;
  • dev_id:回传给处理函数的 cookie,通常使用设备数据结构地址。

线程化中断采用"拆分处理器"设计:handler仍在硬中断上下文中执行,负责检查中断是否来自本设备,若是则在设备侧禁用中断并返回IRQ_WAKE_THREAD唤醒处理线程去执行thread_fn。这种拆分对于支持共享中断是必要的。如果中断是共享的,释放时必须传非 NULL 的dev_id

高层 IRQ 流控处理器(flow handlers)

通用层提供了一组预定义的 irq-flow 方法:

  • handle_level_irq()
  • handle_edge_irq()
  • handle_fasteoi_irq()
  • handle_simple_irq()
  • handle_percpu_irq()
  • handle_edge_eoi_irq()
  • handle_bad_irq()

流控处理器(无论预置的还是架构自定义的)由架构在启动期间设备初始化期间分配给具体中断。这些函数的完整实现集中在 kernel/irq/chip.c,handle_bad_irq则在 kernel/irq/handle.c。

辅助函数(Helper functions)

辅助函数负责调用芯片原语,供默认流控实现使用。原文档给出了如下简化摘录:

default_enable(struct irq_data *data) { desc->irq_data.chip->irq_unmask(data); } default_disable(struct irq_data *data) { if (!delay_disable(data)) desc->irq_data.chip->irq_mask(data); } default_ack(struct irq_data *data) { chip->irq_ack(data); } default_mask_ack(struct irq_data *data) { if (chip->irq_mask_ack) { chip->irq_mask_ack(data); } else { chip->irq_mask(data); chip->irq_ack(data); } } noop(struct irq_data *data) { }

从源码看,这些辅助函数在 kernel/irq/chip.c 中体现为mask_ack_irq()unmask_irq()等内部封装,它们统一走desc->irq_data.chip的回调。注意default_mask_ack体现了irq_mask_ack组合原语的优化意义:若芯片提供该回调则一次调用完成"掩蔽 + ACK",否则退化为两次独立调用。

默认电平触发流控处理器:handle_level_irq()

handle_level_irq为电平触发中断提供通用实现。电平触发中断在硬件线保持有效电平期间一直处于激活状态,因此通常需要先掩蔽中断,等设备被确认(ack)后、中断线恢复到非激活电平,再解除掩蔽。

原文档给出的简化控制流:

desc->irq_data.chip->irq_mask_ack(); handle_irq_event(desc->action); desc->irq_data.chip->irq_unmask();

当前源码实现(kernel/irq/chip.c 第 689 行起)与之呼应,并在细节上做了增强:

void handle_level_irq(struct irq_desc *desc) { guard(raw_spinlock)(&desc->lock); mask_ack_irq(desc); if (!irq_can_handle(desc)) return; kstat_incr_irqs_this_cpu(desc); handle_irq_event(desc); cond_unmask_irq(desc); }

要点:整个处理在desc->lock保护下进行;先mask_ack_irq(即文档中的irq_mask_ack);随后更新每 CPU 中断统计并执行事件处理;最后通过cond_unmask_irq()有条件解除掩蔽——该函数(同文件第 666 行起)只在中断未被禁用且处于掩蔽态、且没有挂起的 oneshot 线程时执行unmask_irq()。也就是说,"执行完处理函数就立即解除掩蔽"只是默认情形,在IRQF_ONESHOT或中断被禁用等场景下会推迟。

默认快速 EOI 流控处理器:handle_fasteoi_irq()

handle_fasteoi_irq面向只需在处理器结束时执行一次 EOI 的中断——这类现代中断控制器把流控细节都在硬件里透明处理了。

原文档简化控制流:

handle_irq_event(desc->action); desc->irq_data.chip->irq_eoi();

当前实现(kernel/irq/chip.c 第 740 行起)更完整地展示了实际调度逻辑:

void handle_fasteoi_irq(struct irq_desc *desc) { struct irq_chip *chip = desc->irq_data.chip; guard(raw_spinlock)(&desc->lock); /* 亲和性变更与中断处理竞争时,中断可能在新 CPU 上先于旧 CPU 完成上一次处理到达,此时可能需要重发 */ if (!irq_can_handle_pm(desc)) { if (irqd_needs_resend_when_in_progress(&desc->irq_data)) desc->istate |= IRQS_PENDING; cond_eoi_irq(chip, &desc->irq_data); return; } if (!irq_can_handle_actions(desc)) { mask_irq(desc); cond_eoi_irq(chip, &desc->irq_data); return; } kstat_incr_irqs_this_cpu(desc); if (desc->istate & IRQS_ONESHOT) mask_irq(desc); handle_irq_event(desc); cond_unmask_eoi_irq(desc, chip); /* 上述竞争发生时,这里会重发中断 */ if (unlikely(desc->istate & IRQS_PENDING)) check_irq_resend(desc, false); }

可见现代实现还对 "fasteoi + ONESHOT"、"亲和性迁移期间中断重发"(check_irq_resend)等场景做了专门处理,并通过IRQCHIP_EOI_IF_HANDLED芯片标志支持"仅当中断确实被处理时才调用irq_eoi()"的优化(cond_eoi_irq,第 725 行起)。同文件还提供了面向 NMI 中断线的handle_fasteoi_nmi()(第 792 行起)。

默认边沿触发流控处理器:handle_edge_irq()

handle_edge_irq为边沿触发中断提供通用实现。边沿中断在硬件信号上升沿/下降沿触发,事件被锁存进控制器硬件,必须 ACK 才能重新使能。同一个中断源在第一次事件被处理完之前可能再次触发,此时视控制器硬件情况可能需要掩蔽中断,并在处理循环内部重新使能;所有挂起事件处理完才退出循环。

原文档简化控制流:

if (desc->status & running) { desc->irq_data.chip->irq_mask_ack(); desc->status |= pending | masked; return; } desc->irq_data.chip->irq_ack(); desc->status |= running; do { if (desc->status & masked) desc->irq_data.chip->irq_unmask(); desc->status &= ~pending; handle_irq_event(desc->action); } while (desc->status & pending); desc->status &= ~running;

当前实现(kernel/irq/chip.c 第 827 行起)使用了istateIRQS_PENDING等现代标志,逻辑与文档一致:

void handle_edge_irq(struct irq_desc *desc) { guard(raw_spinlock)(&desc->lock); if (!irq_can_handle(desc)) { desc->istate |= IRQS_PENDING; mask_ack_irq(desc); return; } kstat_incr_irqs_this_cpu(desc); /* 开始处理该中断 */ desc->irq_data.chip->irq_ack(&desc->irq_data); do { if (unlikely(!desc->action)) { mask_irq(desc); return; } /* 处理期间又有新中断到达时可能已掩蔽中断, 若期间未被禁用则重新使能 */ if (unlikely(desc->istate & IRQS_PENDING)) { if (!irqd_irq_disabled(&desc->irq_data) && irqd_irq_masked(&desc->irq_data)) unmask_irq(desc); } handle_irq_event(desc); } while ((desc->istate & IRQS_PENDING) && !irqd_irq_disabled(&desc->irq_data)); }

do/while循环即文档所述:只要仍有IRQS_PENDING且中断未被显式禁用,就继续处理新到达的边沿事件——这正是"边沿事件可能在处理途中再次到达"这一硬件特性的核心应对逻辑。

默认简单流控处理器:handle_simple_irq()

handle_simple_irq为简单中断提供通用实现。原文档特别强调:简单流控处理器不调用任何 handler/chip 原语——ACK、清除、掩蔽、解除掩蔽等都由调用方(如解复用处理器)自行负责。

handle_irq_event(desc->action);

当前实现(kernel/irq/chip.c 第 614 行起)在desc->lock保护下,先做电源管理/动作可用性检查(必要时置IRQS_PENDING等待重发),然后更新统计并调用handle_irq_event(desc)。注释明确写道:简单中断要么来自解复用中断处理器,要么来自"无需任何中断硬件控制"的硬件。同文件还提供了其变体handle_untracked_irq()(第 645 行起)——用于解复用器无法确定具体设备的情形,不做统计、随机熵与伪中断检测。

默认每 CPU 流控处理器:handle_percpu_irq()

handle_percpu_irq为每 CPU 中断提供通用实现。每 CPU 中断只在 SMP 上可用,其处理器提供不涉及锁的简化版本(因为每个 CPU 各自处理自己的中断线,无需跨 CPU 串行化)。

原文档简化控制流:

if (desc->irq_data.chip->irq_ack) desc->irq_data.chip->irq_ack(); handle_irq_event(desc->action); if (desc->irq_data.chip->irq_eoi) desc->irq_data.chip->irq_eoi();

当前实现(kernel/irq/chip.c 第 871 行起)与此一致,并特别注释"PER CPU 中断不被串行化,不要触碰desc->tot_count",改用__kstat_incr_irqs_this_cpu()做本地统计,再依次调用可选的irq_ackhandle_irq_event_percpu()与可选的irq_eoi。同文件还提供handle_percpu_devid_irq()(第 904 行起):使用每 CPU 的dev_id,可用于 NMI 中断线。

EOI 边沿流控处理器与坏中断处理器

  • handle_edge_eoi_irq:文档直言这是边沿处理器的一个"异种(abnomination)",仅用于驯服 powerpc/cell 上被严重损坏的中断控制器,属于特例而非通用方案。
  • handle_bad_irq:用于没有真实处理器分配的伪中断(spurious interrupts)。其实现位于 kernel/irq/handle.c 第 33 行起:打印描述符调试信息、累加每 CPU 统计,并调用ack_bad_irq()。如前述,所有新分配的描述符默认挂接的就是它,直到架构或驱动为其设置真正的流控处理器。

Quirks 与优化:延迟中断禁用(Delayed interrupt disable)

预置的通用函数是为"干净"的架构与芯片准备的,即那些没有平台特有 IRQ 处理怪癖的实现。若某架构需要在 flow 层面实现 quirks,可以通过覆盖高层 irq-flow 处理器来实现。

延迟中断禁用是文档详细介绍的一个特性,由 Russell King 在 ARM 中断实现中引入,按中断可单独选择,但整体不可配置。其核心思想:

  • 调用disable_irq()不在硬件层面立即掩蔽中断
  • 中断保持使能状态,而是在流控处理器中、当实际中断事件发生时再掩蔽;
  • 这避免了"硬件在中断被禁用期间不保存边沿事件"而导致的边沿中断丢失问题;
  • IRQ_DISABLED标志置位期间有中断到达,则在硬件层掩蔽该中断并置IRQ_PENDING位;
  • enable_irq()重新使能中断时检查 pending 位,若置位则通过硬件重触发或软件重发机制重新投递该中断;
  • 若硬件不具备重触发能力,使用该特性必须开启CONFIG_HARDIRQS_SW_RESEND配置项。

这一机制在当前内核中演化为irqd_needs_resend_when_in_progress()check_irq_resend()等逻辑(可分别在 kernel/irq/chip.c 与 kernel/irq/resend.c 中查看),前述handle_fasteoi_irq实现中的IRQS_PENDING重发路径即是其现代体现。

芯片级硬件封装:struct irq_chip

芯片级硬件描述符结构struct irq_chip包含所有与芯片直接相关的回调,供 irq flow 实现调用。结构定义在 include/linux/irq.h 第 499 行起,文档列出的核心原语为:

  • irq_ack:新中断的开始(ACK)
  • irq_mask_ack:可选,推荐用于性能(一次调用同时 ACK 与掩蔽)
  • irq_mask:掩蔽一个中断源
  • irq_unmask:解除掩蔽一个中断源
  • irq_eoi:可选,EOI 流控处理器必需
  • irq_retrigger:可选,向 CPU 重发 IRQ
  • irq_set_type:可选,设置中断流控类型(IRQ_TYPE_LEVEL 等)
  • irq_set_wake:可选,使能/禁用电源管理的唤醒功能

原文档强调:这些原语必须严格按字面意思实现——ack 就是 ACK,掩蔽就是掩蔽中断线,等等。由哪个流控处理器、如何组合这些底层功能单元,是流控层自己的职责。也就是说,irq_chip只回答"硬件怎么做",handle_*_irq只回答"什么时候做、按什么顺序做"。

完整的struct irq_chip远不止这些,还包括irq_startup/irq_shutdown(分别默认回退到enable/disable)、irq_enable(默认回退到unmask)、irq_set_affinityirq_bus_lock/irq_bus_sync_unlock(用于 i2c 等慢速总线芯片)、MSI 相关回调(irq_compose_msi_msg/irq_write_msi_msg)、irq_get_irqchip_state/irq_set_irqchip_state、虚拟化相关irq_set_vcpu_affinity、IPI 相关ipi_send_single/ipi_send_mask、NMI 相关irq_nmi_setup/irq_nmi_teardown,以及flags(如IRQCHIP_SET_TYPE_MASKEDIRQCHIP_EOI_IF_HANDLEDIRQCHIP_SKIP_SET_WAKE等芯片标志),可查看 include/linux/irq.h 第 448-540 行附近的完整注释。

__do_IRQ 入口点的消亡

原实现__do_IRQ()曾作为所有类型中断的备选入口点,但它被证明并不适用于所有中断硬件,因此被按 edge/level/simple/percpu 拆分重写。这不仅是功能层面的优化,也缩短了中断处理路径。原文档写作时该函数已被弃用;在当前仓库中,这个入口已经不再存在——kernel/irq/下已没有__do_IRQ的实现,验证了文档"no longer exists"的预言。

SMP 下的锁机制

芯片寄存器的加锁由定义芯片原语的架构负责;而每个 IRQ 描述符则由通用层通过desc->lock保护。这一点在 kernel/irq/chip.c 的所有流控处理器实现中体现得非常明显——每个handle_*_irq都使用guard(raw_spinlock)(&desc->lock)包裹(每 CPU 处理器除外,因其设计上无需锁)。synchronize_irq()disable_irq_nosync()等 SMP 专属 API 的存在,也正是为了配合这种逐描述符串行化模型。

通用中断芯片(Generic Interrupt Chip)

为避免各个平台重复实现几乎相同的 IRQ 芯片逻辑,核心层提供了可配置的通用中断芯片实现。原文档建议开发者动手实现相似功能前,先仔细评估通用芯片是否满足需求。该实现位于 kernel/irq/generic-chip.c(其文档注释可通过kernel-doc生成),对外提供的主要接口包括:

  • irq_alloc_generic_chip():分配通用芯片;
  • irq_setup_generic_chip():将通用芯片与描述符绑定并安装;
  • irq_gc_mask_clr_bit()/irq_gc_mask_set_bit():通过"清除/置位掩蔽寄存器中的位"实现掩蔽;
  • irq_gc_ack_set_bit():通过置位寄存器位实现 ACK;
  • irq_gc_unmask_enable_reg()irq_gc_eoi()等系列标准回调。

从 kernel/irq/generic-chip.c 第 69 行起可以看到这类回调的典型模式:从irq_data中取得struct irq_chip_generic *gcirq_data_get_irq_chip_data()),再对gc->reg_base映射的寄存器做位操作。这使得"一个寄存器位对应一条中断线"的常见控制器可以在不写任何芯片代码的情况下接入通用 IRQ 层,只需在平台代码中配置好寄存器基址、位掩码与流控类型。

结构体与函数的自动生成文档

原文档最后三章引用了内核文档系统自动生成的 API 文档,对应源码位置如下,需要查阅完整签名与注释时可深入对应文件:

  • Structures:include/linux/irq.h(irq_descirq_datairq_chip等)与 include/linux/interrupt.h(irqactionirq_handler_t等);
  • Public Functions:kernel/irq/manage.c(导出 API)与 kernel/irq/chip.c 的:export:部分;
  • Internal Functions:kernel/irq/irqdesc.c、kernel/irq/handle.c 与 kernel/irq/chip.c 的内部函数。

参考源码索引

  • Documentation/core-api/genericirq.rst:本文骨架,原文档作者为 Thomas Gleixner 与 Ingo Molnar(2005-2010);
  • kernel/irq/chip.c:全部预置流控处理器与芯片回调调度;
  • kernel/irq/handle.c:handle_bad_irq与线程唤醒逻辑;
  • kernel/irq/manage.c:高层驱动 API;
  • kernel/irq/irqdesc.c:描述符分配与默认流控处理器安装;
  • kernel/irq/generic-chip.c:通用中断芯片;
  • kernel/irq/resend.c:软件重发机制(CONFIG_HARDIRQS_SW_RESEND相关);
  • include/linux/irq.h 与 include/linux/interrupt.h:核心结构体与 API 声明。

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询