近来做 Stanford CS140e 的一些进展和思考(7)¶
在上一篇文章之后,我很长时间都没有在继续我这个项目,清明节刚好闲下来了我就回来继续啃它。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
更新:下一篇在这里。