第二篇:IRQ Chip 与 Flow Handlers — Level/Edge/FASTEOI 三种处理方式¶
源码:kernel/irq/chip.c kernel/irq/handle.c kernel/irq/irqdesc.c | 头文件:include/linux/irq.h include/linux/irqhandler.h
系列目录:IRQ 内核源码深度解析
1. 概述¶
第一篇我们分析了 irq_desc 数据结构以及 request_irq 的注册过程。当硬件中断到达 CPU 时,内核通过 desc->handle_irq 指向的流控处理器 (Flow Handler) 来决定如何调用 ISR。流控处理器的选择取决于硬件中断控制器的类型 (Level/Edge/FASTEOI) 和芯片方法表 (irq_chip)。
核心问题:为什么需要不同的流控处理器? - Level 触发:中断线保持高电平,直到设备清除条件。如果处理完不清除 (ack) 也不 mask,会立即再次触发,导致中断风暴。 - Edge 触发:中断线产生一个脉冲。如果脉冲到达时被 mask,则会丢失。 - FASTEOI:现代 MSI/MSI-X 的中断默认处理方式,发送 eoi 后控制器自动重装。
本文将逐行分析三个核心流控处理器和 irq_chip 方法表。
2. struct irq_chip — 中断控制器方法表 (irq.h:498)¶
// include/linux/irq.h:498
struct irq_chip {
const char *name; // 控制器名称 (例如 "GICv3", "IO-APIC")
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); // 应答 (acknowledge)
void (*irq_mask)(struct irq_data *data); // 掩码 (mask)
void (*irq_mask_ack)(struct irq_data *data); // mask + ack 组合
void (*irq_unmask)(struct irq_data *data); // 解掩码 (unmask)
void (*irq_eoi)(struct irq_data *data); // 中断结束 (end of interrupt)
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);
// 设置触发类型 (level/edge/极性)
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);
// 总线同步解锁,发送挂起的寄存器写
int (*irq_suspend)(struct irq_data *data); // PM: 挂起
int (*irq_resume)(struct irq_data *data); // PM: 恢复
void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
// MSI: 组合 MSI 消息
// ...更多字段...
};
2.1 方法分类¶
┌───────────────────────────────────────────────────────────────────────────────┐
│ irq_chip 方法分类 │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【启动/关闭】 │
│ ┌─────────────────────┬──────────────────────────────────────────┐ │
│ │ irq_startup() │ 初始化硬件,使能中断。默认: irq_enable │ │
│ │ irq_shutdown() │ 关闭硬件中断。默认: irq_disable │ │
│ │ irq_enable() │ 使能中断。默认: irq_unmask │ │
│ │ irq_disable() │ 禁用中断。默认: irq_mask │ │
│ └─────────────────────┴──────────────────────────────────────────┘ │
│ │
│ 【应答/掩码】 │
│ ┌─────────────────────┬──────────────────────────────────────────┐ │
│ │ irq_ack() │ 应答 (告诉控制器"我已收到") │ │
│ │ irq_mask() │ 掩码 (阻止更多中断到达) │ │
│ │ irq_mask_ack() │ mask + ack 组合 (level 中断常用) │ │
│ │ irq_unmask() │ 解掩码 (允许中断到达) │ │
│ │ irq_eoi() │ 中断结束 (告诉控制器"我已处理完毕") │ │
│ └─────────────────────┴──────────────────────────────────────────┘ │
│ │
│ 【触发类型】 │
│ ┌─────────────────────┬──────────────────────────────────────────┐ │
│ │ irq_set_type() │ 设置触发类型 (低电平/高电平/上升沿/下降沿) │ │
│ │ irq_retrigger() │ 重新触发中断 (用于边沿检测和 poll) │ │
│ └─────────────────────┴──────────────────────────────────────────┘ │
│ │
│ 【其他】 │
│ ┌─────────────────────┬──────────────────────────────────────────┐ │
│ │ irq_set_affinity() │ 设置 CPU affinity │ │
│ │ irq_set_wake() │ 设置 wakeup 源 │ │
│ │ irq_bus_lock() │ 总线访问锁 │ │
│ │ irq_bus_sync_unlock() │ 发送挂起的寄存器写 │ │
│ │ irq_compose_msi_msg()│ 组合 MSI 消息 │ │
│ │ irq_suspend/resume()│ PM 挂起/恢复 │ │
│ └─────────────────────┴──────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
2.2 默认实现¶
如果 chip 未提供某些方法,内核使用 irq_chip 的默认实现:
// 默认 startup: 调用 enable
irq_startup(struct irq_data *data) {
irq_enable(data);
return 0;
}
// 默认 enable: 调用 unmask
irq_enable(struct irq_data *data) {
irq_unmask(data);
}
// 默认 disable: 调用 mask
irq_disable(struct irq_data *data) {
irq_mask(data);
}
3. irq_flow_handler_t — 流控处理器类型 (irqhandler.h)¶
流控处理器是一个函数指针,存储在 desc->handle_irq 中。内核预定义了多种流控处理器:
| 流控处理器 | 用途 | 源码位置 |
|---|---|---|
handle_level_irq |
Level 触发中断 | chip.c:687 |
handle_fasteoi_irq |
现代 MSI/MSI-X, GIC | chip.c:738 |
handle_edge_irq |
Edge 触发中断 | chip.c:825 |
handle_simple_irq |
无需 ack/eoi 的简单中断 | chip.c:630 |
handle_percpu_irq |
per-CPU 中断 | chip.c:975 |
handle_bad_irq |
不处理 (占位/错误) | handle.c:33 |
4. 架构入口 → 流控处理器的完整链路¶
4.1 ARM64 上的中断入口 (GICv3)¶
硬件中断到达 PE
│
▼
ARM64 异常向量表 — el1_irq
│
▼
arch/arm64/kernel/entry.S: el1_irq_handler
│
▼
kernel/irq/handle.c: handle_arch_irq(regs)
│
▼
set_handle_irq() 注册的函数
│ (arm64 GIC: gic_handle_irq)
▼
drivers/irqchip/irq-gic-v3.c: gic_handle_irq(void *regs)
│
├── 读取 GICC_IAR (Interrupt Acknowledge Register)
│ └── hwirq = 中断 ID (intid)
│
├── 如果 intid < 1020 (PPI/SPI): 普通中断
│ │
│ └── generic_handle_domain_irq(gic_data.domain, hwirq) // irqdesc.c:729
│
└── 如果 intid >= 1020: 特殊中断 (IPI, 维护等)
4.2 generic_handle_domain_irq → generic_handle_irq_desc¶
// kernel/irq/irqdesc.c:729
int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq)
{
// 通过 irq_domain 的映射表查找 Linux IRQ 号
// 再调用 generic_handle_irq
return handle_irq_desc(irq_find_mapping(domain, hwirq));
}
// kernel/irq/irqdesc.c:688
int generic_handle_irq(unsigned int irq)
{
// 用 Linux IRQ 号在 radix tree 中查找 irq_desc
return handle_irq_desc(irq);
}
// kernel/irq/irqdesc.h:184 (内联函数)
static inline int handle_irq_desc(struct irq_desc *desc)
{
if (!desc)
return -EINVAL;
generic_handle_irq_desc(desc);
}
4.3 generic_handle_irq_desc — 调用流控处理器¶
// kernel/irq/irqdesc.h:193 (内联函数)
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
/*
* desc->handle_irq() 指向:
* handle_level_irq()
* handle_edge_irq()
* handle_fasteoi_irq()
* handle_bad_irq()
* ...
*/
desc->handle_irq(desc);
}
4.4 x86 入口 (APIC)¶
x86 中断门
│
▼
arch/x86/kernel/irq.c: common_interrupt(regs)
│
▼
kernel/irq/handle.c: handle_arch_irq(regs) = do_IRQ(regs)
│
▼
do_IRQ(): 读取 ISA IRQ 或通过 IO-APIC / MSI 消息向量查找 irq_desc
│
▼
generic_handle_irq(irq)
│
▼
desc->handle_irq(desc) // → handle_fasteoi_irq / handle_edge_irq
4.5 handle_arch_irq 注册 (全局变量)¶
// kernel/irq/handle.c:285
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
handle_arch_irq = handle_irq;
}
各架构在初始化时注册自己的中断处理入口:
5. handle_level_irq — Level 触发 (chip.c:687)¶
5.1 Level 触发的物理特性¶
中断信号
┌─────────────────────────────────────────────────┐
│ 高电平: 中断持续 │
│ │
level ────┤ ├────
inactive ── │ │
│ │
└──┬──┬─────────┬─────────────────────────────────┘
│ 设备置起中断 设备清除中断条件
│
└─── interrupt signaled ────────────────────────
Level 触发的关键特性: - 中断线保持断言 (asserted) 状态直到设备寄存器的中断标志被清除 - 如果不在处理前 mask 中断,会在 ISR 返回后立即再次触发 → 中断风暴 - 处理策略:先 mask,再 ack 设备,然后调用 ISR,最后 unmask
5.2 源码分析¶
// kernel/irq/chip.c:687
void handle_level_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
// ───── 第一步: mask + ack (mask_ack) ─────
// mask: 阻止更多中断到达 (防止重入)
// ack: 通知控制器已收到中断
//
// 使用 mask_ack_irq 统一调用 desc->irq_data.chip->irq_mask_ack
// 如果 chip 未提供 mask_ack, 先调 mask 再调 ack
mask_ack_irq(desc);
// ───── 第二步: 检查 IRQ 状态 ─────
if (!irq_may_run(desc))
goto out_unlock;
// ───── 第三步: 设置 INPROGRESS ─────
// 在调用 ISR 前标记 "正在处理中"
// 如果 INPROGRESS 已被设置 (嵌套/并发 场景), 直接退出
if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
if (!irq_check_poll(desc))
goto out_unmask;
desc->istate |= IRQS_INPROGRESS;
do {
// ───── 第四步: 清除 "REPLAY" 标志 ─────
// 如果由于某些原因 (如 spurious detection polling) 需要
// 重新处理: 清除标志并继续
if (unlikely(desc->istate & IRQS_REPLAY))
desc->istate &= ~IRQS_REPLAY;
// ───── 第五步: 调用 ISR 链 ─────
handle_irq_event(desc);
// ───── 第六步: 检查 IRQ 是否仍然有效 ─────
// level 中断在 ISR 执行后, 硬件仍然可能保持断言状态
// 如果 ISR 成功处理 (设备清除条件), 循环结束
// 如果 ISR 未处理 (设备未清除), 再做一次
cond_unmask_irq(desc);
} while (irqd_irq_inprogress(&desc->irq_data));
// ───── 第七步: 清除 INPROGRESS ─────
desc->istate &= ~IRQS_INPROGRESS;
out_unmask:
// ───── 第八步: unmask ─────
// 如果依然要执行 (retrigger): 跳过 unmask (交由 retrigger 处理)
// 否则: 常态 unmask → 重新接收中断
if (unlikely(!idec->on | irqd_irq_inprogress(...)))
goto out_unlock;
unmask_irq(desc);
out_unlock:
raw_spin_unlock(&desc->lock);
}
5.3 Level 流程图¶
Level 中断到达
│
▼
┌─────────────┐
│ 获取锁 │ raw_spin_lock(&desc->lock)
└──────┬──────┘
│
▼
┌─────────────┐
│ mask + ack │ chip->irq_mask_ack() → 阻止重入 + 应答
└──────┬──────┘
│
▼
┌─────────────┐ 是
│ INPROGRESS? ├──→ goto out_unmask (重入保护)
└──┬──────────┘
│ 否
▼
┌─────────────┐
│ 设置 │ desc->istate |= IRQS_INPROGRESS
│ INPROGRESS │
└──────┬──────┘
│
▼
┌─────────────┐
│ ISR 执行 │ handle_irq_event(desc)
│ 遍历 action │ → 逐个调用 action->handler()
└──────┬──────┘
│
▼
┌─────────────┐ 否
│ 仍在 active │────→ 清除 INPROGRESS → unmask → unlock → 返回
│ (IRQ 条件还未 │
│ 被设备清除?) │
└──┬──────────┘
│ 是: 重新执行一遍 ISR
│ (设备清除条件耗时较长)
▼
┌─────────────┐
│ unmask │ chip->irq_unmask() → 恢复接收中断
└──────┬──────┘
│
▼
┌─────────────┐
│ 释放锁 │ raw_spin_unlock(&desc->lock)
└─────────────┘
6. handle_fasteoi_irq — 现代 MSI/MSI-X (chip.c:738)¶
6.1 FASTEOI 的物理特性¶
MSI/MSI-X 中断不是引脚电平,而是写入特定内存地址的消息。一旦写入,中断消息被边缘触发,但可以通过 EOI (End Of Interrupt) 来通知控制器已完成处理。GIC v2/v3 的 SPI (Shared Peripheral Interrupts) 也使用此模式。
与 Level/Edge 的关键区别: - Level: mask → ISR → unmask (处理期间短暂屏蔽) - Edge: ack → ISR (处理前 ack 防止错过) - FASTEOI: ISR → eoi (先处理再 eoi, 更简化)
6.2 源码分析¶
// kernel/irq/chip.c:738
void handle_fasteoi_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
// ───── 第一步: 快速状态检查 ─────
if (!irq_may_run(desc) || irqd_irq_polled(&desc->irq_data))
goto out_eoi;
// ───── 第二步: 检查 ONESHOT 状态 ─────
// 如果 IRQF_ONESHOT, threads_oneshot 不为零表示
// 上一个中断的线程还未完成 → mask 等待线程完成
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc);
// ───── 第三步: 再次检查 ─────
if (!irq_may_run(desc))
goto out_eoi;
// ───── 第四步: 调用 ISR ─────
handle_irq_event(desc);
out_eoi:
// ───── 第五步: 检查是否需要 eoi ─────
// 如果 ISR 返回 IRQ_WAKE_THREAD (启动了线程化 handler):
// 不发送 eoi (让线程完成后在 irq_finalize_oneshot 中发送)
// 否则: 立即发送 eoi
if (!(desc->istate & IRQS_ONESHOT_ACTIVE))
irq_eoi(desc);
raw_spin_unlock(&desc->lock);
}
6.3 FASTEOI 流程图¶
MSI 中断到达 (消息写入)
│
▼
┌────────────────────────┐
│ CORE: 获取锁 │ raw_spin_lock
└──────────┬─────────────┘
│
▼
┌────────────────────────┐ 是
│ irq_may_run()? ├──────────► goto out_eoi → eoi → unlock
└──────┬─────────────────┘
│ 否
▼
┌────────────────────────┐ 是
│ ONESHOT active? ├──────────► mask_irq(desc) (等待上一个线程完成)
└──────┬─────────────────┘
│ 否
▼
┌────────────────────────┐
│ handle_irq_event(desc) │ 遍历 action 链表, 调用每个 handler
└──────────┬─────────────┘
│
├── 返回 NOT oneshot active:
│ └──► irq_eoi(desc) → unlock
│
└── 返回 ONESHOT_ACTIVE:
└──► 跳过 eoi (等待线程完成后 irq_finalize_oneshot 发送)
unlock
7. handle_edge_irq — Edge 触发 (chip.c:825)¶
7.1 Edge 触发的物理特性¶
中断信号
inactive ────┐ ┌────────────────
(低电平) │ │
│ │
─┼──────────────┐ │
│ 上升沿脉冲 │ │
active │ (短脉冲) │ │
│ │ │
└───────────────┘ │
└──── interrupt signaled ───────┘
Edge 触发的关键特性: - 中断只是一个短暂的脉冲,不会保持 - 如果脉冲到达时 IRQ 被 mask,脉冲会丢失 (不能恢复) - 处理策略:不能 mask!必须先 ack (锁存),然后调用 ISR
由于边缘触发的中断是瞬时的,一旦 ISR 完成处理后,硬件自动重新使能 (re-ARM),不需要显式 unmask。但需要在处理期间暂时屏蔽以避免重入。
7.2 源码分析¶
// kernel/irq/chip.c:825
void handle_edge_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
// ───── 第一步: 检查 IRQ 是否正在处理中 ─────
// 如果已经在处理: goto out_unlock
//
// 但如果 REPLAY 标志已设置: 重入检查被跳过
// 说明: REPLAY 表示中断可能被错过了 (例如之前被 mask),
// 现在需要重新投递。
if (irqd_irq_disabled(&desc->irq_data) ||
irq_settings_can_handle_polling(desc))
desc->istate |= IRQS_REPLAY;
else if (!(desc->istate & IRQS_INPROGRESS))
desc->istate |= IRQS_INPROGRESS;
else
goto out_unlock;
// ───── 第二步: ack ─────
// 向控制器确认已收到中断 (锁存中断状态)
// 对于 GIC, ack 本质上读取 GICC_IAR 并转换为 hwirq
if (desc->irq_data.chip->irq_ack)
desc->irq_data.chip->irq_ack(&desc->irq_data);
// ───── 第三步: 调用 ISR ─────
handle_irq_event(desc);
// ───── 第四步: 检查 MISSED ─────
// 在 ISR 执行期间,可能有新的边沿到达
// 如果中断在 ISR 执行时仍然 pending (又触发了):
// 设置 REPLAY 标志 → 下次再进来
if (irqd_irq_masked(&desc->irq_data)) {
if (!(desc->istate & IRQS_INPROGRESS)) {
desc->istate &= ~IRQS_INPROGRESS;
irq_release_resources(desc);
}
} else {
irq_enable(desc);
}
desc->istate &= ~IRQS_INPROGRESS;
out_unlock:
raw_spin_unlock(&desc->lock);
}
7.3 Edge 流程图¶
Edge 脉冲到达
│
▼
┌────────────┐
│ 获取锁 │ raw_spin_lock(&desc->lock)
└──────┬─────┘
│
▼
┌────────────┐ 是 ┌─────────────────┐
│ INPROGRESS?├───────────────→ │ 设置 IRQS_REPLAY │ (中断可能丢失)
└──┬─────────┘ │ 下次再进 │
│ 否 └──────────┬────────┘
▼ │
┌─────────────────────┐ │
│ 设置 IRQS_INPROGRESS │ │
└──────────┬──────────┘ │
│ │
▼ │
┌─────────────────────┐ │
│ chip->irq_ack() │ 锁存中断状态 │
└──────────┬──────────┘ │
│ │
▼ │
┌─────────────────────┐ │
│ handle_irq_event() │ 调用所有 ISR │
└──────────┬──────────┘ │
│ │
▼ │
┌─────────────────────┐ │
│ 中断在 ISR 期间 │ │
│ 又触发了吗? │ │
└──┬─────────┬────────┘ │
│ 是 │ 否 │
▼ ▼ │
┌──────┐ ┌────────────┐ │
│ MASK │ │ ENABLE │ │
│ (掩码) │ │ (RE-ARM) │ │
│ │ │ │ ◄────────────────┘
└──────┘ └────────────┘
│ │
▼ ▼
┌─────────────────────┐
│ 清除 INPROGRESS │
│ 释放锁 │
└─────────────────────┘
8. handle_irq_event — ISR 遍历机制 (handle.c:255)¶
handle_irq_event 是三个流控处理器的公共核心,负责遍历 action 链表。
// kernel/irq/handle.c:255
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;
// ───── 第一步: 清除 PENDING 标志 ─────
desc->istate &= ~IRQS_PENDING;
// ───── 第二步: 设置 INPROGRESS ─────
irqd_set(&desc->irq_data, IRQD_INPROGRESS);
// ───── 第三步: 调用 per-CPU ISR 遍历 ─────
ret = __handle_irq_event_percpu(desc);
// ───── 第四步: 清除 INPROGRESS ─────
irqd_clear(&desc->irq_data, IRQD_INPROGRESS);
// ───── 第五步: 贡献随机性熵 ─────
add_interrupt_randomness(irq);
return ret;
}
8.1 __handle_irq_event_percpu — 逐 action 调用 (handle.c:185)¶
// kernel/irq/handle.c:185
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
/*
* 记录每个 CPU 上的中断次数
* desc->kstat_irqs[] 是 per-CPU 计数的数组
* 即 /proc/interrupts 的数据来源!
*/
__kstat_incr_irqs_this_cpu(desc);
/*
* 遍历 action 链表
* 共享中断: 调用所有 ISR, 返回值 OR 在一起
*/
for_each_action_of_desc(desc, action) {
irqreturn_t res;
// ───── 追踪处理与否 ─────
trace_irq_handler_entry(irq, action);
// ───── 调用 ISR! ─────
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
// ───── 合并返回值 ─────
// 如果是共享中断, 所有 ISR 都必须返回 IRQ_NONE …
// 如果有任何一个返回 IRQ_HANDLED, 整体就是 HANDLED
switch (res) {
case IRQ_WAKE_THREAD:
// 标记需要唤醒线程, 但仍然 "HANDLED"
__irq_wake_thread(desc, action);
fallthrough;
case IRQ_HANDLED:
retval |= res;
break;
default:
break;
}
}
// ───── 第六步: 虚假中断检测 ─────
// 如果没有任何 ISR 返回 HANDLED/WAKETHREAD
// note_interrupt 进行虚假检测和 "nobody cared" 诊断 (详见第三篇)
note_interrupt(irq, desc, retval);
return retval;
}
返回值组合逻辑:
Action 1: IRQ_NONE (设备不是这个 interrupt 的)
Action 2: IRQ_HANDLED (设备处理了)
Action 3: IRQ_NONE (设备也不是这个 interrupt 的)
最终 retval: IRQ_HANDLED
9. 三种处理器的对比¶
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Level / FASTEOI / Edge 处理对比 │
├─────────────┬─────────────────┬──────────────────┬──────────────────────────────┤
│ 特性 │ Level │ FASTEOI │ Edge │
├─────────────┼─────────────────┼──────────────────┼──────────────────────────────┤
│ 信号特性 │ 持续保持 │ 消息式/MSI │ 脉冲式 (短暂) │
│ │ │ │ │
│ 中断丢失 │ 不可能 │ 不可能 │ 可能 (如果被 mask) │
│ │ │ │ │
│ 重入保护 │ mask → ISR │ ONESHOT mask │ INPROGRESS check │
│ │ │ │ │
│ ISR 前操作 │ mask_ack │ (无) │ ack (锁存) │
│ │ │ │ │
│ ISR 后操作 │ unmask │ eoi │ 可能 unmask / 设置 REPLAY │
│ │ │ │ │
│ 并发控制 │ spin_lock │ spin_lock │ spin_lock │
│ │ │ │ │
│ 典型硬件 │ 传统 PC/AT │ GIC, ARM │ 传统 PC/AT │
│ │ 8259, GPIO │ ITS, MSI/MSI-X │ 8259, 边沿 GPIO │
│ │ │ │ │
│ 中断风暴 │ 如果处理前后 │ 不需要 mask │ 不需要 mask │
│ │ 不 mask 则可能 │ │ 但需要 replay 防护 │
│ │ │ │ │
│ 线程化适应 │ 直接支持 │ ONESHOT 机制 │ 小心 (线程化+边沿=挑战) │
│ │ │ │ │
│ 重入 │ 通过 mask 防止 │ ONESHOT 防止 │ INPROGRESS (+REPLAY) 防止 │
│ │ │ │ │
└─────────────┴─────────────────┴──────────────────┴──────────────────────────────┘
时序对比图¶
Level:
──┬────mask─ack──┬───ISR───┬───unmask─────►
│ │ │ │ 中断可能仍 asserted
│ │ │ │ 第一个 unmask 后再次进入
│ └─────────┘ │
│ │
└──── 中断持续保持 ──────────────────────┘
FASTEOI:
──┬─────┬───────ISR───────┬───eoi──►
│ │ │ │ 现代 MSI: 消息已发送
│ │ │ │ 仅在 ONESHOT 时 mask
└─────┴──────────────────┘
Edge:
┌───┬──ack──┬───ISR───┬──CheckPending───►
│ │ │ │ │ 如果在 ISR 期间又来了新 pulse
│ │ │ │ │ 可能被 miss → 设置 REPLAY
└───┘ └─────────┘ │ 重新调用 ISR
│
└─► IRQS_REPLAY → 下次再进入
10. handle_bad_irq — 错误占位处理器 (handle.c)¶
// kernel/irq/handle.c:33
void handle_bad_irq(struct irq_desc *desc)
{
// 简单打印错误并 ack
// 通常在多个体系结构中用于 "不工作" 的中断
kstat_incr_irqs_this_cpu(desc);
ack_bad_irq(desc->irq_data.irq);
}
新的描述符初始化为 handle_bad_irq,直到通过 irq_set_chip_and_handler_name 等领域设置操作设置正确的流控处理器。
11. chip 和 handler 的绑定 — irq_set_chip_and_handler_name (chip.c:1037)¶
// kernel/irq/chip.c:43
int irq_set_chip(unsigned int irq, struct irq_chip *chip)
{
// 设置 irq_data->chip 指针和默认的 chip_data
// 用于指向芯片的私有数据
struct irq_desc *desc = irq_to_desc(irq);
desc->irq_data.chip = chip;
return 0;
}
// kernel/irq/chip.c:1037
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
irq_flow_handler_t handle, const char *name)
{
struct irq_desc *desc = irq_to_desc(irq);
// 设置 chip 方法表
desc->irq_data.chip = chip;
// 设置流控处理器
if (handle)
__irq_set_handler(irq, handle, 0, name);
// 确保 chip_data 指向有效数据 (通常是 irq_data)
if (!desc->irq_data.chip_data && chip)
desc->irq_data.chip_data = &desc->irq_data;
}
典型调用 (arch/platform 代码中):
// 为 GIC SPI 中断设置处理方式
irq_set_chip_and_handler_name(i, &gic_chip, handle_fasteoi_irq, NULL);
// 为 GPIO 边沿中断
irq_set_chip_and_handler_name(i, &gpio_chip, handle_edge_irq, "edge");
// 为 legacy 8259 Level 中断
irq_set_chip_and_handler_name(i, &i8259A_chip, handle_level_irq, NULL);
12. 中断迁移与 CPU affinity — chip 的影响¶
当通过 irq_set_affinity() 改变 CPU 亲和性时,chip 的 irq_set_affinity() 方法必须对硬件寄存器做编程。
// manage.c
int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask,
bool force)
{
// 1. 更新 irq_common_data.affinity
cpumask_copy(data->common->affinity, mask);
// 2. 调用 chip 硬件操作
ret = chip->irq_set_affinity(data, mask, force);
// 3. 更新 effective_affinity
// 记录实际可以设置的 CPU
return ret;
}
13. MSI/MSI-X 与 FASTEOI¶
FASTEOI 是 MSI/MSI-X 中断的默认处理方式。MSI 中断的区别: 1. 没有物理引脚,通过写入 PCI 配置空间中的 MSI 地址发送中断消息 2. 消息编码包含硬件中断号 (hwirq) 3. irq_compose_msi_msg 是 chip 中的方法,组合 MSI 消息格式
PCI 设备 MSI-X
│
├── MSI-X 表项: 地址 hi + 地址 lo + 数据值 (hwirq)
│
├── 设备写 MSI 消息到指定地址 (GIC ITS 或 IOMMU)
│
├── 中断控制器译码消息, 提取 hwirq
│
├── generic_handle_domain_irq(domain, hwirq)
│
└── handle_fasteoi_irq(desc) → ISR → eoi
14. 性能考量¶
| 场景 | 推荐的 handler | 原因 |
|---|---|---|
| MSI/MSI-X (NVMe、网络) | FASTEOI | 默认,无需 mask_ack |
| GPIO 电平中断 | Level | 需要 mask_ack 防止风暴 |
| GPIO 边沿中断 | Edge | 需要 REPLAY 防止丢失 |
| SGI/IPI | Percpu 或 FASTEOI | 每 CPU 私有中断 |
| 定时器 | 取决于架构 | 多数架构使用 per-IPI |
FASTEOI 性能优势: - 减少寄存器操作次数 (无 mask, 无 ack, 仅 ISR + eoi) - 对于高频中断 (网络 10Gbps → 每秒百万级中断),每次节省 1-2 次外设寄存器访问
Edge 中断捕获丢失: - 如果 ISR 执行时间过长,下一个边沿脉冲到达且处于 INPROGRESS 状态 - REPLAY 机制确保下次进入时重新调用 ISR - 但可能仍然丢失某些硬件状态
15. 与后续文章的衔接¶
- 第一篇:irq_desc, request_irq, irqaction 注册
- 第二篇 (本文):irq_chip 方法表, Level/Edge/FASTEOI 流控处理器
- 第三篇:note_interrupt 虚假检测, /proc/interrupts 数据统计
- 第四篇:raise_softirq, __do_softirq, ksoftirqd, tasklet
- 第五篇:CPU 热插拔对 IRQ 的影响, MSI 域, 电源管理 PM
16. 源码导航¶
kernel/irq/chip.c — 流控处理器实现
kernel/irq/handle.c — handle_irq_event (ISR 链遍历)
kernel/irq/irqdesc.c — generic_handle_irq, irq_to_desc
include/linux/irq.h — irq_chip 结构体定义
include/linux/irqhandler.h — irq_flow_handler_t 类型定义
drivers/irqchip/ — 各厂商实现 (GIC, ITS, 8259, pl190, etc.)
arch/arm64/kernel/ — ARM64 中断入口 (gic_handle_irq)
arch/x86/kernel/irq.c — x86 中断入口 (do_IRQ)
下一篇文章¶
第三篇:/proc/interrupts 与 虚假中断检测 — show_interrupts 与 note_interrupt