Skip to content

中断与 DMA 故障处理案例

案例 1:中断不触发

排查步骤

bash
# 1. 确认中断号
cat /proc/interrupts | grep my-device

# 2. 观察中断计数是否增加
watch -n 1 "cat /proc/interrupts | grep my-device"

# 3. 检查中断控制器配置
cat /sys/kernel/irq/<irq_num>/trigger
cat /sys/kernel/irq/<irq_num>/chip_name

常见原因及解决

原因解决方法
设备树 interrupts 属性错误对照 GIC binding 文档检查格式
硬件中断未使能probe 中写寄存器使能中断
中断被 disable_irq() 屏蔽检查代码中是否有未配对的 disable/enable
共享中断但 dev_id 为 NULL共享中断必须传非 NULL 的 dev_id

案例 2:中断处理函数中死锁

现象:系统卡死,dmesg 显示 BUG: spinlock deadlock

错误代码

c
spinlock_t my_lock;

static irqreturn_t my_isr(int irq, void *dev_id)
{
    spin_lock(&my_lock);   /* ❌ 如果进程上下文持有锁时发生中断,死锁! */
    /* ... */
    spin_unlock(&my_lock);
    return IRQ_HANDLED;
}

static void my_process_fn(void)
{
    spin_lock(&my_lock);   /* 持有锁时发生中断 → 死锁 */
    /* ... */
    spin_unlock(&my_lock);
}

正确代码

c
/* 进程上下文中使用 spin_lock_irqsave,禁止中断 */
static void my_process_fn(void)
{
    unsigned long flags;
    spin_lock_irqsave(&my_lock, flags);
    /* ... */
    spin_unlock_irqrestore(&my_lock, flags);
}

案例 3:DMA 数据错误

现象:DMA 传输完成,但读取的数据不正确。

排查

c
/* 检查 1:DMA 方向是否正确 */
/* DMA_FROM_DEVICE:设备写内存,CPU 读 */
/* DMA_TO_DEVICE:CPU 写内存,设备读 */

/* 检查 2:缓存同步是否遗漏 */
/* 设备写完后,CPU 读之前必须无效化缓存 */
dma_sync_single_for_cpu(dev, dma_addr, size, DMA_FROM_DEVICE);
data = priv->dma_buf[0];   /* 现在读取才是最新数据 */

/* 检查 3:DMA 地址是否正确写入设备寄存器 */
pr_info("DMA addr: 0x%llx\n", (u64)dma_addr);
writel(lower_32_bits(dma_addr), base + DMA_ADDR_LO);
writel(upper_32_bits(dma_addr), base + DMA_ADDR_HI);

案例 4:DMA 映射失败

现象dma_map_single() 返回错误地址。

c
dma_addr_t dma_addr = dma_map_single(dev, buf, size, DMA_TO_DEVICE);

/* ✅ 必须检查映射结果 */
if (dma_mapping_error(dev, dma_addr)) {
    dev_err(dev, "DMA mapping failed\n");
    return -ENOMEM;
}

常见原因

  • IOMMU 配置错误(检查 dmesg | grep iommu
  • 设备 DMA 掩码未设置(dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))
  • 内存区域超出设备 DMA 寻址范围

案例 5:tasklet 中访问已释放内存

现象:驱动卸载时偶发崩溃。

错误代码

c
static void __exit my_exit(void)
{
    kfree(priv);           /* ❌ 先释放内存 */
    tasklet_kill(&priv->tasklet);  /* tasklet 可能还在运行! */
}

正确代码

c
static void __exit my_exit(void)
{
    tasklet_kill(&priv->tasklet);  /* ✅ 先等待 tasklet 完成 */
    cancel_work_sync(&priv->work); /* 等待 workqueue 完成 */
    kfree(priv);                   /* 再释放内存 */
}

案例 6:中断延迟过高

现象:实时任务响应延迟超过要求。

排查

bash
# 使用 cyclictest 测量中断延迟
cyclictest -t1 -p 80 -n -i 1000 -l 10000

# 使用 ftrace 追踪中断延迟
echo irqsoff > /sys/kernel/debug/tracing/tracer
cat /sys/kernel/debug/tracing/trace

优化方向

  • 使用 PREEMPT_RT 内核
  • 减少硬中断处理时间,移到线程化中断
  • 设置中断亲和性,避免跨 NUMA 节点
  • 禁用 CPU 频率调节(cpupower frequency-set -g performance

褚成志的笔记