中断可分为同步(synchronous)中断和异步(asynchronous)中断:
1. 同步中断是当指令执行时由 CPU 控制单元产生,之所以称为同步,是因为只有在一条指令执行完毕后 CPU 才会发出中断,而不是发生在代码指令执行期间,比如系统调用。
2. 异步中断是指由其他硬件设备依照 CPU 时钟信号随机产生,即意味着中断能够在指令之间发生,例如键盘中断。
根据 Intel 官方资料,同步中断称为异常(exception),异步中断被称为中断(interrupt)。
中断可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。异常可分为故障(fault)、陷阱(trap)、终止(abort)三类。
从广义上讲,中断可分为四类:中断、故障、陷阱、终止。这些类别之间的异同点请参看 表 1。
表 1:中断类别及其行为
类别
原因
异步/同步
返回行为
中断
来自I/O设备的信号
异步
总是返回到下一条指令
陷阱
有意的异常
同步
总是返回到下一条指令
故障
潜在可恢复的错误
同步
返回到当前指令
终止
不可恢复的错误
同步
不会返回
X86 体系结构的每个中断都被赋予一个唯一的编号或者向量(8 位无符号整数)。非屏蔽中断和异常向量是固定的,而可屏蔽中断向量可以通过对中断控制器的编程来改变。
Linux 2.6 中断处理原理简介
中断描述符表(Interrupt Descriptor Table,IDT)是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中存放的是相应的中断或异常处理程序的入口地址。内核在允许中断发生前,也就是在系统初始化时,必须把 IDT 表的初始化地址装载到 idtr 寄存器中,初始化表中的每一项。
当处于实模式下时,IDT 被初始化并由 BIOS 程序所使用。然而,一旦 Linux 开始接管,IDT 就被移到 arm 的另一个区域,并进行第二次初始化,因为 Linux 不使用任何 BIOS 程序,而使用自己专门的中断服务程序(例程)(interrupt service routine,ISR)。中断和异常处理程序很像常规的 C 函数
有三个主要的数据结构包含了与 IRQ 相关的所有信息:hw_interrupt_type、irq_desc_t 和 irqaction,图3 解释了它们之间是如何关联的。
图 3:IRQ 结构之间的关系
在 X86 系统中,对于 8259A 和 I/O APIC 这两种不同类型的中断控制器,hw_interrupt_type 结构体被赋予不同的值,具体区别参见表 2。
表 2:8259A 和 I/O APIC PIC 的区别
8259A
I/O APIC
static struct hw_interrupt_type i8259A_irq_type = { "XT-PIC", startup_8259A_irq, shutdown_8259A_irq, enable_8259A_irq, disable_8259A_irq, mask_and_ack_8259A, end_8259A_irq, NULL };
static struct hw_interrupt_type ioapic_edge_type = { .typename = "IO-APIC-edge", .startup = startup_edge_ioapic, .shutdown = shutdown_edge_ioapic, .enable = enable_edge_ioapic, .disable = disable_edge_ioapic, .ack = ack_edge_ioapic, .end = end_edge_ioapic, .set_affinity = set_ioapic_affinity, }; static struct hw_interrupt_type ioapic_level_type = { .typename = "IO-APIC-level", .startup = startup_level_ioapic, .shutdown = shutdown_level_ioapic, .enable = enable_level_ioapic, .disable = disable_level_ioapic, .ack = mask_and_ack_level_ioapic, .end = end_level_ioapic, .set_affinity = set_ioapic_affinity, };
在中断初始化阶段,调用 hw_interrupt_type 类型的变量初始化 irq_desc_t 结构中的 handle 成员。在早期的系统中使用级联的8259A,所以将用 i8259A_irq_type 来进行初始化,而对于SMP系统来说,要么以 ioapic_edge_type,或以 ioapic_level_type 来初始化 handle 变量。
对于每一个外设,要么以静态(声明为 static 类型的全局变量)或动态(调用 request_irq 函数)的方式向 Linux 内核注册中断处理程序。不管以何种方式注册,都会声明或分配一块 irqaction 结构(其中 handler 指向中断服务程序),然后调用 setup_irq() 函数,将 irq_desc_t 和 irqaction 联系起来。
当中断发生时,通过中断描述符表 IDT 获取中断服务程序入口地址,对于 32≤ i ≤255(i≠128) 之间的中断向量,将会执行 push $i-256,jmp common_interrupt 指令。随之将调用 do_IRQ() 函数,以中断向量为 irq_desc[] 结构的下标,获取 action 的指针,然后调用 handler 所指向的中断服务程序。