Skip to content

中断与 DMA 最佳实践

1. 中断处理函数要尽量短

硬中断处理期间,同优先级中断被屏蔽,延迟过长影响系统实时性:

c
/* ✅ 正确:硬中断只做最少的事 */
static irqreturn_t my_isr(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;

    /* 1. 读取并清除中断状态(必须在硬中断中做,否则中断会再次触发) */
    u32 status = readl(priv->base + IRQ_STATUS_REG);
    writel(status, priv->base + IRQ_STATUS_REG);

    /* 2. 保存必要数据 */
    priv->pending_status = status;

    /* 3. 调度下半部 */
    tasklet_schedule(&priv->tasklet);

    return IRQ_HANDLED;
}

/* ❌ 错误:在硬中断中做耗时操作 */
static irqreturn_t bad_isr(int irq, void *dev_id)
{
    msleep(10);           /* 绝对不行! */
    mutex_lock(&lock);    /* 可能睡眠,绝对不行! */
    i2c_transfer(...);    /* 可能睡眠,绝对不行! */
    return IRQ_HANDLED;
}

2. 共享中断必须检查来源

c
static irqreturn_t my_shared_isr(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;
    u32 status = readl(priv->base + IRQ_STATUS);

    /* 必须检查是否是本设备触发的中断 */
    if (!(status & MY_IRQ_BIT))
        return IRQ_NONE;   /* 不是我的,让其他处理函数处理 */

    /* 处理中断 */
    return IRQ_HANDLED;
}

3. 使用 devm_request_irq 自动释放

c
/* ✅ devm 版本,设备解绑时自动 free_irq */
ret = devm_request_irq(&pdev->dev, irq, my_isr,
                        IRQF_SHARED, "my-device", priv);

4. DMA 缓冲区对齐

c
/* DMA 缓冲区需要缓存行对齐,避免 false sharing */
#define DMA_BUF_SIZE  PAGE_SIZE

/* 使用 dma_alloc_coherent 保证对齐 */
priv->dma_buf = dma_alloc_coherent(dev, DMA_BUF_SIZE,
                                    &priv->dma_addr, GFP_KERNEL);

/* 或使用 ARCH_DMA_MINALIGN 对齐的 kmalloc */
priv->buf = kmalloc(size, GFP_KERNEL | GFP_DMA);

5. DMA 方向与缓存一致性

c
/* 写入设备前:CPU 写 → 刷新缓存 → DMA 读 */
dma_sync_single_for_device(dev, dma_addr, size, DMA_TO_DEVICE);

/* 设备写完后:DMA 写 → 无效化缓存 → CPU 读 */
dma_sync_single_for_cpu(dev, dma_addr, size, DMA_FROM_DEVICE);

6. 避免在中断上下文分配内存

c
/* ❌ 中断上下文不能用 GFP_KERNEL */
static irqreturn_t bad_isr(int irq, void *dev_id)
{
    void *buf = kmalloc(size, GFP_KERNEL);  /* 可能睡眠! */
}

/* ✅ 预先分配,或使用 GFP_ATOMIC */
static irqreturn_t good_isr(int irq, void *dev_id)
{
    void *buf = kmalloc(size, GFP_ATOMIC);  /* 不睡眠,可能失败 */
    if (!buf)
        return IRQ_HANDLED;   /* 内存不足时跳过 */
}

7. 中断风暴防护

c
/* 检测并处理中断风暴 */
static irqreturn_t my_isr(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;

    if (++priv->irq_count > MAX_IRQ_PER_SEC) {
        dev_err(priv->dev, "IRQ storm detected, disabling IRQ\n");
        disable_irq_nosync(irq);   /* 不可在中断中用 disable_irq(会死锁) */
        schedule_work(&priv->irq_storm_work);
        return IRQ_HANDLED;
    }

    return IRQ_HANDLED;
}

8. workqueue 选择

c
/* 系统 workqueue(适合大多数场景) */
schedule_work(&priv->work);

/* 高优先级 workqueue(对延迟敏感) */
queue_work(system_highpri_wq, &priv->work);

/* 专用 workqueue(避免被其他驱动阻塞) */
priv->wq = alloc_ordered_workqueue("my-driver", WQ_MEM_RECLAIM);
queue_work(priv->wq, &priv->work);
destroy_workqueue(priv->wq);   /* 卸载时销毁 */

褚成志的笔记