這里以除0錯誤這個異常來講解異常處理的機制.
1) 注冊異常處理函數
在系統初始化的時候,調用
trap_init函數注冊異常處理函數.
這個函數里調用set_trap_gate(0,÷_error);注冊除0錯誤的異常由divide_error函數處理,而這個錯誤的中斷向量號是0.
2) 當異常被觸發時,調用原先注冊的divide_error函數.
這個函數的實現的Entry.S文件中:
ENTRY(divide_error)
pushl $0 # no error code
pushl $do_divide_error
ALIGN
error_code:
pushl %ds
pushl %eax
xorl %eax, %eax
pushl %ebp
pushl %edi
pushl %esi
pushl %edx
decl %eax # eax = -1
pushl %ecx
pushl %ebx
cld
movl %es, %ecx
movl ES(%esp), %edi # get the function address
movl ORIG_EAX(%esp), %edx # get the error code
movl %eax, ORIG_EAX(%esp)
movl %ecx, ES(%esp)
movl $(__USER_DS), %ecx
movl %ecx, %ds
movl %ecx, %es
movl %esp,%eax # pt_regs pointer
call *%edi
jmp ret_from_exception
首先,它將真正的處理函數do_divide_error壓入棧中,其實,對于每個異常而言,真正的處理函數都是名為"do_注冊函數"的函數.
緊跟著,將一些需要保存的寄存器也壓入棧中.
接著,由于處理函數的地址已經在edi寄存器中了,調用call *%edi調用處理函數.
當處理完畢之后,調用函數ret_from_exception從異常處理中返回.
上面是大致的流程,下面詳細看看do_divide_error函數做了什么.
這個函數的實現在文件trap.c中:
#define DO_VM86_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr) \
fastcall void do_##name(struct pt_regs * regs, long error_code) \
{ \
siginfo_t info; \
info.si_signo = signr; \
info.si_errno = 0; \
info.si_code = sicode; \
info.si_addr = (void __user *)siaddr; \
if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr) \
== NOTIFY_STOP) \
return; \
do_trap(trapnr, signr, str, 1, regs, error_code, &info); \
}
DO_VM86_ERROR_INFO( 0, SIGFPE, "divide error", divide_error, FPE_INTDIV, regs->eip)
可以看到,最終這個函數會走到do_trap函數中,接著看這個函數中的代碼片段:
trap_signal: {
struct task_struct *tsk = current;
tsk->thread.error_code = error_code;
tsk->thread.trap_no = trapnr;
if (info)
force_sig_info(signr, info, tsk);
else
force_sig(signr, tsk);
return;
}
首先得到當前進程的指針,在進程結構體的thread結構體中保存error_code和trapnr,也就是錯誤號和中斷向量.
接著調用force_sig_info函數,可以跟進這個函數,其實最終要做的就是將該異常以信號量的形式加入到當前進程的信號集合中,也就是給當前進程發送信號,告訴進程有異常被觸發了,需要處理.以除0錯誤來看,這個信號量是SIGFPE.