异常处理

CPU产生的大部分异常都是由Linux解释为出错条件。当其中一个异常发生时,内核就向引起异常的进程发送一个信号向它通知一个反常条件。例如,如果进程执行了一个被0除的操作,CPU就产生了一个”Divideerror”异常,并由相应的异常处理程序向当前进程发送一个SIGFPE信号,这个进程将采取若干必要的步骤来恢复或者中止运行。

但是,在两种情况下,Linux利用CPU异常更有效地管理硬件资源。第一种情况已经在第三章”保存和加载FPUMMXXMM寄存器”一节描述过,”Devicenot availeble”异常与cr0寄存器的TS标志一起用来把新值装入浮点寄存器。第二种情况指的是”PageFault”异常,该异常推迟给进程分配新的页框,直到不能再推迟为止。相应的处理程序比较复杂,因为异常可能表示一个错误条件,也可能不表示一个错误条件。

异常处理程序有一个标准的结构,由以下三部分组成:

  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()来完成的。


由于”Doublefault”异常表示内核有严重的非法操作,其处理是通过任务门而不是陷阱门或系统门来完成的,因而,试图显示寄存器值的异常处理程序并不确定esp寄存器的值是否正确。产生这种异常的时候,CPU取出存放在IDT8项中的任务门描述符,该描述符指向存放在GDT表第32项中TSS段描述符。然后,CPUTSS段中的相关值装载eipesp寄存器,结果是:处理器在自己的私有栈上执行doublefault_fn()异常处理函数。

现在我们要考察一旦一个典型的异常处理程序被调用,它会做些什么。由于篇幅所限,我们对异常处理仅做粗略的描述,尤其是我们不涉及下面的内容:

  1. 由一些处理函数发送给用户态进程的信号码。

  2. 内核运行在MSDOS虚拟模式时产生的异常,它们的处理是不同的。

  3. Debug”异常


为异常处理程序保存寄存器的值


让我们用handler_name来表示一个通用的异常处理程序的名字。每一个异常处理程序都以下列的汇编指令开始:

handle_name:

pushl$0

pushl$do_handler_name

jmperror_code

当异常发生时,如果控制单元没有自动地把一个硬件出错代码插入到栈中,相应的汇编语言片段会包含一条pushl$0指令,在栈中垫上一个空值。然后,把高级C函数的地址压栈中,它的名字由异常处理程序名与do_前缀组成。

标号为error_code的汇编语言片段对所有的异常处理程序都是相同的。除了”Devicenot available”这一个异常。这段代码执行以下步骤:

  1. 把高级C函数可能用到的寄存器保存在栈中。

  2. 产生一条cld指令来清eflags的方向标志DF,以确保调用字符串指令时会自动增加ediesi寄存器的值。

  3. 把栈中位于esp+36处的硬件出错码拷贝到edx中,给栈中这一位置存上值-1,正如我们将在第十一章的”系统调用的重新执行”一节中所看到的哪样,这个值用来把0x80异常与其它异常隔离开。

  4. 把保存在栈中esp32位置的do_handler_name()高级C函数的地址装入edi寄存器中,然后,在栈的这个位置写入es的值。

  5. 把内核栈的当前栈顶拷贝到eax寄存器。这个地址表示内存单元的地址,在这个单元中存放的是第1步所保存的最后一个寄存器的值。

  6. 把用户数据段的选择符拷贝到dses寄存器中。

  7. 调用地址在edi中的高级C函数。

被调用的函数从eaxedx寄存器而不是从栈中接收参数。我们已经遇见过一个从CPU寄存器获取参数的函数__switch_to(),在第三章”执行进程切换”一节我们讨论过这个函数。


进入和离开异常处理程序

如前所述,执行异常处理程序的C函数名总是由do_前缀和处理程序名组成。其中的大部分函数把硬件出错码和异常向量保存在当前进程的描述符中,然后,向当前进程发送一个适当的信号。用代码描述如下:

current->thread.error_code= error_code

current->thread.trap_no= vector;

force_sig(sig_number,current);

异常处理程序刚一终止,当前进程就关注这个信号。该信号要么在用户态由进程自己的信号处理程序来处理,要么由内核来处理。在后面这种情况下,内核一般会杀死这个进程。异常处理程序发送的信号已在表4-1中列出。

异常处理程序总是检查异常是发生在用户态还是在内核态,在后一种情况下,还要检查是否由系统调用的无效参数引起,我们将在第十章“动态地址检查:修正代码”一节描述内核如何防御自己受无效的系统调用参数攻击。出现在内核态的任何其它异常都是由于内核的bug引起的。在这种情况下,异常处理程序认为是内核行为失常了。为了避免硬盘上的数据崩溃,处理程序调用die()函数,该函数在控制台上打印出所有CPU寄存器的内容,并调用do_exit()来终止当前进程。

当执行异常处理的C函数终止时,程序执行一条jmp指令以跳转到ret_from_exception()函数。这个函数将在后面的”从中断和异常返回”一节中进行描述。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
疯狂Java讲义课后题 自己编写的 对于这个程序,最关键的在于判断是否连成5个棋子 一般的棋盘大小为15*15, 用大小为[15][15]的二维数组来盛放棋盘 要判断是否连成5个棋子,就要对棋子的周围进行遍历 这里规定遍历的方式为(左,右),(上,下),(左上,右下),(右上,左下) 4种最特殊的情况:  左上角:只进行右,下,右下  左下角:只进行右,上,右上  右上角:只进行左,下,左下  右下角:只进行左,上,左上 4种特殊情况(不含4个角):  上边界:不进行上遍历  下边界:不进行下遍历  左边界:不进行左遍历  右边界:不进行右遍历 对其他位置的遍历则为(左,右),(上,下),(左上,右下),(右上,左下) 每下一个棋子,都要判断它所处的位置来选择遍历的方式且每次遍历时都要判断是否到达边界,以防数组越界 这样的话,现实起来就会挺复杂 所以改进的方法是这样的: 用大小为[17][17]的二维数组来盛放棋盘 这样就可对每个位置的棋子用相同的遍历方式来遍历,即(左,右),(上,下),(左上,右下),(右上,左下)。并且不需要判断是否到达边界,大大降低了复杂性。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 棋盘为如图所示的那么大。而实际上程序中规定的“棋盘”为中间的部分(即棋子只能摆在从1-15的位置上,否则会被程序告知摆放的位置不对),这样一来,不管棋子位于“棋盘”中的哪个位置,都可以采用(左,右),(上,下),(左上,右下),(右上,左下)的方式遍历而不会发生数组越界的错误,且无需判断是否到达边界。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值