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),指定中断处理函数的入口地址、特权级和类型(中断门/陷阱门/任务门)。
1.3 中断处理流程:上半部(Top Half)与下半部(Bottom Half)
Linux 内核将中断处理过程拆分为两个阶段,以平衡响应速度和处理能力之间的矛盾:
- 上半部(Top Half):在中断上下文(Interrupt Context)中立即执行,必须快速完成,通常只做最紧急的工作(如清除硬件中断标志、拷贝少量数据)。
- 下半部(Bottom Half):推迟到更宽松的上下文中执行,可以处理耗时操作(如协议解析、数据分发)。
| 特性 | 上半部(Top Half) | 下半部(Bottom Half) |
|---|---|---|
| 执行上下文 | 中断上下文(不可睡眠) | 软中断 / 工作队列(可睡眠) |
| 可中断性 | 默认关闭本地中断(可配置) | 可被其他中断抢占 |
| 执行时机 | 中断触发后立即执行 | 调度点(如 do_softirq()) |
| 耗时要求 | 极短(微秒级) | 相对宽松(毫秒级) |
| 典型操作 | ACK 硬件、拷贝 FIFO 数据 | 网络协议栈处理、磁盘 I/O 完成 |
性能陷阱:如果上半部执行时间过长,会导致 CPU 长时间关闭本地中断,造成高优先级中断丢失或系统响应延迟飙升。网卡驱动是典型场景——上半部只将数据包从硬件 FIFO 拷贝到内存 Ring Buffer,协议栈解析全部交给下半部。
1.4 完整中断处理流程
从硬件触发中断到 Linux 内核完成处理,完整的流程包含以下步骤:
- 硬件触发:外设(如网卡、磁盘控制器)通过中断控制器(如 APIC)向 CPU 发送中断信号。
- CPU 响应:CPU 在指令边界检查中断引脚,若 IF 标志位允许,则保存当前上下文(CS、EIP/RIP、EFLAGS 等寄存器)。
- 查 IDT:CPU 根据中断号从 IDT 中取出对应的门描述符,获取中断处理函数入口地址。
- 执行上半部:跳转到
common_interrupt汇编入口,调用do_IRQ(),最终执行注册的irq_handler_t回调。 - 标记下半部:上半部返回前,通过
raise_softirq()或tasklet_schedule()标记需要执行的下半部。 - 执行下半部:在
do_softirq()或内核线程(如ksoftirqd)中执行下半部处理函数。 - 恢复现场:CPU 恢复被中断前的上下文,继续执行原程序。
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 缓存,可能驱逐当前进程的热数据。