Linux handle_edge_irq边沿触发与IRQ_WAITING检测
2026/6/15 6:37:01 网站建设 项目流程

Linux handle_edge_irq边沿触发与IRQ_WAITING检测

handle_edge_irq是Linux内核中断子系统为边沿触发中断提供的标准处理函数,定义在kernel/irq/chip.c。边沿触发中断的特点是在信号电平跳变沿(上升沿或下降沿)产生中断请求,锁存在中断控制器中,要求处理函数在清除pending位前必须完成所有必要的操作。handle_edge_irq的设计围绕这个特性展开。

函数源码实现:

```c
void handle_edge_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);

desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

if (irqd_irq_disabled(&desc->irq_data)) {
desc->istate |= IRQS_PENDING;
mask_ack_irq(desc);
goto out_unlock;
}

if ((desc->istate & IRQS_PENDING) || !desc->action) {
if (!desc->action)
mask_ack_irq(desc);
else
ack_irq(desc);
goto out_unlock;
}

kstat_incr_irqs_this_cpu(desc);
ack_irq(desc);

do {
if (unlikely(!desc->action)) {
mask_irq(desc);
goto out_unlock;
}

if (unlikely(irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
mask_ack_irq(desc);
goto out_unlock;
}

irq_compat_set_prog_affinity(desc);
raw_spin_unlock(&desc->lock);
handle_irq_event(desc);
raw_spin_lock(&desc->lock);

} while ((desc->istate & IRQS_PENDING) &&
!irqd_irq_disabled(&desc->irq_data));

out_unlock:
raw_spin_unlock(&desc->lock);
}
```

IRQ_WAITING标志是handle_edge_irq中重要的检测机制。在中断注册过程中,__setup_irq会调用irq_startup,而在startup过程中内核会为边沿触发中断设置IRQS_WAITING标志。当该标志存在时,第一次中断到达后,handle_edge_irq在顶部清除它,以确认中断源确实可工作并正确连接:

```c
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
```

IRQS_WAITING的赋值点在__setup_irq的启动路径上:

```c
static void irq_state_clr_started(struct irq_desc *desc)
{
desc->istate &= ~IRQS_STARTED;
if (irq_settings_is_edge_triggered(desc))
desc->istate |= IRQS_WAITING;
}
```

这个标志用于检测"虚假中断"(spurious interrupt)。如果ISR中该中断的IRQ_WAITING已被清除但在后续中断中又被检测到pending状态且action为空,则说明设备可能无法正确释放中断请求线。

IRQS_PENDING标志管理中断的二次触发。边沿触发中断在handler执行期间如果再次产生边沿信号,硬件锁存该请求并在EOI后再次上报。内核通过IRQS_PENDING标记记录这个二次触发:

```c
if (irqd_irq_disabled(&desc->irq_data)) {
desc->istate |= IRQS_PENDING;
mask_ack_irq(desc);
goto out_unlock;
}
```

当检测到IRQS_PENDING时,可能有两种情况:一是当前handler正在处理时新的边沿触发了;二是handler还没执行完就发生了中断嵌套。由于边沿中断一旦丢失就无法恢复,内核使用do-while循环反复调用handle_irq_event直到IRQS_PENDING被消耗完毕:

```c
do {
...
handle_irq_event(desc);
} while ((desc->istate & IRQS_PENDING) &&
!irqd_irq_disabled(&desc->irq_data));
```

ack_irq在handle_edge_irq的每个分支中都有调用。ack操作清除中断控制器中的pending位,使后续边沿触发信号能够再次被锁存和上报。对于x86 APIC,ack_irq展开为:

```c
static inline void ack_irq(struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;
chip->irq_ack(&desc->irq_data);
}
```

掩码与ack的关系在边沿触发中较为特殊。当中断被disable时,先mask_ack_irq(同时mask和ack),然后等待条件恢复。mask操作抑制了后续上报,但ack必须发出以清除当前pending状态。若只mask不ack,可能造成中断控制器持续上报已屏蔽的中断。

kstat_incr_irqs_this_cpu在每次有效中断到达时递增Per-CPU统计计数器,该数据通过/proc/interrupts暴露。用户可以通过观察该计数器来验证边沿中断是否频繁丢失—如果期望的中断数远大于统计计数,可能存在硬件锁存器溢出。

IRQS_REPLAY标志用于回放中断。当处理器在mask状态下丢失了一个边沿中断时,irq_poll或resend_irq机制设置IRQS_REPLAY并重新触发中断:

```c
if (chip->irq_retrigger) {
chip->irq_retrigger(&desc->irq_data);
desc->istate |= IRQS_REPLAY;
}
```

retrigger回调通过APIC向自身发送IPI,模拟中断源重新产生中断信号。handle_edge_irq在顶部清除IRQS_REPLAY,避免重复回放。

对于线程化边沿中断,handle_edge_irq的行为略有差异。当设置了IRQF_ONESHOT时,中断处理线程结束后才unmask,因此在边沿触发模式下存在丢失潜在边沿信号的风险。解决办法是利用IRQS_PENDING标记在unmask前记录所有到达的边沿信号,在do-while循环中一次性处理。

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

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

立即咨询