Skip to content

ch1

  • BASE_ADDRESS:定义基地址
  • skernel:定义当前位置(可通过.表示)
  • stext:定义.text节的起始位置
  • etext:定义.text节的结束位置
  • srodata:定义.rodata节的起始位置
  • erodata:定义.rodata节的结束位置
  • sdata:定义.data节的起始位置
  • edata:定义.data节的结束位置
  • sbss:定义.bss节的起始位置
  • ebss:定义.bss节的结束位置
  • ekernel:定义链接脚本的结束位置

链接脚本的结构

ch2

控制状态寄存器 (CSR, Control and Status Register)

当 CPU 执行完一条指令(如 ecall )并准备从用户特权级 陷入( Trap )到 S 特权级的时候,硬件会自动完成如下这些事情:

  • sstatus 的 SPP 字段会被修改为 CPU 当前的特权级(U/S)。
  • sepc 会被修改为 Trap 处理完成后默认会执行的下一条指令的地址。
  • scause/stval 分别会被修改成这次 Trap 的原因以及相关的附加信息。
  • CPU 会跳转到 stvec 所设置的 Trap 处理入口地址,并将当前特权级设置为 S ,然后从Trap 处理入口地址处开始执行。

而当 CPU 完成 Trap 处理准备返回的时候,需要通过一条 S 特权级的特权指令 sret 来完成,这一条指令具体完成以下功能:

  • CPU 会将当前的特权级按照 sstatus 的 SPP 字段设置为 U 或者 S ;
  • CPU 会跳转到 sepc 寄存器指向的那条指令,然后继续执行。

KernelStact 内核栈

UserStack 用户栈

两种栈的大小都设为8KiB

Trap上下文

// os/src/trap/context.rs

#[repr(C)]
pub struct TrapContext {
    pub x: [usize; 32],
    pub sstatus: Sstatus,
    pub sepc: usize,
}

可以看到里面包含所有的通用寄存器 x0~x31 ,还有 sstatus 和 sepc 。那么为什么需要保存它们呢?

  • 对于通用寄存器而言,两条控制流(应用程序控制流和内核控制流)运行在不同的特权级,所属的软件也可能由不同的编程语言编写,虽然在 Trap 控制流中只是会执行 Trap 处理相关的代码,但依然可能直接或间接调用很多模块,因此很难甚至不可能找出哪些寄存器无需保存。既然如此我们就只能全部保存了。但这里也有一些例外,如 x0 被硬编码为 0 ,它自然不会有变化;还有 tp(x4) 寄存器,除非我们手动出于一些特殊用途使用它,否则一般也不会被用到。虽然它们无需保存,但我们仍然在 TrapContext 中为它们预留空间,主要是为了后续的实现方便。
  • 对于 CSR 而言,我们知道进入 Trap 的时候,硬件会立即覆盖掉 scause/stval/sstatus/sepc 的全部或是其中一部分。scause/stval 的情况是:它总是在 Trap 处理的第一时间就被使用或者是在其他地方保存下来了,因此它没有被修改并造成不良影响的风险。而对于 sstatus/sepc 而言,它们会在 Trap 处理的全程有意义(在 Trap 控制流最后 sret 的时候还用到了它们),而且确实会出现 Trap 嵌套的情况使得它们的值被覆盖掉。所以我们需要将它们也一起保存下来,并在 sret 之前恢复原样。