处理器在执行的过程中,除了分支类型的指令之外,很多其他的情况也可以打断程序的执行,这些情况统称为异常(exception);
不同的指令集体系中,异常包括的内容也不尽相同,可以概括地将异常分为下面几种。
- 处理器的外部事件引起的异常,这种类型的异常其实更多时候被称做中断(interrupt)
- 因为发生在处理器的外部,中断本质上和处理器中执行的指令没有必然的关系,处理器在执行的任何阶段都有可能接收到中断,因此也称做是异步的异常;
- VA->PA转换的异常;
- TLB miss;
- page fault;
- 访问了一个受保护的page;
- 指令自身引起的错误,例如:
- 未定义的指令、用户态下的非法指令、整数运算时的溢出、访问存储器的地址未对齐等。
- 很多处理器还支持数据的完整性检查,例如处理器对L2Cache送来的数据进行奇偶校验或ECC校验,如果发现校验失败,也会产生异常;
- 指令自身产生的异常;
- 例如MIPS指令集中的SYSCALL和Trap两种指令,执行这些指令就会产生异常,其中:
- SYSCALL 指令是无条件产生异常;
- 而 Trap 指令则是有条件地产生异常
- 普通的程序可以通过这种类型的指令来调用操作系统的某些任务,一个处理器要支持操作系统,这种类型的指令是必须有的。
异常处理流程
????????对于一个特定的处理器来说,所有类型异常的处理过程是一样的,从处理器外部看起来,产生异常的指令之前的所有指令都已经完成了,而这条产生异常的指令及其之后的所有指令都不允许完成。
??????????????????????????????????????????????????????
- 什么时候处理异常?
- 遵循PO的顺序,需要再流水线最后一个阶段再对异常进行统一的处理;也就是,当ROB中,将要commit的指令,发现有excption时,才进行处理;
- 发生异常的指令,及其以后的所有指令,都不能完成,因此,需要将异常之后的所有指令,都kill掉;
- 处理器会跳转到对应的异常处理程序(exception handler)的入口地址,开始执行这个异常处理程序。
- 保存PC值(或者下一条PC值,根据异常类型来决定),对于RISC处理器,放到EPC中来存储;
- 将通用寄存器的值保存到堆栈(memory)中,因为异常处理过程,可能会修改这些寄存器的值;
- 对于RISC处理器,访问memory只能用load/store指令;因此,存储通用寄存器的值,需要使用store指令;
- 而堆栈的指针,则使用通用寄存器来模拟,MIPS中使用R29来作为堆栈指针;
- 例如,将通用寄存器R20的内容,写到memory中:SW R20,0[R29], 实际上R29只是一个普通的寄存器,和其他寄存器没区别,只是一种约定俗称的做法,需要使用软件来管理堆栈指针的增减;
- 当其执行完成后,会返回到刚才发生异常的地方,重新开始将这条指令取到流水线中,就好像这个异常没有发生过一样,这种方式也称做精确的异常(precise exception),因为总能够找到哪条指令发生了异常。
- 对于大部分类型的异常来说,从其对应的异常处理程序返回的时候,都需要重新执行这条产生异常的指令;有些类型的异常不能这样做,典型的例子就是 SYSCALL/Trap 指令产生的异常,如果重新执行,会死锁;