rust-os.07 cpu异常
Contents
异常与函数调用非常相似:CPU跳转到被调用函数的第一条指令并执行它。 之后,CPU跳转到返回地址并继续执行父功能。 但是,异常和函数调用之间存在主要区别:函数调用是由编译器插入的调用指令自愿调用的,而任何指令都可能发生异常。 为了了解这种差异的后果,我们需要更详细地研究函数调用。
|
|
与函数调用相反,任何指令都可能发生异常。 在大多数情况下,我们甚至在编译时都不知道生成的代码是否会导致异常。 例如,编译器无法知道指令是否导致堆栈溢出或页面错误。 由于我们不知道什么时候发生异常,因此我们之前无法备份任何寄存器。 这意味着我们不能使用依赖于调用者保存的寄存器的调用约定作为异常处理程序。 相反,我们需要保留所有寄存器的调用约定。 x86中断调用约定就是这样的调用约定,因此它可以保证在函数返回时所有寄存器值都恢复为其原始值。 注意,这并不意味着所有寄存器都在函数入口处保存到堆栈中。 而是,编译器仅备份该函数覆盖的寄存器。 这样,可以为仅使用几个寄存器的短函数生成非常有效的代码。
|
|
上面代码让cpu使用自己的idt时会有报错:
|
|
原因:
装入方法需要一个&static self,该self对于程序的完整运行时有效。 原因是CPU将在每次中断时访问该表,直到我们加载不同的IDT。 因此,使用比“静态”更短的生存期可能会导致使用后使用错误。 实际上,这正是这里发生的情况。 我们的idt是在堆栈上创建的,因此仅在init函数内部有效。 之后,堆栈存储器将重用于其他功能,因此CPU会将随机堆栈存储器解释为IDT。
为了解决此问题,我们需要将idt存储在具有“静态寿命”的位置。 为此,我们可以使用Box在堆上分配IDT,然后将其转换为“静态引用”,但是由于我们正在编写OS内核,因此还没有堆。 作为替代方案,我们可以尝试将IDT存储为静态:
|
|
但是,存在一个问题:静态变量是不可变的,因此我们无法通过init函数修改断点条目。 我们可以通过使用静态mut解决此问题:
|
|
该变体编译没有错误,但绝不是惯用语言。 静态muts非常容易发生数据争用,因此我们每次访问都需要一个unsafe块。
lazy_static宏的存在可以让我们不需要使用上面的不够安全的方法。 当在第一次引用静态变量时,宏会执行初始化,而不是在编译时评估静态变量。 因此,我们几乎可以在初始化块中执行所有操作,甚至可以读取运行时值:
|
|
请注意,此解决方案是怎样不要求unsafe块的: lazy_static! 宏确实在幕后使用了unsafe,但是它在安全接口中被抽象掉了。(Note how this solution requires no unsafe blocks. The lazy_static! macro does use unsafe behind the scenes, but it is abstracted away in a safe interface.)
使异常在我们的内核中起作用的最后一步是从main.rs中调用init_idt函数。 而不是直接调用它,我们在lib.rs中引入了一个通用的init函数:
|
|
Author sorvik
LastMod 2020-02-20