中断的定义
中断分为两类:
- 同步中断,由CPU内部错误生成 - exception(异常)
异常又分为:
- fault (错误):可纠正的错误,比如page fault。发生异常后,执行完异常的处理函数后,返回到发生错误的那条指令,重新执行。
- trap(陷阱):在陷阱指令执行后立即报告;用于调试,GDB的实现依赖。在这种情况下,中断信号的作用是通知调试程序一条特殊指令已被执行。之后会返回到下一条指令。
- abort(终止):严重错误,比如硬件错误等,发生了这种异常,就切换到异常中止处理程序,中止当前进程。
- Programmed exception:应用程序主动请求发出的,由指令int/int3触发。比如:用户程序调用系统调用时,执行int指令陷入内核。
- 异步中断,来自设备的外部中断 - interrupt(中断)
中断分为:
- 可屏蔽中断:来自IO的中断都是可屏蔽的
- 不可屏蔽中断:NMI,一般是硬件故障。

|
类别 |
原因 |
异步/同步 |
返回行为 |
|
中断 |
来自I/O设备的信号 |
异步 |
总是返回到下一条指令 |
|
陷阱 |
有意的异常 |
同步 |
总是返回到下一条指令 |
|
故障 |
潜在可恢复的错误 |
同步 |
返回到当前指令 |
|
终止 |
不可恢复的错误 |
同步 |
不会返回 |
中断向量
每个中断或者异常由0-255的数来标识,这就是中断向量。NMI和exception的中断向量是固定的,可屏蔽中断的中断向量是变化的。
x86发布了20多种异常,这些异常的中断向量是固定的,定义在CPU的SPEC中,见Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 1 6.5.1 Call and Return Operation for Interrupt or Exception Handling Procedures。
每种异常都对应一个异常处理函数。定义在arch/x86/include/asm/idtentry.h 中。
每个异常处理函数执行时,会给引起异常的进程发一个信号。
|
向量号 |
异常代号 |
描述 |
来源 |
异常处理函数 |
信号 |
|
0 |
#DE |
Divide Error |
DIV and IDIV instructions. |
divide_error() |
SIGFPE |
|
1 |
#DB |
Debug |
Any code or data referenc. |
debug() |
SIGTRAP |
|
2 |
NMI Interrupt |
Non-maskable external interrupt. |
nmi() |
None | |
|
3 |
#BP |
Breakpoint |
INT3 instruction. |
int3() |
SIGTRAP |
|
4 |
#OF |
Overflow |
INTO instruction. |
overflow() |
SIGSEGV |
|
5 |
#BR |
BOUND Range Exceeded |
BOUND instruction. |
bounds() |
SIGSEGV |
|
6 |
#UD |
Invalid Opcode (Undefined Opcode) |
UD instruction or reserved opcode. |
invalid_op() |
SIGILL |
|
7 |
#NM |
Device Not Available (No Math Coprocessor) |
Floating-point or WAIT/FWAIT instruction. |
device_not_available() |
None |
|
8 |
#DF |
Double Fault |
Any instruction that can generate an exception, an NMI, or an INTR. |
doublefault_fn() |
None |
|
9 |
#MF |
CoProcessor Segment Overrun (reserved) |
Floating-point instruction. |
coprocessor_segment_overrun() |
SIGPE |
|
10 |
#TS |
Invalid TSS |
Task switch or TSS access. |
invalid_tss() |
SIGSEGV |
|
11 |
#NP |
Segment Not Present |
Loading segment registers or accessing system segments. |
segment_not_present() |
SIGBUS |
|
12 |
#SS |
Stack Segment Fault |
Stack operations and SS register loads. |
stack_segment |
SIGBUS |
|
13 |
#GP |
General Protection |
Any memory reference and other protection checks. |
general_protection() |
SIGSEGV |
|
14 |
#PF |
Page Fault |
Any memory reference. |
page_fault() |
SIGSEGV |
|
15 |
Reserved | ||||
|
16 |
#MF |
Floating-Point Error (Math Fault) |
Floating-point or WAIT/FWAIT instruction. |
coprocessor_error() |
SIGFPE |
|
17 |
#AC |
Alignment Check |
Any data reference in memory. |
alignment_check() |
SIGSEGV |
|
18 |
#MC |
Machine Check |
Error codes (if any) and source are model dependent. |
machine_check() |
None |
|
19 |
#XM |
SIMD Floating-Point Exception |
SIMD Floating-Point Instruction |
simd_coprocessor_error() |
SIGFPE |
|
20 |
#VE |
Virtualization Exception |
EPT violations |
virtualization_exception() | |
|
21 |
#CP |
Control Protection Exception |
The RET, IRET, RSTORSSP, and SETSSBSY instructions can generate this exception. When CET indirect branch tracking is enabled, this exception can be generated due to a missing ENDBRANCH instruction at the target of an indirect call or jump. |
control_protection() | |
|
22-31 |
Reserved | ||||
|
32-255 |
Maskable Interrupts |
External interrupt from INTR pin or INT n instruction. |
硬件结构
- 外部PIC芯片(早期架构)
PIC:可编程中断控制器(Programmable Interrupt),x86的PIC是独立于CPU的一块芯片,嵌入式领域的SoC一般在芯片内集成。
外部设备连接到PIC上,PIC的INT引脚连接CPU的INTR引脚。

- 外设将RQ信号线连接到PIC的输入引脚(IRQ0-IRQ7)。
- 当外设发起中断时,它通过自己的IRQ线向PIC发送一个电信号。
- PIC接收这个请求,决定其优先级。
- PIC然后通过INTR,Interrupt Request向CPU发送一个信号,告诉CPU“有一个中断需要处理”。
- CPU在执行完当前指令后,如果允许中断,会通过另一条线INTA(Interrupt Acknowledge)向PIC发送一个确认信号。
- PIC收到确认后,会将该设备对应的中断向量号放到数据总线上。
- CPU读取这个向量号,然后在中断描述符表(IDT) 中查找对应的条目,找到处理这个中断的服务函数地址,并跳转过去执行。
- 执行完毕后,会通知PIC中断处理已完成。
- APIC(现代架构)
为了支持多处理器系统(SMP),APIC产生了。APIC包含两个部分:
- LAPIC:本地APIC,内置在CPU中。每个CPU core有一个专属的LAPIC,负责处理
- CPU本地产生的中断,如IPI,定时器中断等
- 接收I/O APIC发来的中断消息,并将其传递给core执行。
- I/O APIC:CPU外置的一个芯片。负责收集所有外设的中断请求,将中断以消息的形式发送给一个或多个core的LAPIC。

中断描述符表(IDT)
IDT, Interrupt Descriptor Table。当中断或者异常来临,CPU通过中断号从IDT中查找到对应的entry得到中断或异常的处理函数的入口地址。
IDT可以放在内存的任何地方,把IDT的基地址填入CPU的Interrupt Descriptor Table Register(中断描述表寄存器),CPU就能找到它。内核启动后,在允许中断发生前,必须使用 lidt 汇编指令初始化IDT。
Linux对IDT的初始化的代码
在arch/x86/kernel/head_64.S中:

startup_64_setup_gdt_idt定义在arch/x86/boot/startup/gdt_idt.c中。它的任务是在内核切换到虚拟地址运行前,设置GDT和IDT。调用startup_64_load_idt对IDT进行设置。

创建好描述符然后调用native_load_idt,使用lidt指令初始化idtr。

门描述符(Gate Descriptors)
参考Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 6.11 IDT DESCRIPTORS.
Intel为了让控制CPU在不同的特权级对代码段的访问,CPU提供了4种门描述符,IDT可能包含除call gate外的三种门描述符。
- Call gates(调用门):可能存在于GDT和LDT中,不能存在于IDT中。这里跳过。
- Trap gates(陷阱门):和中断门一样,不过用于处理异常。
- Interrupt gates(中断门):用于中断处理,当中断来到,跳转到描述符中的处理函数的地址执行。
- Task gates(任务门):用于task切换。当中断来临用一个进程取代当前的进程。

Linux下对门描述符的细分
刚刚说的那三个类型的中断描述符是Intel制定的,Linux在这些描述符上进行了二创,并进行了更细的分类。
- 中断门(interrupt gate):本质是Intel中断门,DPL(标识特权级)字段为0。Linux所有的中断处理程序都通过中断门激活,并全部在内核态。
- 系统门(system interrupt gate):本质是Intel中断门,DPL为3。用户态可以访问。通过系统门可以激活3个Linux异常处理程序,它们的向量是3、4和128(0x80)。所以在用户态下,可以通过into、bound、int 0x80陷入内核。
- Interrupt gate with interrupt stack:本质是Intel中断门,DPL为0,ist不为0。
问:啥是IST?
- 任务门(Task gate):本质是Intel任务门,DPL为0。对Double fault异常是通过任务门触发的。



被折叠的 条评论
为什么被折叠?



