所有异常处理程序被调用的方式比较相似,因此,我们用handler_name来表示一个通用的异常处理程序的名字(实际名字都出现在表3.1中)。进入异常处理程序的汇编指令在arch/I386/kernel/entry.S中:
handler_name:
pushl $0 /* only for some exceptions */
pushl $do_handler_name
jmp error_code
例如: overflow:
pushl $0
pushl $ do_overflow
jmp error_code
当异常发生时,如果控制单元没有自动地把一个硬件错误代码插入到栈中,相应的汇编语言片段会包含一条pushl $0指令,在栈中垫上一个空值,如果错误码已经被压入堆栈,则没有这条指令。然后,把异常处理函数的地址压进栈中;函数的名字由异常处理程序名与do_前缀组成。
标号为error_code的汇编语言片段对所有的异常处理程序都是相同的,除了“设备不可用”这一个异常。这段代码为实际上是为异常处理程序的调用和返回进行相关的操作,代码如下:
error_code:
pushl %ds
pushl %eax
xorl %eax,%eax
pushl %ebp
pushl %edi 把C函数可能用到的寄存器都保存在栈中
pushl %esi
pushl %edx
decl %eax
#eax = -1
pushl %ecx
pushl %ebx
cld
# 清eflags的方向标志,以确保edi和esi寄存器的值自动增加
movl %es,%ecx
movl ORIG_EAX(%esp), %esi # get the
error code, ORIG_EAX= 0x24
movl ES(%esp),
%edi
# get the function address, ES = 0x20
movl %eax, ORIG_EAX(%esp) #把栈中的这个位置置为-1
movl %ecx, ES(%esp)
movl %esp,%edx
pushl
%esi
# push the error code
pushl %edx
# push the pt_regs pointer
movl $(__KERNEL_DS),%edx
movl %edx,%ds
#把内核数据段选择符装入ds寄存器
movl %edx,%es
GET_CURRENT(%ebx)
#ebx中存放当前进程task_struct结构的地址
call *%edi #调用这个异常处理程序
addl $8,%esp
jmp ret_from_exception
如图3.6给出从用户进程进入异常处理程序时内核堆栈的示意图:
+0x38 用户栈的SS
+0x34 用户栈的ESP 用户堆栈指针
+0x30 EFLAGS
+0x
+0x28 EIP
返回到用户态的地址
+0x24 错误码或0
+0x20 函数地址
+0x
+0x18 EAX
+0x14 EBP
+0x10 EDI
+0XC ESI
+8 EDX
+4 ECX
内核堆栈指针ESP 0 EBX
(a) 进入异常处理程序时内核堆栈示意图
+0x38 用户栈的SS
+0x34 用户栈的ESP
+0x30 EFLAGS
+0x
+0x28 EIP
+0x24 -1
+0x20 ES
+0x
+0x18 EAX
+0x14 EBP
+0x10 EDI
+0XC ESI
+8 EDX
+4 ECX
0 EBX
错误码
内核堆栈指针ESP
ESP
(b) 异常处理程序被调用后堆栈的示意图
图3.6 进入异常后内核堆栈的变化