这里以除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.