2. 中断基础概念:什么是中断、中断向量表、中断优先级、中断嵌套、Linux中断号

各位同学,咱们今天聊聊中断。说实话,中断是嵌入式系统里最核心的机制之一。我做了这么多年驱动开发,可以负责任地告诉你——搞不懂中断,你就别想写好Camera驱动。Camera的数据流是实时的,一帧图像几十兆甚至上百兆,全靠中断来协调。

2.1 什么是中断?

中断,说白了就是CPU正在干活,突然被一个外部事件打断了。

举个例子。你正在写代码,突然手机响了。你放下键盘,接电话,聊完再回来继续写。这个「手机响」就是中断信号,你「接电话」就是中断服务程序,你「回来继续写」就是中断返回。

在嵌入式系统里,中断的来源很多:

  • 外部中断:GPIO电平变化、按键按下
  • 内部中断:定时器溢出、DMA传输完成
  • 软件中断:系统调用、异常处理

对于Camera驱动来说,最常用的中断是VSYNC(帧同步)中断和DMA传输完成中断。VSYNC中断告诉驱动「新的一帧来了」,DMA中断告诉驱动「数据已经搬到内存了」。

我的经验: 刚开始做Camera驱动时,我总以为中断越多越好。后来发现,中断频率太高反而会拖垮系统。比如1080p 30fps的视频流,每秒30个VSYNC中断就够了,没必要每个像素都触发中断。

2.2 中断向量表

中断向量表,你可以把它理解成一个「电话本」。CPU收到中断信号后,会查这个电话本,找到对应的处理函数地址,然后跳过去执行。

在ARM Cortex-A系列处理器上,中断向量表通常放在内存的起始位置(0x00000000或0xFFFF0000)。每个中断源对应一个表项,表项里存的是中断服务程序的入口地址。

// 典型的ARM中断向量表结构
// 每个表项占4字节,存放跳转指令或地址
__attribute__((section(".vectors")))
void (*vector_table[16])(void) = {
    reset_handler,      // 0x00: 复位
    undefined_handler,  // 0x04: 未定义指令
    swi_handler,        // 0x08: 软件中断
    prefetch_handler,   // 0x0C: 预取中止
    data_handler,       // 0x10: 数据中止
    reserved,           // 0x14: 保留
    irq_handler,        // 0x18: IRQ中断
    fiq_handler         // 0x1C: FIQ中断
};

嗯,这里要注意。在Linux内核里,中断向量表已经被内核管理起来了。驱动开发者不需要直接操作向量表,而是通过request_irq()来注册中断处理函数。内核会自动帮你把处理函数挂到对应的中断向量上。

2.3 中断优先级

中断优先级,解决的是「多个中断同时来了怎么办」的问题。

你想想看,Camera的VSYNC中断和UART接收中断同时到达,CPU先处理哪个?这就看优先级了。优先级高的先执行,优先级低的后执行。

在ARM GIC(通用中断控制器)中,中断优先级通常用0~255的数字表示,数字越小优先级越高。Camera驱动里的VSYNC中断,我一般会设置成较高的优先级(比如32),因为帧同步信号不能丢,丢了画面就撕裂了。

中断类型 典型优先级 说明
VSYNC中断 32(高) 帧同步,不能丢
DMA完成中断 64(中) 数据传输完成,可以稍等
UART接收中断 128(低) 串口数据,可以缓冲
避坑指南: 我曾经在一个项目里把Camera中断优先级设得太低,结果被网卡中断频繁抢占,导致帧率不稳定。后来把Camera中断优先级提到网卡之上,问题就解决了。记住:实时性要求高的中断,优先级一定要给够。

2.4 中断嵌套

中断嵌套,就是中断里面又来了中断。

举个例子。CPU正在处理Camera的VSYNC中断,突然来了一个更高优先级的DMA错误中断。这时候CPU会暂停VSYNC中断处理,先去处理DMA错误中断,处理完再回来继续处理VSYNC中断。

中断嵌套的好处是:高优先级的中断不会被低优先级的中断阻塞。但坏处也很明显——嵌套层数太多,栈空间容易爆掉,而且调试起来非常痛苦。

我个人习惯是:中断服务程序里尽量少做事。能放到下半部(tasklet或workqueue)的,绝不放在上半部。这样中断处理时间短,嵌套的概率就小。

// 中断上半部:只做最紧急的事
static irqreturn_t camera_irq_handler(int irq, void *dev_id)
{
    struct camera_device *cam = dev_id;
    
    // 清除中断标志
    writel(0x1, cam->base + IRQ_CLEAR);
    
    // 记录中断时间戳
    cam->last_vsync_time = ktime_get();
    
    // 唤醒等待队列,让下半部处理
    wake_up_interruptible(&cam->vsync_wait);
    
    return IRQ_HANDLED;
}

你看,上半部只做了三件事:清中断、记时间、唤醒等待队列。真正的图像数据处理,放在下半部或者用户空间去做。

2.5 Linux中断号

Linux中断号,是内核给每个中断源分配的一个数字标识。驱动通过这个中断号来注册和操作中断。

中断号分为两类:

  • 硬件中断号:由芯片厂商定义,比如GPIO中断号可能是32~63
  • 软件中断号(IRQ number):Linux内核重新映射后的逻辑编号

在设备树里,我们通常这样描述Camera的中断:

camera: camera@3c {
    compatible = "sony,imx219";
    reg = <0x3c>;
    interrupts = <GIC_SPI 100 IRQ_TYPE_EDGE_RISING>;
    // GIC_SPI: 共享外设中断
    // 100: 硬件中断号
    // IRQ_TYPE_EDGE_RISING: 上升沿触发
};

驱动里通过platform_get_irq()获取中断号,然后注册:

int irq = platform_get_irq(pdev, 0);
if (irq < 0) {
    dev_err(&pdev->dev, "failed to get IRQ\n");
    return irq;
}

ret = request_irq(irq, camera_irq_handler, 
                  IRQF_TRIGGER_RISING,
                  "camera", cam);
if (ret) {
    dev_err(&pdev->dev, "failed to request IRQ\n");
    return ret;
}
关键点: Linux中断号是动态分配的,不要硬编码。不同平台、不同芯片,同一个Camera传感器的中断号可能完全不同。一定要通过设备树或ACPI来获取。

好了,中断的基础概念就讲到这里。下一节我们会深入Camera驱动里的具体中断处理流程,包括VSYNC中断的时序分析和DMA中断的协同设计。到时候我会拿一个实际项目里的代码来拆解,保证干货满满。