8Ox86微处理器发布了大约20种不同的异常,内核必须为每一种异常提供一个异常处理程序。对于某些异常,CPU控制单元在开始执行异常处理程序前会产生一个硬件出错码(hardwar eerror code) , 并且压入内核态堆钱。(见本文最后面的几张大图)
CPU的大部分异常都被Linux解释为出错条件。
当其中一个异常发生肘,内核就向引起异常的进程发送一个信号向色通知一个反常条件.例如,如果进程执行了一个被0除的操作,CPU就产生一个"Devde error" 异常,并由相应的异常处理程序向当前进程发送一个SIGFPE信号,这个进程将采取若干必要的步骤来(从出错中)恢复或者中
止运行(如果没有为这个信号设置处理程序的话)。
但是,在两种情况下,Linux利用CPU异常更有效地管理硬件资源。一种是,"Device not availeble" 异常与crO寄存器的TS标志一起用来把新值装入浮点寄存器。第二种情况是"page Fault"异常,该异常推迟给进程分配新的页框,直到不能再推迟为止.
异常处理程序有一个标准的结构,由以下三部分组成:
1. 在内核堆校中保存大多数寄存器的内容(这部分用汇编语言实现)。
2. 用高级的C函数处理异常.
3. 通过ret_from_exception()函数从异常处理程序退出.
为了利用异常,必须对IDT进行适当的初始化,使得每个被确认的异常都有一个异常处理程序. trap_init ()函数的工作是将一些最终值(即处理异常的函数)插入到IDT的非屏蔽中断及异常表项中.这是由函数set_trap_gate() 、 set_intr…gate() 、set_system_gate()、 set_system_intr_gate()和set_task_gate( )来完成的.
void __init trap_init(void)
{
......
set_intr_gate(0, &devide_error);
set_intr_gate_ist(1, &debug, DEBUG_STACK);
set_intr_gate_ist(2, &nmi, NMI_STACK);
/* int3 can be called from all */
set_system_intr_gate_ist(3, &int3, DEBUG_STACK);
/* int4 can be called from all */
set_system_intr_gate(4, &overflow);
set_intr_gate(5, &bounds);
set_intr_gate(6, &invalid_op);
set_intr_gate(7, &device_not_available);
#ifdef CONFIG_X86_32
set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);
#else
set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK);
#endif
set_intr_gate(9, &coprocessor_segment_overrun);
set_intr_gate(10, &invalid_TSS);
set_intr_gate(11, &segment_not_present);
set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK);
set_intr_gate(13, &general_protection);
set_intr_gate(14, &page_fault);
set_intr_gate(15, &spurious_interrupt_bug);
set_intr_gate(16, &coprocessor_error);
set_intr_gate(17, &alignment_check);
#ifdef CONFIG_X86_MCE
set_intr_gate_ist(18, &machine_check, MCE_STACK);
#endif
set_intr_gate(19, &simd_coprocessor_error);
/* Reserve all the builtin and the syscall vector: */
for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
set_bit(i, used_vectors);
......
}
ENTRY(handler_name)
RING0_INT_FRAME
pushl $0 # no error code
CFI_ADJUST_CFA_OFFSET 4 //可能不一定是4这里
pushl $do_handler_name
CFI_ADJUST_CFA_OFFSET 4
jmp error_code
CFI_ENDPROC
END(handler_name)
如divide_error异常:
ENTRY(divide_error)
RING0_INT_FRAME
pushl $0 # no error code
CFI_ADJUST_CFA_OFFSET 4
pushl $do_divide_error
CFI_ADJUST_CFA_OFFSET 4
jmp error_code
CFI_ENDPROC
END(divide_error)
当异常发生时,如果控制单元没有自动地把一个硬件出错代码插入到战中,相应的汇编语言片段会包含一条pushl $0指令,在战中垫上一个空值.然后,把高级C函数的地址压进枝中,它的名字由异常处理程序名与do一前强组成.
标号为error_code的汇编语言片段对所有的异常处理程序都是相同的,除了"Oevicenot avaiJabJe" 这一个异常。这段代码执行以下步骤:
如前所述,执行异常处理程序的C函数名总是由do_前缀和处理程序名组成.其中的大部分函数把硬件出错码和异常向量保存在当前进程的描述符中,然后向当前进程发送一个适当的信号.用代码描述如下
current->thread.trap_no = 19;
current->thread.error_code = error_code;
die_if_kernel("cache flush denied", regs, error_code);
异常处理程序刚一终止,当前进程就关注这个信号.i主信号要么在用户态由进程自己的信号处理程序(如果存在的话)来处理.要么由
内核来处理。