《深入理解Linux内核》笔记3 - 中断和异常
中断和异常的区别:
- 同步中断(称为异常),由CPU控制单元产生,只有在执行某一条指令后才会触发异常。
- 异步中断,由外部硬件产生,如硬盘、网络等。
产生异常的原因以下两种:
- 程序错误,这种情况下内核通常发送信号进程。
- 需要内核处理的异常条件,如缺页、系统调用(
int
和sysenter
指令)。
中断信号的作用⌗
中断和异常⌗
中断(interrupt):
- 可屏蔽中断(maskable interrupt),控制单元可以忽略中断,I/O设备产生的中断请求都可屏蔽。
- 不可屏蔽中断(nonmaskable interrupt, NMI),危急事件(例如硬件故障)引起的中断。
异常(expception):
- 处理器探测异常(processor-detected exception),CPU执行指令时检测到的异常。
- 故障(fault),可以纠正,纠正后重新执行指令。例如page fault。
- 陷阱(trap),主要为了调试程序,当内核把控制权返回给应用程序后,程序继续执行下一条指令。
- 终止(abort),严重错误,例如控制单元故障,强制终止程序。
- 编程异常(programmed exception),在应用程序发出请求时发生,由
int
或int3
指令触发。或者由into
指令检查溢出或bound
指令检查边界触发。控制单元把programmed exception作为trap处理,也叫做软件中断(software interrupt)。
IRQ和中断⌗
每个能够发出的中断请求的硬件设备控制器都有一条名为IRQ(interrupt request)的输出线,与可编程中断控制器(programmable interrupt controller,PIC,现在已经被淘汰)的输入引脚相连。
IRQ线从0开始编号,例如第一条线表示为IRQ0
,默认关联到中断向量的第n+32项。
可以有选择地禁止或激活每条IRQ线,禁止的中断不会丢失。
有选择的激活、禁止IRQ线不同于全局屏蔽、非屏蔽中断,当eflags
寄存器的IF
标志清0时,所有的中断都会被屏蔽,当eflags
寄存器的IF
标志清1时,所有的中断都会被激活。用cli
清除标志,sti
设置标志。
高级可编程中断控制器 APIC⌗
为了支持SMP体系结构,每个CPU有一个本地APIC,所有本地APIC都连接到一个外部的I/O APIC,形成一个多APIC系统。支持中断分发和处理器间中断(interprocessor interrupt)。
异常⌗
中断向量的0~31项预留给了处理器硬件异常,已经定义的有大约20项。
IVT Offset | INT # | Description | 信号 |
---|---|---|---|
0x0000 | 0x00 (0) | Divide by 0 | SIGFPE |
0x0004 | 0x01 (1) | Reserved | SIGTRAP |
0x0008 | 0x02 (2) | NMI Interrupt | |
0x000C | 0x03 (3) | Breakpoint (INT3) | SIGTRAP |
0x0010 | 0x04 (4) | Overflow (INTO) | SIGSEGV |
0x0014 | 0x05 (5) | Bounds range exceeded (BOUND) | SIGSEGV |
0x0018 | 0x06 (6) | Invalid opcode (UD2) | SIGILL |
0x001C | 0x07 (7) | Device not available (WAIT/FWAIT) | |
0x0020 | 0x08 (8) | Double fault | |
0x0024 | 0x09 (9) | Coprocessor segment overrun | SIGFPE |
0x0028 | 0x0A (10) | Invalid TSS | SIGSEGV |
0x002C | 0x0B (11) | Segment not present | SIGBUS |
0x0030 | 0x0C (12) | Stack-segment fault | SIGBUS |
0x0034 | 0x0D (13) | General protection fault | SIGSEGV |
0x0038 | 0x0E (14) | Page fault | SIGSEGV |
0x003C | 0x0F (15) | Reserved | |
0x0040 | 0x10 (16) | x87 FPU error | SIGFPE |
0x0044 | 0x11 (17) | Alignment check | SIGSEGV |
0x0048 | 0x12 (18) | Machine check | |
0x004C | 0x13 (19) | SIMD Floating-Point Exception | SIGFPE |
0x00xx | 0x14-0x1F (20-31) | Reserved | |
0x0xxx | 0x20-0xFF (32-255) | User definable | |
0x0080 | 0x80 | System call |
完整的定义可以看下面的trap_init
函数。
中断描述符表 Interrupt descriptor table IDT⌗
- IDT由256项组成,其中每项都是一个中断描述符,8字节,总共
8*256=2048
字节。 - IDT可以位于内存中的任何地方。
- IDT的基址和最大长度存在
idtr
寄存器中。 - 在允许中断前,必须用
lidt
指令初始化idtr
。
idt_table
定义如下:
// include/asm/processor.h
struct desc_struct {
unsigned long a,b;
};
// arch/i386/kernel/traps.c
struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, }; // 长度为256的IDT表
中断描述符定义如下图所示:
- 每个描述符占8字节,64位。
- 40~43位表示描述符gate的类型。
- 2位DPL表示特权级,通常都为0,表示内核态。
Intel提供3种类型的中断描述符,分为3种gate:
- task gate:当中断发生时,必须取代当前进程的那个进程的TSS segment selector放在task gate中。这个gate用来处理
Double Fault
异常。 - interrupt gate:包含16位segment selector和32位offset,当控制权转移到一个合适的段时,处理器清IF标志关中断。这个gate用来处理中断。
- trap gate:和interrupt gate类似,区别是不修改IF标志。这个gate用来处理异常。
中断和异常处理程序的嵌套执行⌗
2种处理程序:
- 异常处理程序
- 中断处理程序
- 每个中断或异常都会引起一个内核控制路径(kernel control flow),内核控制路径代表当前进程在内核态执行单独的指令序列。至于为什么中断一定要在内核态处理(interrupt gate 中断门),可能是出于安全的考量,可以看知乎上的这篇回答,理论上可以将中断描述符的DPL设置成3用户态,但是没有操作系统这么做。
- 内核控制路径可以任意嵌套,一个中断处理程序可以被另一个中断处理程序打断。所以当嵌套层数>1时,中断处理程序的最后一部分指令不能使当前进程返回用户态。
- 允许内核控制路径嵌套的代价,中断处理程序必须永不阻塞(不能发生进程切换)。
- 大多数异常只在用户态发生,异常要么由编程错误引起,要么由调试程序触发。
- 内核态能触发的唯一异常是缺页异常page fault。缺页异常处理程序挂起当前进程,从不进一步引发异常。,所以和异常相关的内核处理路径最多会有两个叠在一起(系统调用和缺页异常)。
- 中断处理程序和当前运行的进程无关。
- 中断处理程序可以抢占其他中断处理程序,也可以抢占异常处理程序。异常处理程序不能抢占中断处理程序。
- 中断处理程序不能执行导致缺页的操作。
- 多处理器系统上,多个内核控制路径可以并发执行。
初始化IDT⌗
Linux将异常和中断分为5种gate,不同于Intel的3种:
- interrupt gate,中断门,Linux系统所有中断都通过中断门来处理,限制在内核态,DPL字段为0。
void set_intr_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,14,0,addr,__KERNEL_CS); // 0表示内核态
}
// 例子
set_intr_gate(1,&debug);
set_intr_gate(2,&nmi);
- system interrupt gate,系统中断门,用户态进程可以访问的Intel中断门,,DPL字段为3。用于注册中断向量3的处理函数
int3
,由汇编指令int3
触发。
static inline void set_system_intr_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n, 14, 3, addr, __KERNEL_CS); // 3表示用户态
}
// 例子
set_system_intr_gate(3, &int3);
- system gate,系统门,用户态可以访问的陷阱门,DPL字段为3。用来注册中断向量4(
into
指令)、5(bound
指令)、128(int $0x80
指令,系统调用)的处理函数。
static void __init set_system_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,15,3,addr,__KERNEL_CS);
}
// 例子
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_system_gate(SYSCALL_VECTOR,&system_call);
- trap gate,陷阱门,只有内核态可以访问,DPL字段为0。大部分的Linux异常都注册位陷阱门。
static void __init set_trap_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,15,0,addr,__KERNEL_CS);
}
// 例子
set_trap_gate(0,÷_error);
- task gate,任务门,只有内核态可以访问,,DPL字段为0。用来注册
Double fault
异常的处理函数。
static void __init set_task_gate(unsigned int n, unsigned int gdt_entry)
{
_set_gate(idt_table+n,5,0,0,(gdt_entry<<3));
}
// 例子
set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);
gate | DPL | 例子 |
---|---|---|
interrupt gate | 0 | 所有中断处理程序,set_intr_gate(2,&nmi); |
system interrupt gate | 3 | set_system_intr_gate(3, &int3); |
system gate | 3 | set_system_gate(SYSCALL_VECTOR,&system_call); |
trap gate | 0 | set_trap_gate(0,÷_error); |
task gate | 0 | set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS); |
异常处理⌗
略
中断处理⌗
中断向量⌗
中断和异常由8 bit整数表示,这个整数称为中断向量,范围0~255。
trap_init
函数:
// arch/i386/kernel/traps.c
void __init trap_init(void)
{
set_trap_gate(0,÷_error);
set_intr_gate(1,&debug);
set_intr_gate(2,&nmi);
set_system_intr_gate(3, &int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_intr_gate(14,&page_fault);
set_trap_gate(15,&spurious_interrupt_bug);
set_trap_gate(16,&coprocessor_error);
set_trap_gate(17,&alignment_check);
set_trap_gate(18,&machine_check);
set_trap_gate(19,&simd_coprocessor_error);
set_system_gate(SYSCALL_VECTOR,&system_call);
cpu_init();
trap_init_hook();
}
参考:
- https://blog.csdn.net/qq_39376747/article/details/113736525?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165011655316780264030832%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165011655316780264030832&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-113736525.142^v9^pc_search_result_cache,157^v4^new_style&utm_term=%E9%AB%98%E7%BA%A7%E5%8F%AF%E7%BC%96%E7%A8%8B%E4%B8%AD%E6%96%AD%E6%8E%A7%E5%88%B6%E5%99%A8&spm=1018.2226.3001.4187
- https://zhuanlan.zhihu.com/p/26464793
- OS Dev: https://wiki.osdev.org/Interrupt_Vector_Table
- https://blog.csdn.net/cwcmcw/article/details/21640363
其他内容