1. 中断基础概念:中断的定义、中断向量表、中断服务程序(ISR)与上下文的切换

1.1 中断的定义与本质

中断(Interrupt)是一种硬件或软件触发的机制,它使处理器暂停当前正在执行的程序,转而执行一个特定的处理程序(即中断服务程序),待处理完成后返回到被中断的程序继续执行。中断是嵌入式系统实现实时响应、异步事件处理和多任务抢占的基石。

核心特征:中断是异步的、优先级驱动的、由硬件直接支持的上下文切换机制。它不依赖软件轮询,因此响应延迟低且确定性强。
特性 中断 轮询
触发方式 硬件异步触发 软件周期性检查
CPU 占用 仅在事件发生时占用 持续占用 CPU 资源
响应延迟 微秒级(硬件直接响应) 取决于轮询周期
实时性 高,适合硬实时场景 低,适合非实时场景
适用场景 按键、定时器、DMA、外设数据就绪 温度采集、状态监控

1.2 中断向量表(IVT / IDT)

中断向量表是处理器内存中一张固定的表格,每个表项存放着对应中断号的中断服务程序入口地址。当 CPU 检测到中断信号时,硬件自动根据中断号查表,跳转到对应的 ISR 执行。

  • 向量号(Vector Number):每个中断源分配一个唯一的编号,CPU 通过该编号索引向量表。
  • 向量表基址:由 CPU 架构决定(如 ARM Cortex-M 固定从 0x00000000 开始),或由系统寄存器配置(如 x86 的 IDTR)。
  • 表项大小:ARM Cortex-M 每个表项 4 字节(函数指针),x86 保护模式下每个表项 8 字节(门描述符)。
  • 异常与中断共用:复位、NMI、硬 fault 等异常也通过向量表处理,通常占据低编号表项。
驱动开发要点:在 Linux 内核中,中断向量表由 arch/<arch>/kernel/entry.S 或 irq.c 初始化。驱动开发者通常不直接操作向量表,而是通过 request_irq() 注册 ISR,由内核框架完成向量表的管理。
中断向量表结构(ARM Cortex-M 示例) 中断向量表(从 0x00000000 开始) 向量 0: 复位 向量 1: NMI 向量 2: HardFault 向量 3~15: 系统异常 向量 16~255: 外部中断(IRQ0 ~ IRQ239) IRQ0: TIMER IRQ1: UART IRQ2: GPIO 每个表项存放 ISR 入口地址 CPU 硬件自动查表跳转

1.3 中断服务程序(ISR)

中断服务程序(Interrupt Service Routine)是中断触发后 CPU 跳转执行的专用函数。ISR 必须短小、快速、不可阻塞,因为它在中断上下文中执行,会抢占所有普通任务。

ISR 的设计约束

  • 不可睡眠:中断上下文没有进程调度,调用 schedule() 或 wait_event() 会导致内核崩溃。
  • 不可持有互斥锁:互斥锁可能引起睡眠,应使用自旋锁(spinlock)保护共享数据。
  • 执行时间极短:长时间 ISR 会阻塞其他中断和任务,影响系统实时性。
  • 可重入性:同一中断可能嵌套触发,ISR 必须支持重入或由硬件自动屏蔽同级中断。
常见错误:在 ISR 中调用 printk() 过多会导致控制台输出延迟,进而影响中断响应。生产代码中 ISR 应仅做必要的数据搬运和标志置位,耗时操作推迟到下半部(Bottom Half)或工作队列(Workqueue)中处理。
/* Linux 内核 ISR 注册示例 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    /* 读取硬件状态寄存器 */
    uint32_t status = readl(reg_base + STATUS_REG);

    /* 清除中断标志 */
    writel(status, reg_base + CLEAR_REG);

    /* 将数据放入缓冲区,唤醒等待队列 */
    spin_lock(&lock);
    fifo_push(&rx_fifo, status);
    spin_unlock(&lock);

    wake_up_interruptible(&wq);

    return IRQ_HANDLED;
}

/* 驱动初始化时注册 ISR */
ret = request_irq(irq_num, my_irq_handler,
                  IRQF_TRIGGER_RISING,
                  "my_device", dev);
if (ret) {
    pr_err("Failed to request IRQ %d\n", irq_num);
    return ret;
}

1.4 上下文切换

上下文切换(Context Switch)是指 CPU 在中断发生时,保存当前正在执行任务的寄存器状态(上下文),加载中断服务程序的执行环境,并在 ISR 返回时恢复原任务状态的过程。上下文切换由硬件和软件协同完成。

中断上下文切换的步骤

  1. 硬件自动保存:CPU 自动将 PC、PSR、LR、R0-R3、R12 压入当前栈(主栈或进程栈)。
  2. 获取向量地址:硬件根据中断号查向量表,加载 ISR 入口地址到 PC。
  3. 切换栈指针:ARM Cortex-M 自动切换到 MSP(主栈指针),x86 切换到中断栈。
  4. 软件保存剩余寄存器:ISR 入口汇编代码保存 R4-R11 等被调用者保存寄存器。
  5. 执行 ISR 主体:运行 C 语言编写的 ISR 函数。
  6. 软件恢复寄存器:ISR 返回前汇编代码恢复 R4-R11。
  7. 硬件自动恢复:执行异常返回指令,硬件恢复 PC、PSR、LR、R0-R3、R12,继续执行被中断的任务。
中断上下文切换流程 任务 A(正在执行) PC = 0x08001234 SP = 0x20001FF0 LR = 0x08001100 R0~R12 = ... 中断触发 硬件自动压栈 压入: PC, PSR, LR 压入: R0, R1, R2, R3 压入: R12 更新 SP = SP - 32 查表 ISR 执行 PC = ISR 入口 SP = MSP LR = EXC_RETURN ISR 返回 硬件自动出栈 恢复 PC, PSR, LR 恢复 R0~R3, R12 SP = 原任务 SP 恢复任务 A 执行 整个上下文切换过程由 CPU 硬件自动完成压栈/出栈,软件只需保证 ISR 入口和出口的汇编代码正确保存/恢复剩余寄存器 ARM Cortex-M 使用硬件自动压栈(8 个寄存器),x86 使用硬件 TSS + 软件保存

1.5 中断上下文 vs 进程上下文

对比维度 中断上下文 进程上下文
可睡眠 ❌ 不可睡眠 ✅ 可以睡眠
可调度 ❌ 不可主动调度 ✅ 可调用 schedule()
锁机制 自旋锁(spinlock) 互斥锁(mutex)、信号量
栈空间 有限(通常 4KB~8KB) 较大(通常 8KB+)
访问用户空间 ❌ 不可访问 ✅ 可通过 copy_from_user()
执行时间限制 极短(微秒级) 无严格限制
典型操作 读硬件寄存器、清除中断标志、唤醒等待队列 文件读写、网络通信、内存分配
驱动设计原则:ISR 只做最紧急的硬件操作(如读取 FIFO、清除中断位),耗时数据处理通过 tasklet、workqueue 或内核线程推迟到进程上下文执行。这样既保证了中断响应的实时性,又避免了在中断上下文中执行复杂操作带来的风险。