中断与 DMA:原理与架构
中断子系统架构
硬件设备
│ 电信号
▼
中断控制器(GIC / APIC / RISC-V PLIC)
│ IRQ 线
▼
CPU 异常向量表
│
▼ 汇编入口(irq_entry)
通用中断处理框架(generic IRQ)
│
├── 硬中断处理(hardirq)
│ └── 驱动的 irq_handler_t
│
└── 软中断(softirq)/ tasklet / workqueue
└── 延迟处理注册中断
c
#include <linux/interrupt.h>
/* 中断处理函数 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_priv *priv = dev_id;
u32 status;
/* 读取并清除中断状态 */
status = readl(priv->base + IRQ_STATUS);
writel(status, priv->base + IRQ_STATUS);
if (!(status & MY_IRQ_MASK))
return IRQ_NONE; /* 不是我的中断(共享中断时) */
/* 快速处理:只做最必要的操作 */
priv->irq_data = readl(priv->base + DATA_REG);
/* 触发下半部处理 */
tasklet_schedule(&priv->tasklet);
return IRQ_HANDLED;
}
/* 注册中断 */
ret = request_irq(irq,
my_irq_handler,
IRQF_SHARED, /* 共享中断线 */
"my-device",
priv); /* dev_id,共享中断时用于区分 */
/* 注销中断 */
free_irq(irq, priv);中断标志
| 标志 | 说明 |
|---|---|
IRQF_SHARED | 共享中断线(多设备共用) |
IRQF_TRIGGER_RISING | 上升沿触发 |
IRQF_TRIGGER_FALLING | 下降沿触发 |
IRQF_TRIGGER_HIGH | 高电平触发 |
IRQF_TRIGGER_LOW | 低电平触发 |
IRQF_ONESHOT | 线程化中断:处理完成前保持屏蔽 |
线程化中断(Threaded IRQ)
将中断处理移到内核线程,允许睡眠:
c
ret = request_threaded_irq(
irq,
my_hardirq_handler, /* 上半部:快速,不可睡眠 */
my_thread_handler, /* 下半部:在内核线程中运行,可睡眠 */
IRQF_ONESHOT,
"my-device",
priv
);
/* 上半部:仅判断是否是本设备中断 */
static irqreturn_t my_hardirq_handler(int irq, void *dev_id)
{
if (!is_my_interrupt())
return IRQ_NONE;
return IRQ_WAKE_THREAD; /* 唤醒线程处理 */
}
/* 下半部:可以睡眠,可以调用 i2c_transfer 等 */
static irqreturn_t my_thread_handler(int irq, void *dev_id)
{
struct my_priv *priv = dev_id;
/* 可以睡眠的操作 */
i2c_smbus_read_byte_data(priv->client, REG_DATA);
return IRQ_HANDLED;
}下半部机制对比
| 机制 | 上下文 | 可睡眠 | 适用场景 |
|---|---|---|---|
| 硬中断 | 中断上下文 | ❌ | 最紧急操作(清中断、读状态) |
| softirq | 软中断上下文 | ❌ | 网络收发、块设备 |
| tasklet | 软中断上下文 | ❌ | 简单延迟处理 |
| workqueue | 进程上下文 | ✅ | 需要睡眠的延迟处理 |
| threaded IRQ | 进程上下文 | ✅ | 现代驱动推荐方式 |
tasklet
c
static void my_tasklet_fn(struct tasklet_struct *t)
{
struct my_priv *priv = from_tasklet(priv, t, tasklet);
/* 处理数据,不可睡眠 */
}
/* 初始化 */
tasklet_setup(&priv->tasklet, my_tasklet_fn);
/* 在中断中调度 */
tasklet_schedule(&priv->tasklet);
/* 销毁 */
tasklet_kill(&priv->tasklet);workqueue
c
/* 使用系统 workqueue */
INIT_WORK(&priv->work, my_work_fn);
/* 在中断中调度 */
schedule_work(&priv->work);
/* 延迟调度 */
schedule_delayed_work(&priv->dwork, msecs_to_jiffies(100));
/* 工作函数(进程上下文,可睡眠) */
static void my_work_fn(struct work_struct *work)
{
struct my_priv *priv = container_of(work, struct my_priv, work);
/* 可以睡眠 */
msleep(10);
mutex_lock(&priv->lock);
/* ... */
mutex_unlock(&priv->lock);
}DMA 子系统
DMA Engine API
c
#include <linux/dmaengine.h>
/* 申请 DMA 通道 */
struct dma_chan *chan = dma_request_chan(dev, "tx");
if (IS_ERR(chan))
return PTR_ERR(chan);
/* 准备 DMA 描述符(内存到内存) */
struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_dma_memcpy(chan,
dst_dma_addr, /* 目标物理地址 */
src_dma_addr, /* 源物理地址 */
len,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK
);
/* 设置完成回调 */
desc->callback = my_dma_callback;
desc->callback_param = priv;
/* 提交并启动 */
dma_cookie_t cookie = dmaengine_submit(desc);
dma_async_issue_pending(chan);
/* 等待完成(同步方式) */
dma_sync_wait(chan, cookie);
/* 释放通道 */
dma_release_channel(chan);Scatter-Gather DMA
c
/* 准备 sg 列表 */
struct scatterlist sg[4];
sg_init_table(sg, 4);
sg_set_buf(&sg[0], buf0, len0);
sg_set_buf(&sg[1], buf1, len1);
/* 映射 sg 列表 */
int nents = dma_map_sg(dev, sg, 4, DMA_TO_DEVICE);
/* 准备 sg 描述符 */
desc = dmaengine_prep_slave_sg(chan, sg, nents,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT);
/* 提交 */
dmaengine_submit(desc);
dma_async_issue_pending(chan);
/* 完成后解映射 */
dma_unmap_sg(dev, sg, 4, DMA_TO_DEVICE);中断亲和性与 CPU 隔离
bash
# 查看中断分配
cat /proc/interrupts
# 设置中断亲和性(绑定到 CPU 2)
echo 4 > /proc/irq/42/smp_affinity # 位掩码,4 = CPU 2
# 查看中断触发类型
cat /sys/kernel/irq/42/trigger