目录 Table of Contents
MIPS 32 异常处理
基本概念
异常
在程序运行过程中, 某些打断正常运行流程的、且会引起运行态改变 (从用户态到核心态) 的事件.
异常分为两类, 一个是同步异常, 一个是异步异常
首先我们写的程序都是用户态的, 然后程序一步步执行过程中, 有些事件发生了, 打断了程序正常运行流程, 具体什么事件还不知道, 但这些事件会引起运行状态的改变, 当前程序跑的时候 CPU 是用户态, 忽然换成核心态, 严格来说就是操作系统接管了.
切换成内核态, 然后有相关的异常处理程序来进行接管, 接管完了之后又从核心态返回到用户态. 但是这有多种返回方式, 一种是说程序出问题了, 直接把程序杀掉退出 ; 还有一种就是回到当前被中断的那条指令, 重新执行 ; 还有一种是回到中断指令的下一条指令, 继续执行.
同步异常
因为异常分为两类, 两类异常的处理方式不一样. 所谓同步异常指的是由指令执行引起的.
- 由指令执行引起的
- 陷入(Traps)
- 程序 "故意" 引起的
如 : 系统调用 (system call)、断点 (breakpoint)、Trap 指令 - 返回到下一条指令
- 程序 "故意" 引起的
指令执行引起, 实际上也分好几种情况. 一种是陷入 Traps, 这种程序 "故意" 引起的, 最显然的就是你调用 system call, 你用户态要执行某一个功能, 但这个功能是内核态操作系统给你提供的, 所以你调动它来完成, 这种情况下你不得不把程序的状态从用户态切换到核心态, 切换的命令是 system call, 这个状态是你有意而为之, 一般这种系统调用完成之后正常返回的话, 是不会出问题的, 正常返回到 system call 的下一条指令, 否则就是进入无限循环了, 所以这就是 Traps 引起的.
- Faults
- 程序 "无意" 引起的、但是可恢复
如 : 页缺失(recoverable) - 重新执行当前指令
- 程序 "无意" 引起的、但是可恢复
第二种就是 faults 引起, 就是程序 "无意" 引起, "无意" 比如说程序跑的时候页缺失了, 这个不是错误, 你程序也不知道什么时候引起页缺失, 但确实这是由一条你的访存指令引起的, 但是这种是可以恢复的, 没什么问题, 可以返回, 返回之后就把你当前引起页缺失的这条指令再重新跑一遍就 OK 了, 这是一种情况, 我们下一节讲虚存会说到
- Aborts
- 程序 "无意" 引起的、不可恢复
如 : parity error, machine check - 程序退出
- 程序 "无意" 引起的、不可恢复
还有一种就是真出问题了, 就是 aborts, 你必须得放弃了, 程序无意引起的, 不可恢复的这种. 比方说你访问内存的时候, load/store 的时候, 校验和出错, 但实际上这个不是你程序引起的, 但确实是因为你这条指令, 正好这条指令访问内存, 校验和出问题了, 隐含的问题在那里, 但是你触发它了, 也算是你引起的, 无意引起的不可恢复的, 因为硬件出问题了, 这种情况下程序就退出
异步异常 (中断)
除了同步异常之外, 还有一种是异步异常, 也叫做中断, 是由 "外部事件" 引起, 往往是外设的触发.
-
由 "外部事件" 引起, 往往是外设触发
如 : IIO 中断、Hard Reset、Soft Reset -
重新执行当前指令或者下一条指令
比方说程序跑着跑着, 网卡上面收到一个数据包, 同时网卡结构 buffer 快满了, 这个时候如果不处理的话, 在网络上丢包了, 毕竟 buffer 有限, 然后可能网卡给你 CPU 传一个中断过来, 让 CPU 赶紧处理一下, 这样 CPU 得中断当前正在跑的用户程序, 然后去处理这个网卡, 处理完再回来跑用户程序, 处理完返回, 返回到哪条指令这个得看情况, 是返回到当前指令, 或者是下一条指令, 就要看情况.
异常处理向量
针对异常进行处理有一个概念, 就做异常处理向量
就是异常分不同类型, 不同类型都有一个号, 所以它的系统里就维护一个异常的处理向量表. 你这个号, 对应表里的一项, 这一项的地址就是要处理这个异常的代码的入口地址, 就是向量处理方式.
注意, 向量处理方式并不是说所有的处理器都有这种方式, 因为这种方式如果不同的异常号, 直接可以通过查表的方式, 马上跳到它所对应的处理入口, 这种方式是比较快的. 但是有一些处理器, 比如 4KC 处理器就没有这种方式, 它的异常入口地址非常少, 就那么几个, 完了进去之后通过软件访问, 来判断当前到底哪个异常, 然后再跳到不同的入口, 这个就慢一些, 所以它的实时性不太好.
处理向量就是实时性比较好的一种方式, 但不能一概而论, 因为实际情况不一定是一样的, 每类异常都有编号, 对应一个入口地址, 这样如果快的话就可以直接跳过去, 但有些处理器, MIPS 处理器早期就没有这种方式, 它从 4KE 才开始引入.
-
每类异常都有其编号以及异常处理入口地址, 往往在内存中构成一张地址表.
-
所以称之为向量
-
但是早期 MIPS 不支持这种模式, 从 MIPS 4Ke 开始引入
-
精确异常处理 (MIPS 支持)
精确异常指的就是, 在处理异常时, 产生异常的指令之前的指令都应执行完毕; 该指令之后 (包括该指令本身) 的指令则都不处理.
- 需要精确记录异常的位置 (指令)
这种情况下, 需要精确记录异常的位置, 到底是由哪条指令触发的. 然后再看情况到底是要它重新执行, 还是到下一条执行, 还是 abort 掉.
针对 MIPS 而言还有一个特殊情况就是 branch delay slot, 如果你是在 branch delay slot, 也就是你在这个跳转指令 slot 里头触发的异常, 那么这种情况下如果你要返回的话, 因为 branch delay slot 的特性, 不管你跳不跳, 它后面紧跟的指令是一定要执行的, 如果是后面紧跟的指令触发了异常, 异常返回的话, 也要跳到那条相关的分支指令或者是 jump 指令, 而不能返回到它这个指令本身, 所以这是一个异常情况, MIPS 所特有的.
-
需要取消后续指令
-
需要正确恢复执行
MIPS 32 的异常种类
MIPS 异常处理基本过程
首先是保存现场--在异常程序入口, 硬件只记录了被打断程序的很少量的信息, 因此需要保留相关的控制寄存器等值使得异常处理程序能够执行 (k0、k1 寄存器保留给异常处理使用);
然后判断不同的异常--查询 cause 寄存器, 根据其不同的异常原因来进行不同的处理流程;
再就是构造异常处理内存空间--异常处理程序可能由高级语言书写, 需要保留通用寄存器, 构造堆/栈存储区;
处理异常--......
最后返回--恢复寄存器, 清零 cause 寄存器, 将 status 寄存器的相关位置 1 以打开中断
异常返回
一般而言, 异常处理代码工作在核心态, 而被中断的程序是在用户态, 所以异常返回意味着状态转换.
这个转换与指令返回必须 "同时" 完成, 也就是说用一条指令完成它.
MIPS 里头专门有一条 eret
指令, exception return 不是通常的 return, 它就是返回 epc 指向的地址. epc 就是硬件设置的发生中断的产生异常的一条指令的地址, 它产生通过它来返回, 但不一定, 有时候可能通过减法 epc, 有时候可能通过 epc + 4.
然后再去修改 status 寄存器, status 寄存器就是用于控制你当前的处理器运行在哪个状态的, 也就是 epc 这个东西, 这条指令, 一条指令两用, 一个是返回, 一个是通过修改 status 寄存器来修改当前的状态.
中断
中断就是异步发生的, 来自处理器外部的 I/O 信号的一个引脚引发的.
一般来说不是由专门的一条指令所造成的, I/O 设备, 比如网络适配器, 磁盘控制器等通过处理器芯片上的一个引脚发送信号, 并将中断信号放到控制系统总线上, 用来触发中断, 这个异常号标识了引起中断的设备.
SPIM 模拟器支持的异常处理流程
status 寄存器
cause 寄存器
异常处理实例
-
触发读数据地址不对齐异常 (AdEL)
-
定位导致异常的指令 (EPC 或者 EPC+4, 取决于 cause 中的相关位)
-
解码该指令, 取得异常数据地址, 并处理 (?)
-
如何返回 ?