中断与 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)