中断与异常

一、基本概念

二、中断分类

类型 说明
同步(异常) CPU 执行指令产生(除零、缺页)
异步(中断) 外设硬件触发

GIC 中断分类:
- SGI(Software Generated Interrupt):CPU 间中断(0-15)
- PPI(Private Peripheral Interrupt):CPU 私有外设(16-31)
- SPI(Shared Peripheral Interrupt):共享外设(32+)

三、中断处理模型

1. 顶半部(Top Half)

2. 底半部(Bottom Half)

四、中断 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);

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 内核)

六、disable_irq / enable_irq

disable_irq(irq);          // 等待 ISR 结束后屏蔽
disable_irq_nosync(irq);   // 不等待
enable_irq(irq);

七、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 又触发