配套教材:
Computer Organization and Design: The Hardware / Software Interface (5th Edition)
建议先修课程:数字逻辑电路、C / C++、汇编语言。
这是专业必修课《计算机组成原理》的复习指引。建议将本复习指导与博客中的《简明操作系统原理》配合复习。
在本文的最后附有复习指导的高清截图。需要掌握的概念在文档截图中以蓝色标识,并用可读性更好的字体显示 Linux 命令和代码。代码部分语法高亮。
计算机组成原理不是语言课,本复习指导对用到的编程语言的语法的讲解也不会很细致。如果不知道代码中的一些关键字、指令或函数的具体用法,你应该自行查找相关资料。
第三节 异常
26、异常(exception)有时也称中断(interrupt),是指除了分支和跳转以外的改变正常指令流的指令执行过程。最初,异常的设计是为了处理意外事件,例如算术溢出。后来,这个机制被用于处理IO设备(见第5章)与CPU的通信。
Intel x86统一将内中断和外中断称为中断,但按照MIPS的传统,内部和外部中断统称异常,而外部中断称中断。
以下是5种常见异常的来源及其对应的MIPS术语:
27、MIPS处理器包含一个异常程序计数器(EPC)。当产生异常时,引发异常的指令地址会被记录于此,然后控制权被移交至操作系统。然后操作系统再继续处理:可以为用户程序提供相应服务,执行一些预定义的动作,或者停止异常程序并报错。动作完毕后,可以选择结束程序或继续执行。
28、MIPS使用一个状态寄存器(status register,或称原因寄存器(cause register)),来保存引发异常的原因。
另一种方法是向量化中断(vectored interrupts)。每个中断被分配一个唯一的编码(中断类型码)。当设备发生中断时,把编码发送给CPU,通知CPU应该调用哪一个中断处理程序(interrupt handler / interrupt service routine)。中断向量中保存了每个中断处理程序的入口地址。CPU根据中断码获得相应的中断向量号,然后根据中断向量表(interrupt vector table)所在的地址和中断向量号去查找中断向量表获得相应中断号的中断程序地址,进一步执行对应的中断处理程序。
29、一个流水线化的CPU将异常当作另一种控制冲突。例如:当一个加法指令产生了算术溢出时,把跟在加法指令后面的指令清除掉,然后在新的位置重新取指令。这个方法也是分支预测失败时的处理方法。
指令会不会产生异常,一般是在执行完毕后才可以判断的。所以产生异常以后要给出相应的控制信号,不要让计算结果传入下一级。
如下图,控制单元中给出三个信号IF.Flush、ID.Flush、EX.Flush,用于产生异常后阻止数据继续传输给接下来的硬件单元。在PC之前多了一个MUX,用于决定输入正常的指令地址还是异常处理程序的地址。
当然,产生异常的指令的下一条指令要保存下来,以便在决定继续执行时能回到正确的位置。
30、现代CPU一般可以在同一周期同时执行多条指令,因此在同一个时钟周期内,可以有多条指令同时发生异常。这时候,要为不同的异常设置优先级。当不同优先级的异常同时发生时,总是处理优先级最高的异常。
异常程序计数器会存储引发中断的指令的地址,MIPS原因寄存器记录一个周期内的所有异常。异常处理软件必须把异常与引发异常的指令对应起来。具体来说,有的异常,例如未定义指令(undefined instruction)异常,在译码阶段就会产生,并在执行阶段时交由操作系统处理异常;算术指令通常在执行阶段才会产生异常。根据产生异常的阶段不同可以帮助区分一个异常是由流水线上的哪条指令引起。
31、异常处理必须由硬件和操作系统配合。硬件阻止异常指令,清除该指令之后的指令,而使该指令之前的指令正常完成,并设置寄存器来标示异常原因,保存异常指令的地址,并跳至事先编排好的异常处理程序。操作系统查看异常原因并相应处理。对于未定义指令、硬件失败或计算溢出,OS一般结束进程并返回出错原因(C / C++编写的程序在计算溢出时不做任何动作)。如果IO设备请求系统服务,OS需要保存现场,处理相应的请求,然后恢复现场,令程序继续执行。如果程序正在进行IO,OS要将其阻塞(block),待IO结束后再继续。
32、现代CPU一般具有多级流水线,而一个指令进入流水线后不能马上判定异常,所以当异常被探测到后,PC已经指向异常所在的位置之后。使用非精确异常(imprecise exception)机制的处理器在检测到异常后,传递的EPC的值并不指向发生异常的那一条指令。
非精确异常的硬件比较简单。一种实现方案是:允许已进入流水线中的指令执行完毕再转去执行中断处理。无论在指令的哪一流水段上发生异常,都不再允许后继指令进入流水线,断点为最后进入流水线的那条指令的地址(非精确)。不同的流水线阶段发生异常时,EPC的值与异常指令的实际地址的差不同。
如果等待已经进入流水线的指令执行结束再暂停程序并处理异常,而不是一旦发现异常就立即暂停程序运行并转至异常处理程序,可能会导致程序出错:
另一种实现方案是:将异常指令的后续指令排空。步骤如下:
·暂停指令流中导致异常的指令
·执行完异常指令之前的所有指令
·清除异常指令之后的所有指令
·记录异常原因
·保存断点
·转异常处理程序
在探测到异常后,该方法不允许后续指令继续执行,直到异常处理完毕。
这两种实现方法的共同缺点也是非精确异常的缺点:异常响应时间较长、程序调试不便(程序员在某条指令设置断点,但程序不能准确中断在所设置的断点处)。
要实现精确异常(precise exception),硬件开销就比较大:因为需要安全暂停流水线,也就是说要完整保存当前的状态,在异常处理完毕后必须得以恢复该状态并继续执行。这需要大量后援寄存器保存流水线中各指令的现场(包括寄存器堆、PSW(程序状态字)、流水段寄存器(含各段的控制寄存器))。
MIPS采用提交点技术实现精确异常。提交点为内存访问段。由流水线中最深的指令引起的异常,优先级最高,因为指令在流水线中越深的阶段,表示该指令越早执行(即使晚执行的指令先发生异常,也需要先处理更早进入流水线的指令的异常)。先发生的异常并不立即处理,只是被标记。保持流水线的异常标记直到提交点(M段)。
在分支预测中,当预测分支中出现了异常,而后由于预测错误而取消该指令时,需要取消异常。
无论是非精确异常还是精确异常,发生异常后,异常指令之前的指令都可以执行完毕。
33、异常亦可分为可恢复异常和不可恢复异常。可恢复异常在处理完毕后令程序继续执行,不可恢复异常处理后,程序被终止。
34、MIPS的异常一览:
35、MIPS的几乎所有异常的处理程序入口都为0x80000180h,不过也有例外。例如TLB(翻译后备缓冲)未命中(TLB-miss)的异常处理程序的入口地址就在0x80000000。这样的设计是出于提升性能的考虑。