异常的分类和常见类型
在CPU设计中, 除了分支指令, 异常也可以打断程序的执行. 异常大致会有下面这几类:
- CPU外部引起的异常, 这种异常也叫做中断(Interrupt), 中断本质上和CPU内部执行的指令没有关系, CPU在任何时期都可能会出现中断, 中断也叫做异步异常.
- 虚拟地址转换时出现的异常:
- 如果TLB Miss, 那么会出现异常.
- 如果转换关系在页表中不存在, 出现Page Fault.
- 程序出现了被保护的页, 访问权限异常.
- 执行指令自身会出现异常, 例如
trap
指令, OS需要这些指令实现系统调用.
精准异常
在CPU中, 所有异常的处理过程都是一样的, 当出现异常时:
- 出现异常之前的指令都必须执行完成.
- 产生异常, 以及产生异常之后的指令都不允许完成.
- CPU会跳转到Exception Handler执行.
- 从Exception Handler返回后, 需要重新执行导致异常的指令, 但不是绝对的, 需要根据异常的类型决定.
如果能够实现以上四步, 那么在CPU外部看来, 异常就和没发生过一样, 这种方式称为精准异常(precise exception).
异常处理的一些设计
对于CPU来说, 一般有一个寄存器保存发生异常的指令pc, 一般叫epc.
还有一个寄存器保留发生异常的类型信息, 一般叫ecause.
在异常处理程序中, 又可能会修改通用寄存器的值, 因此需要提前将这些寄存器的值保留到栈上.
在流水线上, 任何阶段都有可能出现异常:
- Fetch:
- I-TLB Miss.
- Page Fault.
- Decode:
- 未定义的指令编码.
- Execute:
- 除0.
- 溢出.
- Memory:
- D-TLB Miss.
- Page Fault.
- 地址没对齐.
假设这个流水线是顺序执行的, 那么对于异常的处理步骤如下:
- 首先, 我需要在流水线中找到一个异常处理点(Commit Point), 要求这个处理点之后, CPU不会再发生异常.
- 例如在五级流水线上, Memory阶段之后, 后面就不会再发生异常.
- 所有异常都会在到达流水线的异常处理点之后再进行处理.
- 每个指令的PC值都会随着流水线进行移动, 如果出现异常, 会在Commit Point中将PC写入epc寄存器.
- 流水线中, 如果某个阶段出现异常, 那么异常信息也会随着流水线进行移动, 并且在Commit Point中写入ecause寄存器.
- 当到达Commit Point时:
- 首先, 把引起异常的指令从流水线中Flush, 不要让它进入Write Back阶段.
- 之后, 将后面已经取出的指令, 以及这些指令与异常有关的流水线寄存器Flush.
- 然后, 写入epc和ecause寄存器.
- 然后, PC变成epc, CPU取出异常处理指令.