1. 中断基础概念:中断的定义、中断向量表、中断处理流程(上半部与下半部)

1.1 中断的定义与本质

中断(Interrupt)是硬件或软件向处理器发送的一种异步信号,用于通知处理器发生了需要立即关注的事件。中断机制是现代操作系统实现多任务、响应外部设备的核心基石。

从系统架构角度看,中断的本质是处理器执行流的强制转移:CPU 暂停当前正在执行的指令序列,保存现场,跳转到一个预先定义好的中断服务例程(ISR, Interrupt Service Routine)执行,执行完毕后再恢复现场,继续执行被中断的程序。

关键特性:
  • 异步性:中断的发生与当前 CPU 执行的指令流无关,随时可能发生。
  • 可抢占性:高优先级中断可以抢占低优先级中断的处理。
  • 可屏蔽性:大多数中断可以通过 CPU 的标志位(如 IF 位)临时屏蔽。

1.2 中断向量表(IVT / IDT)

中断向量表是操作系统在内存中维护的一张表格,用于将中断号(Interrupt Number)映射到对应的中断处理函数入口地址。在 x86 架构中,早期使用中断向量表(IVT),现代保护模式下使用中断描述符表(IDT)。

概念 架构 表项结构 最大中断数
IVT(Interrupt Vector Table) x86 实模式 4 字节:段地址 + 偏移量 256(0x00 ~ 0xFF)
IDT(Interrupt Descriptor Table) x86 保护模式 / x86-64 8 字节(x86)或 16 字节(x86-64):段选择子 + 偏移量 + 属性 256(0x00 ~ 0xFF)

中断向量表的前 32 个中断(0x00 ~ 0x1F)通常保留给 CPU 异常和陷阱(如除零错误、页错误),从 0x20(32)开始分配给硬件中断(IRQ)。

Linux 中的 IDT 初始化:Linux 内核在启动阶段通过 trap_init() 和 init_IRQ() 函数填充 IDT 表项。每个表项包含一个门描述符(Gate Descriptor),指定中断处理函数的入口地址、特权级和类型(中断门/陷阱门/任务门)。
中断向量表(IDT)结构示意图 中断描述符表(IDT)基地址 中断 0 中断 1 中断 2 中断 3 中断 4 中断 5 中断 6 中断 7 ⋮ 中断 255 每个表项包含: 段选择子 + 偏移量 + 属性(DPL/类型)

1.3 中断处理流程:上半部(Top Half)与下半部(Bottom Half)

Linux 内核将中断处理过程拆分为两个阶段,以平衡响应速度和处理能力之间的矛盾:

  1. 上半部(Top Half):在中断上下文(Interrupt Context)中立即执行,必须快速完成,通常只做最紧急的工作(如清除硬件中断标志、拷贝少量数据)。
  2. 下半部(Bottom Half):推迟到更宽松的上下文中执行,可以处理耗时操作(如协议解析、数据分发)。
特性 上半部(Top Half) 下半部(Bottom Half)
执行上下文 中断上下文(不可睡眠) 软中断 / 工作队列(可睡眠)
可中断性 默认关闭本地中断(可配置) 可被其他中断抢占
执行时机 中断触发后立即执行 调度点(如 do_softirq())
耗时要求 极短(微秒级) 相对宽松(毫秒级)
典型操作 ACK 硬件、拷贝 FIFO 数据 网络协议栈处理、磁盘 I/O 完成
性能陷阱:如果上半部执行时间过长,会导致 CPU 长时间关闭本地中断,造成高优先级中断丢失或系统响应延迟飙升。网卡驱动是典型场景——上半部只将数据包从硬件 FIFO 拷贝到内存 Ring Buffer,协议栈解析全部交给下半部。

1.4 完整中断处理流程

从硬件触发中断到 Linux 内核完成处理,完整的流程包含以下步骤:

  1. 硬件触发:外设(如网卡、磁盘控制器)通过中断控制器(如 APIC)向 CPU 发送中断信号。
  2. CPU 响应:CPU 在指令边界检查中断引脚,若 IF 标志位允许,则保存当前上下文(CS、EIP/RIP、EFLAGS 等寄存器)。
  3. 查 IDT:CPU 根据中断号从 IDT 中取出对应的门描述符,获取中断处理函数入口地址。
  4. 执行上半部:跳转到 common_interrupt 汇编入口,调用 do_IRQ(),最终执行注册的 irq_handler_t 回调。
  5. 标记下半部:上半部返回前,通过 raise_softirq() 或 tasklet_schedule() 标记需要执行的下半部。
  6. 执行下半部:在 do_softirq() 或内核线程(如 ksoftirqd)中执行下半部处理函数。
  7. 恢复现场:CPU 恢复被中断前的上下文,继续执行原程序。
Linux 中断处理完整流程 ① 硬件触发中断 ② CPU 保存上下文 ③ 查询 IDT 获取入口 ④ 执行上半部(Top Half) ⑤ 标记下半部(raise_softirq) ⑥ 执行下半部(Bottom Half) 中断上下文 不可睡眠 本地中断关闭 必须快速返回 进程上下文 可睡眠 可被中断抢占 可处理复杂逻辑

1.5 下半部机制对比

Linux 内核提供了多种下半部实现机制,不同机制在延迟、并行度和使用场景上各有侧重:

机制 引入版本 执行上下文 并行度 典型场景
软中断(SoftIRQ) Linux 2.3 中断上下文 多 CPU 并行执行(同类型可并发) 网络 RX/TX、定时器、任务调度
Tasklet Linux 2.3 中断上下文 同类型串行(同一 Tasklet 不会并发) 块设备、串口驱动
工作队列(Workqueue) Linux 2.5 进程上下文 多 CPU 并行执行 需要睡眠的复杂操作(如文件系统)
选择建议:
  • 如果下半部需要极低延迟且不需要睡眠 → 使用 软中断(如网络栈)。
  • 如果下半部需要简单串行化且不需要睡眠 → 使用 Tasklet(如旧版驱动)。
  • 如果下半部需要睡眠或持有锁 → 使用 工作队列(如存储栈)。

1.6 中断上下文与进程上下文的切换代价

理解中断处理流程时,必须清楚中断上下文与进程上下文之间的切换开销:

  • 进入中断上下文:CPU 自动压栈(SS、ESP、EFLAGS、CS、EIP),约 20~30 个 CPU 周期。
  • 退出中断上下文:CPU 执行 iret 指令恢复现场,约 15~25 个 CPU 周期。
  • TLB 影响:中断处理可能刷新 TLB,导致后续进程切换时产生额外的页表遍历开销。
  • 缓存污染:中断处理函数会占用 L1/L2 缓存,可能驱逐当前进程的热数据。