# 近来做 Stanford CS140e 的一些进展和思考（7）

在[上一篇文章](https://jia.je/programming/2018/03/05/thoughts-on-stanford-cs140e-6/index.md)之后，我很长时间都没有在继续我这个项目，清明节刚好闲下来了我就回来继续啃它。Stanford 那边已经结课，最后的 `3-spawn` 也只有一部分，剩下的部分不知道什么时候作者才会填上去了。

这次主要要写的代码就是，对异常的处理。这里的异常并不是我们编程语言中的 catch/throw，而是硬件的异常。AArch64 和 x86 一样，也有不同的特权级别的区分，前者是 EL0~EL3，后者则是 RING0 和 RING3。特权级别高可以往特权级别低转换，但是反过来，只能通过异常的方式提高特权等级，并且切换特权等级后只有固定的一些代码可能会跳转，这就是 `exception handler/vectors` 。这些函数可以知道是什么原因调用了他们，根据硬件规定好的文档，我们可以知道发生了什么事情，是对齐出错了呢，还是用户调用了 syscall 呢，等等。根据不同的情况，我们需要进行不同的处理。当处理完之后，我们需要考虑，跳转回用户代码的时候，回到哪里，提供什么值，不提供什么。

实现的话，需要很多步骤。首先是构造好 `exception vector` ，这里作者已经写好了一个宏（这里 @BenYip 遇到了一个 assembler 的 BUG），直接用宏就可以把它写出来。然后，我们需要把它加载到当前 EL 的 `VBAR_ELx` 寄存器中，当 CPU 抛出异常的时候，就会找到这里相应的处理器进行处理。进到这里以后，我们首先先不考虑太多上下文保存的事情--我们先保证能处理异常，恢复也是个有很多坑的步骤，作者也是在这里分成了两个 Subphase。首先还是从 `ESR_ELx` 中解析到错误的来源的具体内容，如果是我们在 `shell` 中自己调用的 `brk 2` 指令，我们就自己新开一个 `shell` ，修改了提示符以示区别。这样，我们就成功地捕捉到了这个异常。由于我们还无法恢复回去，所以我们直接死循环。

接下来我们要做的是，从异常中恢复出来。由于用户代码可能在各种地方抛出异常，异常也分同步和异步两种情况，这里有许多需要考虑的问题。为了简化，我们目前只考虑同步的 `brk 2` 导致的 Brk 异常。为了能恢复之后能够正常运行，我们需要把所有的寄存器都保存下来，即 `TrapFrame` 。保存的时候需要讲究 AArch64 平台下 SP 寄存器的对齐问题。我们也要把一些特殊的寄存器保存下来。还有一点，就是，因为 `exception handler` 中调用了 `context_save` 函数，所以此时的 lr 本身也需要进行保存，这个地方也卡了我很久。最后，再把这些一个一个地恢复到原来的样子，调整 `ELR_EL1` 使得退回到原来的状态时，会跳过当前的 `brk 2` 指令，调用它的下一调指令。这样，我们就成功地在遇到异常时，弹出一个 `shell` ，而且还可以回退回来。

学到了很多很多。之后大三，我们可能需要做自己的 CPU，在自己的 CPU 上跑自己的操作系统，在自己的操作系统中跑自己的编译器，在自己的编译器中编译一个数据库。希望到时我还活着吧。#flag

更新：[下一篇在这里](https://jia.je/programming/2018/04/10/thoughts-on-stanford-cs140e-8/index.md)。
