4. 中断丢失现象:典型表现与系统影响

中断丢失是网关驱动开发中最隐蔽、排查代价最高的故障之一。当中断信号从外设发出后,CPU 未能正确响应并执行对应的中断服务程序(ISR),导致数据无法及时处理。本节从现象、根因到影响逐层拆解,帮助开发者建立快速识别中断丢失的能力。

4.1 中断丢失的典型表现

中断丢失不会直接触发内核崩溃,而是通过一系列“软故障”暴露出来。下表总结了不同场景下的典型表现:

表现类别 具体现象 可观测指标
数据面 接收数据包偶发丢失,重传率上升 ifconfig 显示 RX dropped 持续增长
时序面 DMA 完成中断延迟到达,触发超时重传 /proc/interrupts 对应 IRQ 计数增长缓慢
状态面 外设状态寄存器显示中断挂起位未清除 读取外设 ISR 寄存器发现 pending 位为 1
性能面 CPU 占用率异常升高,但吞吐量不升反降 top 显示 ksoftirqd 占用高,软中断循环空转
日志面 dmesg 出现 "nobody cared" 或 "irq timeout" 内核自动禁用该 IRQ,设备彻底失联

排查切入点: 当发现 /proc/interrupts 中某个 IRQ 的计数在业务压力下长时间不增长,而外设仍在持续产生数据,即可判定中断丢失正在发生。

4.2 中断丢失的根因分类

根据 Linux 中断处理流程,中断丢失可归为以下四类:

  1. 硬件级丢失: 外设中断引脚电平保持时间不足,或中断控制器(GIC/IOAPIC)采样窗口错过脉冲。
  2. 屏蔽级丢失: 中断服务程序执行时间过长,导致本地 CPU 中断被硬件自动屏蔽,后续同源中断无法递达。
  3. 竞争级丢失: 共享中断线路上多个驱动注册了 handler,但某个 handler 返回 IRQ_NONE 导致内核误判为“无人处理”。
  4. 嵌套级丢失: 在中断上下文中调用可能导致睡眠的函数(如 mutex_lock),触发内核调度,中断上下文被切换出去。

开发建议: 在 ISR 顶部立即读取外设中断状态寄存器并写入日志缓冲区(使用 trace_printk),可以区分“硬件未产生中断”与“软件未及时处理”。

4.3 中断丢失对系统的影响

中断丢失的影响范围从单包丢失逐步扩散到整个子系统失效,其传播路径如下:

中断丢失影响传播路径 中断丢失 DMA 数据滞留 FIFO FIFO 溢出 / 数据覆盖 协议层超时重传 吞吐量断崖式下降 内核自动禁用 IRQ 设备彻底离线

从传播路径可以看出,中断丢失的初期影响是局部的,但如果不加干预,会迅速演变为全局性故障:

  • 数据完整性受损: FIFO 溢出导致数据覆盖,上层协议校验失败,触发 TCP 重传或应用层丢包。
  • 实时性崩溃: 网关的实时控制报文(如工业以太网周期数据)因中断延迟错过 deadline,导致控制环路失效。
  • 资源死锁: 驱动等待中断完成 DMA 传输,但中断永远不来,导致 wait_for_completion 永久阻塞。
  • 系统稳定性下降: 内核在检测到连续中断丢失后,会调用 disable_irq 或 free_irq,设备永久失效。

生产环境警示: 某工业网关项目因 SPI 中断丢失导致 PLC 数据采集延迟 200ms,触发产线急停。最终定位为 ISR 中执行了 printk 导致中断屏蔽窗口过长。修复后中断丢失率从 3.2% 降至 0.001%。

4.4 中断丢失的快速诊断方法

当怀疑中断丢失时,按以下步骤快速定位:

步骤 操作 预期结果
1 cat /proc/interrupts | grep <irq_num> 计数应随业务压力线性增长
2 cat /sys/kernel/debug/irq/<irq_num> 查看 handler 注册状态与统计
3 perf stat -e irq_vectors:* -a sleep 5 统计中断向量分布,确认是否被重定向
4 在 ISR 入口添加 trace_printk 对比外设中断状态寄存器与 ISR 执行次数
5 检查外设中断极性/电平配置 确认上升沿/高电平触发与硬件设计一致

关键指标: 使用 irqbalance 时,检查中断是否被错误地绑定到了隔离 CPU 或离线 CPU 上,这是多核系统中中断丢失的常见原因。

4.5 中断丢失与 DMA 超时的关联

中断丢失与 DMA 超时存在强耦合关系。DMA 传输完成后,外设通过中断通知 CPU 读取数据。如果中断丢失,DMA 描述符链表的完成状态无法被及时处理,驱动中的超时定时器会率先触发:

/* 典型 DMA 超时处理路径 */
static irqreturn_t dma_complete_handler(int irq, void *dev_id)
{
    struct dma_chan *chan = dev_id;
    /* 清除中断状态 */
    writel(INT_CLEAR, chan->regs + INT_STATUS);
    /* 处理完成描述符 */
    dma_cookie_complete(chan);
    return IRQ_HANDLED;
}

/* 超时保护:如果中断丢失,定时器会调用此函数 */
static void dma_timeout_handler(struct timer_list *t)
{
    struct dma_chan *chan = from_timer(chan, t, timeout);
    if (readl(chan->regs + DMA_STATUS) & DMA_COMPLETE) {
        /* 中断丢失!DMA 已完成但未触发中断 */
        dev_warn(chan->dev, "DMA timeout - interrupt lost, recovering\n");
        dma_cookie_complete(chan);
        /* 重新使能中断 */
        writel(INT_ENABLE, chan->regs + INT_MASK);
    }
}

上述代码展示了中断丢失时的典型恢复策略:超时处理函数主动轮询 DMA 状态寄存器,若发现传输已完成,则手动完成描述符处理并重新使能中断。这种“软轮询”机制虽然能恢复功能,但会引入额外的延迟和 CPU 开销。

最佳实践: 在 DMA 驱动中同时实现中断完成和超时轮询两条路径,超时阈值设置为正常中断延迟的 3~5 倍。这样既能保证中断丢失时的自愈能力,又不会因频繁轮询浪费 CPU。