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,由内核框架完成向量表的管理。
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 返回时恢复原任务状态的过程。上下文切换由硬件和软件协同完成。
中断上下文切换的步骤
- 硬件自动保存:CPU 自动将 PC、PSR、LR、R0-R3、R12 压入当前栈(主栈或进程栈)。
- 获取向量地址:硬件根据中断号查向量表,加载 ISR 入口地址到 PC。
- 切换栈指针:ARM Cortex-M 自动切换到 MSP(主栈指针),x86 切换到中断栈。
- 软件保存剩余寄存器:ISR 入口汇编代码保存 R4-R11 等被调用者保存寄存器。
- 执行 ISR 主体:运行 C 语言编写的 ISR 函数。
- 软件恢复寄存器:ISR 返回前汇编代码恢复 R4-R11。
- 硬件自动恢复:执行异常返回指令,硬件恢复 PC、PSR、LR、R0-R3、R12,继续执行被中断的任务。
1.5 中断上下文 vs 进程上下文
| 对比维度 | 中断上下文 | 进程上下文 |
|---|---|---|
| 可睡眠 | ❌ 不可睡眠 | ✅ 可以睡眠 |
| 可调度 | ❌ 不可主动调度 | ✅ 可调用 schedule() |
| 锁机制 | 自旋锁(spinlock) | 互斥锁(mutex)、信号量 |
| 栈空间 | 有限(通常 4KB~8KB) | 较大(通常 8KB+) |
| 访问用户空间 | ❌ 不可访问 | ✅ 可通过 copy_from_user() |
| 执行时间限制 | 极短(微秒级) | 无严格限制 |
| 典型操作 | 读硬件寄存器、清除中断标志、唤醒等待队列 | 文件读写、网络通信、内存分配 |
驱动设计原则:ISR 只做最紧急的硬件操作(如读取 FIFO、清除中断位),耗时数据处理通过
tasklet、workqueue 或内核线程推迟到进程上下文执行。这样既保证了中断响应的实时性,又避免了在中断上下文中执行复杂操作带来的风险。