中断与异常
一、基本概念
- 中断(Interrupt):外设通知 CPU"有事"
- 异常(Exception):CPU 内部事件(系统调用、缺页)
- IRQ:中断请求
- ISR:Interrupt Service Routine(中断处理函数)
二、中断分类
| 类型 | 说明 |
|---|---|
| 同步(异常) | CPU 执行指令产生(除零、缺页) |
| 异步(中断) | 外设硬件触发 |
GIC 中断分类:
- SGI(Software Generated Interrupt):CPU 间中断(0-15)
- PPI(Private Peripheral Interrupt):CPU 私有外设(16-31)
- SPI(Shared Peripheral Interrupt):共享外设(32+)
三、中断处理模型
1. 顶半部(Top Half)
- 真正的 ISR
- 关中断上下文
- 必须快速返回
- 不能睡眠、不能调度
2. 底半部(Bottom Half)
- 推迟的处理
- 可睡眠
- 多种实现:
- softirq:静态、不可睡眠
- tasklet:基于 softirq、动态
- workqueue:可睡眠、进程上下文
- threaded IRQ:内核线程化
- tasklet 即将废弃,推荐 workqueue
四、中断 API
1. 注册中断
// 老 API(已废弃)
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags, const char *name, void *dev);
// 现代 API
int request_threaded_irq(unsigned int irq,
irq_handler_t hard_handler, // 顶半部
irq_handler_t thread_fn, // 线程化底半部
unsigned long flags,
const char *name,
void *dev);
// devres 版本
int devm_request_threaded_irq(struct device *dev,
unsigned int irq,
irq_handler_t hard_handler,
irq_handler_t thread_fn,
unsigned long flags,
const char *name,
void *dev);
flags 常用:
- IRQF_SHARED:共享
- IRQF_TRIGGER_RISING / FALLING / HIGH / LOW:触发方式
- IRQF_ONESHOT:oneshot,线程化中断必须
- IRQF_NO_SUSPEND:挂起时不屏蔽
2. 释放
free_irq(unsigned int irq, void *dev);
3. 中断处理函数
static irqreturn_t my_handler(int irq, void *dev_id)
{
// 必须快速!
// 不能 sleep / mutex_lock
// 不能用 printk 太多(用 pr_info_warn 或 dev_info)
// 不能拷贝大量数据
return IRQ_HANDLED; // 或 IRQ_NONE 表示不是本设备
}
五、底半部
1. tasklet
void my_tasklet_func(unsigned long data);
DECLARE_TASKLET(my_tasklet, my_tasklet_func, 0);
// 调度
tasklet_schedule(&my_tasklet);
- 运行在 softirq 上下文,不可睡眠
- 现在不推荐
2. workqueue(推荐)
struct work_struct my_work;
INIT_WORK(&my_work, my_work_func);
void my_work_func(struct work_struct *work)
{
// 可睡眠、可用 mutex
msleep(10);
// ...
}
// 调度
schedule_work(&my_work);
专用 workqueue(推荐用于中断底半部):
struct workqueue_struct *my_wq = alloc_workqueue("my", WQ_UNBOUND, 0);
queue_work(my_wq, &my_work);
// 取消
cancel_work_sync(&my_work);
flush_workqueue(my_wq);
system_wq(系统共享):
schedule_work(&my_work);
3. threaded IRQ
int request_threaded_irq(irq,
my_hard_handler, // 顶半部
my_thread_fn, // 线程化
IRQF_ONESHOT,
"name", dev);
static irqreturn_t my_hard_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD; // 唤醒内核线程
}
特点:
- 顶半部只触发;底半部跑在内核线程
- 自动在 mask 后唤醒(避免竞态)
- 适合需要睡眠的 ISR
4. 中断线程化(RT 内核)
- PREEMPT_RT 下,所有中断强制线程化
- 几乎所有 ISR 都可睡眠
六、disable_irq / enable_irq
disable_irq(irq); // 等待 ISR 结束后屏蔽
disable_irq_nosync(irq); // 不等待
enable_irq(irq);
- 谨慎使用,容易死锁
- 推荐用
disable_irq+ 标志位,或 device-managed API
七、IRQ 域与 GIC(ARM)
// 设备树
intc: interrupt-controller@00a01000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a11000 0x2000>;
};
// 客户
uart0: serial@40010000 {
compatible = "fsl,imx6ul-uart";
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&intc>;
};
八、IRQ 编号
cat /proc/interrupts
cat /proc/irq/N/smp_affinity
九、中断亲和性
echo 2 > /proc/irq/32/smp_affinity # 把 IRQ 32 绑到 CPU1
驱动中:
struct irq_affinity_hint hint = { .mask = cpumask_of(1) };
irq_set_affinity_hint(irq, &hint);
十、踩坑清单
| 现象 | 原因 |
|---|---|
| 中断不触发 | GPIO 没配中断功能 / 没使能 |
| 中断风暴 | 顶半部没清中断 / 电平触发无屏蔽 |
BUG: scheduling while atomic |
顶半部调用了睡眠函数 |
| 中断丢失 | 边沿触发 + 处理慢 |
| 死锁 | 在 ISR 中 mutex_lock 后 ISR 又触发 |