Linux中的中断和异常
中断和异常的初步介绍
在逻辑电路的层面,中断被定义为一个信号,用来改变处理器的指令执行顺序。这里的中断被分为同步中断和异步中断:
- 同步中断:由处理器自发产生,它一定发生在一条指令执行完之后
- 异步中断:有其它设备按照处理器的时钟随机产生
既然发生了中断,就说明需要对应的中断处理程序==中断处理程序不是进程,它更应该被视为一种CPU对硬件事件的响应机制,是一种软硬件结合的“执行路径抽象”==,出于性能的需求,中断处理程序必须满足:简短,高效且不可阻塞的。其中中断有不同的种类,所有的中断都用无符号八位数来标识,这个标识被英特尔称为向量。
上面所说的是学习用的通俗理解,在现代的Linux内核中,中断真正的处理往往采用:中断上下文 + 延迟处理机制 方式的结合,来高效的处理中断。在本文的最后会给出现代Linux内核处理的详细解释和介绍。
注意,在上面的中断是包括了同步中断和异步中断,后面,在 intel
开发手册中,将同步中断定义为异常,异步中断则定义为中断。中断具体指代什么,要在具体的语境中去理解。
异常
异常是CPU在执行指令的时候,探测到的一个反常的问题。异常可以分为三种:故障(fault),陷阱(trap),错误(error)。
异常处理
异常处理的流程可以概括为:程序执行中触发异常 → CPU 根据中断向量跳转 → 自动保存部分上下文并切换到内核态 → 执行trap入口 → 调用C语言trap处理函数 → 执行具体异常处理逻辑 → 恢复上下文 → 返回用户态。
在执行期间,如果发生除零错误、page fault、非法指令或系统调用等情况,CPU会立即响应异常。对于系统调用这种人为触发的trap,也会走统一的异常处理路径。
触发异常后,CPU会自动保存部分关键上下文(如PC、CS、EFLAGS),并切换到内核栈和内核态,再根据中断向量表跳转到对应trap handler入口。
trap handler汇编代码会进一步保存所有通用寄存器内容,并最终调用高级语言编写的trap处理函数(如Linux中的 do_page_fault
等)。处理函数会根据异常类型决定是修复(如分配页框)还是终止进程。
处理完成后,内核恢复用户态上下文,并使用 iret
/sysret
等指令返回用户态,继续程序执行(如果未被终止)。
中断
中断则是由外部的硬件设备所引起的,中断分为两种,可屏蔽中断和不可屏蔽中断。其中,IO设备所产生的中断都是可屏蔽中断。
中断处理
每个能发出中断请求的硬件设备都会通过一条 IRQ 线连接到 PIC(可编程中断控制器)。PIC 监视这些 IRQ 线,当检测到有中断信号时,会将对应的中断向量编号准备好,并通过 INTR 线向 CPU 发送中断请求。
CPU 收到 INTR 信号后,会发出 INTA(中断确认)信号,PIC 会将中断向量号发送到数据总线。CPU 读取该向量号后,用它去中断描述符表(IDT)中查找并跳转到对应的中断服务程序。
后续处理中断的流程(切换内核态、保存上下文、执行 handler、恢复现场)与异常处理类似,但中断源于外设,异常则是程序执行错误引发。
==注:这里的PIC是单一CPU上的概念,现在更多使用的是APIC,即高级可编程中断控制器。即APIC接收所有的IRQ线,然后通过ICC(中断控制通信总线)来在SMP(Symmetric Multi-Processing,对称多处理器)下和其他的CPU通讯==
值得注意的是,并不是所有的IRQ和硬件设备都是一一对应的,IRQ的数量是有限的,会出现一个IRQ映射到多个可以发出中断请求的硬件设备的情况。例如IRQ43既可以分配给声卡,也可以分配给USB端口。而处理这种情况主要有两种方式:
- IRQ共享:每个硬件设备被抽象为一个 IRS(中断请求源),当中断控制器检测到某个 IRQ 线(或 MSI 中断)触发时,它会将对应的中断号传递给 CPU。内核通过中断号在
irq_desc[]
中查找到对应的中断处理程序列表,然后调用已注册的 ISR(中断服务例程)。如果该中断号上注册了多个共享 ISR,则会遍历这些 ISR,以确定哪个设备实际触发了中断。 - IRQ动态分配:引入了虚拟IRQ的概念,一条IRQ线可能在最后时刻才会与一个设备驱动程序相关联。通过动态分配的方式,使得IRQ可以挂载更多的硬件设备,并且支持热插拔。
中断处理程序和异常处理程序的嵌套
首先要明确的一点是,在进行中断处理程序或者异常处理程序的时候,CPU是处于中断上下文的,这个时候禁用了调度器,所以无法发生进程切换。关于中断处理程序和异常处理程序的嵌套:
- 中断处理程序可以被更高级的中断处理程序打断
- 异常处理程序可以被中断处理程序打断,反之则不可以
Linux中的中断处理
在 Linux 内核中,中断处理被设计成一个分层体系,除了硬件触发的硬中断(硬中断处理程序),即中断上下文中的上半部分处理,还包括延迟执行的软中断(SoftIRQ) 和 tasklet,它们被广泛用于中断上下文中的下半部分处理。
部分 | 名称 | 执行位置 | 特点 |
---|---|---|---|
上半部分 | 硬中断处理程序(ISR) | 中断上下文 | 响应快,执行短,不可阻塞 |
下半部分 | 软中断(SoftIRQ)、tasklet、工作队列(workqueue) | 进程上下文或中断上下文(非抢占) | 延迟处理,功能丰富 |
在 Linux 中,中断处理被划分为“上半部分”和“下半部分”两层结构,其中上半部分(即中断服务例程,ISR)主要负责快速响应中断事件,通常只执行一些必要且简短的操作。具体而言,它的核心职责包括:清除中断标志位以避免中断抖动、保存设备状态(如读入寄存器值)、以及调度下半部分处理逻辑的入口函数(如tasklet_schedule()或 raise_softirq())。
由于 ISR 运行在中断上下文中,不允许阻塞或进行复杂处理,Linux 要求其尽可能快速返回,以避免中断屏蔽时间过长、影响系统整体响应性能。
因此,真正的业务逻辑处理——例如网络数据包解析、磁盘数据传输、状态更新等,通常放在下半部分执行。这些延迟执行的处理可以通过软中断(softirq)、tasklet 或工作队列(workqueue)实现,分别适用于不同的实时性、可阻塞性和并发需求。这样设计既保证了中断响应的实时性,也提升了系统的并发处理能力和整体调度灵活性。