diff --git a/articles/20220724-virt-mode.md b/articles/20220724-virt-mode.md new file mode 100644 index 0000000000000000000000000000000000000000..80b2986c7b21d622d9052ac652e05973f10e08c3 --- /dev/null +++ b/articles/20220724-virt-mode.md @@ -0,0 +1,732 @@ +> Author: 潘夏凯 <13212017962@163.com>
+> Date: 2022/07/25
+> Revisor: ABC
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Proposal: [RISC-V 虚拟化技术调研与分析](https://gitee.com/tinylab/riscv-linux/issues/I5E4VB) + +# RISC-V 虚拟化模式切换简析 + +## 前言 + +本文简要介绍了 RISC-V 虚拟化的实现方式、特权级划分以及基于 trap 实现的虚拟化模式切换机制,并分析了当前主流的 RISC-V 模拟器中与模式切换相关的代码实现,尝试理清基于 KVM 的虚拟机系统其 Host 和 Guest 的切换机制。 + +## 软件版本信息 + +| 软件 | 版本 | +| ------------ | ---------------------------------------- | +| Linux Kernel | Linux 5.19-rc5 | +| Spike | ac466a21df442c59962589ba296c702631e041b5 | +| QEMU | a74c66b1b933b37248dd4a3f70a14f779f8825ba | + +## RISC-V 虚拟化 + +### RISC-V 如何实现虚拟化? + +RISC-V 通过引入虚拟化指令集扩展 (Hypervisor extension, 后简称 H 扩展) 实现了在 S 扩展基础上的虚拟化。 + +#### 何为 H 扩展 + +1. 非虚拟化的系统架构(Supervisor-Level Architecture) + + 通俗地说,S 级架构指的是由硬件、操作系统、应用 (Application) 三层结构构建的计算机系统,OS 运行在 S-mode 下,处理 虚拟地址和物理地址的转换等任务。对应的,硬件运行在 M-mode(Machine Mode),应用程序则运行在 U-mode(User Mode)。 + +2. 支持虚拟化的系统架构 (Hypervisor-Level Architecture) + + 相较于上述非虚拟化系统架构,虚拟化要求在 OS 与硬件之间添加一个可统筹管理 OS 的 hypervisor,此时的 OS 被称为客户操作系统(Guest OS),后续简称为 Guest。这样一来,在同一个硬件上就可以同时运行多个互相独立的 Guest,每个 Guest 都认为自己是一台独立的机器,这便实现了所谓的虚拟化。此时,硬件仍旧运行在 M-mode,supervisor 则运行在 HS-mode(Hypervisor-extended Supervisor mode),对应的原来分别运行于 S-mode 和 U-mode 的 OS 和应用程序则在此处被标记为 VS-mode(Virtual Supervisor) 和 VU-mode(Virtual User mode)。 + +3. RISC-V 中对虚拟化支持的支持 + + RISC-V 指令集中添加了 H 扩展,规定了支持 hypervisor 在 H-mode 下需要执行的所有操作对应的指令(instructions)和控制状态寄存器(CSRs)。在具体的设计(处理器架构、模拟器等)中,通过添加、修改对应 CSR 及增加对应指令的操作的支持,可以实现系统的虚拟化。 + +#### H 扩展的实现 + +1. 系统分层与各层间通讯 + + 对于 S 级架构而言,系统包含硬件、OS、App 三层,各类 App 通过 OS 提供的各类 API 完成与系统的交互,这里对此不作讨论。RISC-V ISA 中,OS 与物理硬件的通信仅给出了机制的定义而非具体的实现,以期实现对干净的虚拟化 (clean virtualization) 的支持,如对 timer 的请求、处理器间中断请求等处理,在某些系统中是以 SBI (supervisor binary interface) 的形式实现了一个 SEE (supervisor execution environment) 来支持的,在其它系统中则是对于这类请求直接进行了具体的实现。上述关于 S-mode 下硬件与 OS 的通信的论述,参考自 RISC-V 特权指令级(20211203)规范的第四章前言(Page63)。 + + 而对于 H 级架构而言,系统包含了硬件、supervisor、OSs、Apps 四层,硬件与 supervisor 之间的通信与 S 级架构下硬件与 OS 的通信类似,可以使用相同的 SBI;但 supervisor 与 Guest 之间的通信则需要额外的实现。 + + 值得一提的是,hypervisor 除了可以是单独实现的管理器之外,还可以是具备管理多个 Guest 的能力的 OS。 + + ```mermaid + graph BT + subgraph 系统分层与层间通信 + + M1[hardware]--SBI---H[hypervisor]--SBI to be implemented---Guest1[Guest OS 1]--OS API---Apps1[Apps on Guest1] + + H-.SBI to be implemented-.-Guests[Guest 2, 3, ...]-.OS API-.-Apps[Other Apps] + + M2[harware]--SBI----S[Supervisor OS]--OS API---Apps2[Apps] + end + ``` + +2. 寄存器数目要求 + + 该扩展要求 32 的整数倍个寄存器,因此依赖于 `RV32I或RV64I` 指令集,`RV32E` 仅有 16 个寄存器,无法支持 H 扩展。 + +3. 地址转换机制要求 + + 依赖的基础指令集必须支持标准的基于页的地址转换机制,即 `Sv32 for RV32` 或 `minimum Sv39 for RV64` + +4. CSR 规定 + + `mtval` 不能是 x0 寄存器; + 规定 H 扩展通过设置 `misa` 的第七位开启,对应字母 H; + 推荐不对 `misa[7]` 使用硬连线 (hardwired) 从而保证该扩展可以被关闭。 + +### RISC-V 如何区分虚拟化? + +指令集中约定用虚拟化模式 V (_virtualization mode_) 来标记当前是否是在 Guest 系统中运行。V=1 表示当前确实运行在 Guest 系统中,V=0 则表示不运行在 Guest 中。具体如下表所示: + +| V | 虚拟化 (H-Level Arch.) | V | 虚拟化特例 | 名义特权级 | +| --- | ---------------------- | --- | ---------- | ---------- | +| 1 | VU-mode | 0 | U-mode | U-0 | +| 1 | VS-mode | | | S-1 | +| 0 | HS-mode | 0 | HS-mode | S-1 | +| 0 | M-mode | 0 | M-mode | M-3 | + +在上述表格中,虚拟化特例指 hart 所指示的应用程序以 U-mode 直接运行在一个运行于 HS-mode 的 OS 上。 + +名义特权级 (Nominal Privilege) 是在 S-mode 基础上的特权级约定,分为 U, S, M 三级,分别用 0,1,3 表示,各类指令集模拟器均以此标准实现。 + +### RISC-V 如何处理虚拟化? + +#### 相关 CSR 简介 + +##### mstatus + +参考:riscv-privileged-20211203, 3.1.6.1 + +`mstatus` CSR 分区如下图所示: +![mstatus](./images/20220723-virt-mode/mstatus.png) + +`mstatus` 具备全局中断使能栈机制: +该 CSR 中,`MIE`, `SIE` 分别用于 M/S-mode 下的中断使能,另有 `MPIE`, `SPIE` 用于记录 trap 之前 `mstatus` 的中断使能状态,还有 `SPP, MPP` 记录 trap 之前的特权级 (SPP 一位: 0,1; MPP 两位:0, 1, 2, 3),由此实现了一个支持嵌套 trap 的两级栈。 + +基于中断使能栈的 trap 返回机制:trap 处理完成后从 M-mode 或 S-mode 返回需要调用 `mret` 或 `sret` 指令,`mstatus` 需做如下对应修改。假设执行 $xRET$ 指令,$xPP=y$:$xIE = xPIE$;当前特权级设置为 $y$,$xPIE=1$;$xPP$ 设置为支持的最小特权级(若支持 U-mode 则设置为 0,否则为 3 即 M-mode);若$xPP \not ={M}$, $MPRV=0$。 + +上述修改可在 QEMU、Spike 的 `sret` 及 `mret` 中找到实现,分析见[返回指令与虚拟化](#返回指令与虚拟化)部分。 + +`TSR` (Trap SRet) 支持拦截 supervisor 异常返回指令 `sret`: + +- TSR=1,在 S-mode 下尝试执行 `sret` 将会导致 `illegal instruction exception`; +- TSR=0,则允许在 S-mode 下执行 `sret`。若不支持 S-mode,TSR 为只读 0. + +##### hstatus + +参考:riscv-privileged-20211203, 8.2.1 + +![hstatus](./images/20220723-virt-mode/hstatus.png) + +`hstatus` 寄存器提供了类似于 `mstatus` 的特性用于追踪和控制一个 VS-mode 下的 Guest 的异常的行为。 + +- `SPV` (Supervisor Previous Virtualization) + + trap 到 HS-mode 就会涉及写入:`sstatus.SPP` 在 trap 时会被设置为 trap 对应的名义特权级,此时 `hstatus.SPV` 就会被设置为 trap 时的 V 值;当 V=0 时执行 `sret` 指令,`SPV` 置为 V。 + +- `SPVP` (Supervisor Previous Virtual Privilege) + + V=1 时,行为与 `sstatus.SPP` 相同,即置为 trap 时的名义特权级;V=0 时,保持不变。 + +- `GVA` (Guest Virtual Address) + + trap 到 HS-mode 时写入:对于写虚拟地址到 `stval` 的寄存器的 trap(breakpoint, address misaligned, access fault, page fault, or guest-page fault),`hstatus.GVA` 置 1,对于其他 trap 置 0. + +##### sstatus + +参考:riscv-privileged-20211203, 4.1.1 + +![sstatus](./images/20220723-virt-mode/sstatus.png) + +`sstatus`用于追踪处理器当前的运行状态,`sstatus` 是 `mstatus` 的一个子集。 + +- `SPP` (Supervisor Previous Privilige) + + 用于标识 trap 进入 S-mode 之前 hart 所在的特权级:来自 U-mode 则置 0,否则为 1。 + +- `SIE`, `SPIE` (Supervisor Previous Interrupt Enable) +- trap 处理过程中 `sstatus` 的行为 + + trap to S-mode: `SPIE=SIE`, `SIE=0` + `sret`: `SIE=SPIE`, `SPIE=1` + +##### vsstatus + +V=1 时,`vsstatus` 用于替代 `sstatus`,所以通常针对 `sstatus` 的操作会替换为 `vsstatus`。 + +#### Trap 引起虚拟化模式切换 + +##### RISC-V 术语 + +- _hart_ + + RISC-V ISA 中将包含一个独立的取值单元的组件定义为 _core_,一个兼容 RISC-V 的 _core_ 可以通过多线程的方式支持多个兼容 RISC-V 的硬件线程,这样的一个硬件线程定义为一个 _hart_(**har**dware **t**hread)。(riscv-spec-20191213, 1.1 **RISC-V Hardware Platform Terminology**, p2) + +- _SEE_, _EEI_ and _hart_ + + SEE (Software Execution Environment)决定了一个 RISC-V 程序的具体行为,SEE 是通过具体的 EEI (Execution Environment Interface) 来定义的,而一个 EEI 应该定义一个程序的初始状态、访存与 IO、执行环境应包含的 hart 的类型、数量、特权级、合法指令的行为,如 ABI (Linux Application Binary Interface), SBI (RISC-V Supervisor Binary Interface)。 + + 在裸机硬件平台,hart 是由物理处理器线程直接实现的,其 EE 在硬件加电重置时就被定义;对于一个 RISC-V 平台的操作系统来说,其通过控制虚拟地址的访问和将用户级的 hart 分配到可用的物理处理器的线程上,为应用提供了多个用户级的 EE;对于 RISC-V supervisor 而言,其为 Guest OS 提供 Supervisor-level EE 的方式视 hypervisor 的实现方式而异,与 OS 相同的是,它也包含了多个可用的 hart。 + + 综上所述,不论是硬件、OS 还是 supervisor,都可以视为其内部基于不同等级的 hart 为更高一级提供了运行环境。因此,某一时刻的一个 RISC-V 程序必然对应着一个特定等级的 hart。故而本文所指的运行在某一模式,均可以视为某一程序对应的 hart 处于特定等级: + + | hart 等级 | 运行模式 | + | ------------------------------------ | -------- | + | User-Level | U | + | Supervisor-Level | S | + | Hypervisor-Extended Supervisor-Level | S | + | Machine-Level | M | + + 因此,H-mode 下的虚拟模式切换,其具体行为与 S-mode 具有诸多相同之处。 + +##### RISC-V 中的 Trap + +| | 定义 | +| ----------- | ------------------------------------------------------------ | +| _exception_ | 当前 hart **内部**与某条指令相关的运行时中出现了异常的条件 | +| _interrupt_ | 可能导致 hart 控制转移的**外部**异步事件 | +| _trap_ | 异常或中断导致的从原 hart 到特定 trap handler 的**控制转移** | + +由上述定义可得,在 RISC-V 中,trap 是控制转移的总称。控制转移意味着 hart 的等级可能发生变化,即在一个虚拟化的系统中,trap 可能导致虚拟化模式切换。 + +##### Trap 处理概览 + +一个 trap 意味着 hart 的控制转移,导致 trap 的程序对应的 hart 有可能从一个特权级 $x$ 跳转到另一个特权级 $y$ ($x \leq y$). + +当 trap 在 $y$ 特权级下被处理完成后,hart 需要返回原来的特权级$x$,可以通过 `sret` 和 `mret` 分别实现从 S-mode 和 M-mode 返回。 + +上述两个操作的具体细节详见 [Trap 与虚拟化](#trap-与虚拟化) 和 [返回指令与虚拟化](#返回指令与虚拟化)两节。 + +### 总结 + +首先,RISC-V ISA 借助 HS (hypervisor-extended supervisor) 指令集扩展实现了对虚拟化的支持。RISC-V hypervosir 指令集扩展将 S(supervisor) 级架构进行虚拟化,从而使之支持 Guest OS 在 Hypervisor 上的运行。 + +其次,相较于 S 扩展,从指令集需要的功能和对应的修改两个方面的对应关系来解读,H 扩展改动如下: + +| 功能 | ISA 改动(相较于 S 扩展) | +| -------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| 地址转换: GPA (guest physical address) $\to$ SA (supervisor address) | 用于支持 guest OS 运行在 VS-mode (Virtual Supervisor mode) 的指令以及 CSR | +| hypervisor 运行 | 用于控制地址转换新阶段的指令以及 CSR | + +第三,可以运行在 S-mode 的 OS 均可以无需修改就可在 HS-mode 和 VS-mode 下运行。 + +## Trap 与虚拟化 + +Trap 将导致 hart 的控制转移、模式切换及 CSR 修改。 + +### 控制转移 + +RISC-V 中,trap 可能导致的控制转移及模式切换如下图所示: + +```mermaid +graph BT +M[M-mode/MRET]---HS[HS-mode/SRET]-----VS[VS-mode/SRET]----VU[VU-mode] + +HS-------U[U-mode] +U-.trap-.->M + +HS-.trap-.->M +HS==trap by medeleg or mideleg==>HS + +VS-.trap-.->M +VU-.trap-.->M + +VS==trap by medeleg or mideleg==>HS +VU==trap by hedeleg or hideleg==>VS + +``` + +从上图可知,正常情况下 trap 都会导致 hart 的控制转移至 M-mode,处理之后通过 `mret` 指令返回到原来的模式。 + +特殊情况下 trap 会经由 `mdeleg` 或 `mideleg` 委派从 HS-mode 或 VS-mode 转移至 HS-mode,或再经由 `hedeleg` 或 `hideleg` 委派从 VU-mode 转移至 VS-mode. + +被委派至 HS-mode 和 VS-mode 的 trap 在处理完毕后,将通过 `sret` 指令返回至 trap 之前的模式。 + +### 虚拟模式和特权级切换、CSR 修改 + +虚拟化模式及特权级的切换对应上述控制转移图,具体对于 CSR 的修改包含如下情况: + +trap 到 **M-mode**, V=0, **`msatatus`**'s fields MPV (Machine Previous Virtualization), MPP (Machine Previous Previlige) 依据下表设置: + +| Previous Mode | msatatus.MPV | msatatus.MPP | +| ------------- | ------------ | ------------ | +| U-mode | 0 | 0 | +| HS-mode | 0 | 1 | +| M-Mode | 0 | 3 | +| VU-mode | 1 | 0 | +| VS-mode | 1 | 1 | + +并修改 `mstatus` 的 GVA, MIE, MPIE 位,修改 CSR `mepc`, `mcause`, `mtval`, `mtval2`, `mtinst`. + +trap 到 **HS-mode**, V=0, **`hstatus`** 的 MPV 和 MPP 位调整如下: + +| Previous Mode | hstatus.SPV | hstatus.SPP | +| ------------- | ----------- | ----------- | +| U-mode | 0 | 0 | +| HS-mode | 0 | 1 | +| VU-mode | 1 | 0 | +| VS-mode | 1 | 1 | + +若 trap 前 V=1,`hstatus.SPVP = sstatus.SPP`;若 trap 前 V=0,保持不变。 +trap 到 HS-mode 要求写 `hstatus.GVA`, `sstatus.SIE`, `sstatus.SPIE` 和 CSR `sepc`, `scause`, `stval`, `htval`, `htinst`. + +trap 到 **VS-mode**,`vsstatus.SPP` 依照下表设置: + +| Previous Mode | vsstatus.SPP | +| ------------- | ------------ | +| VU-mode | 0 | +| VS-mode | 1 | + +`hstatus`, `sstatus` 不修改,V=1;写 `vsstatus.PIE`, `vsstatus.SPIE` 和 CSR `vsepc`, `vscause`, `vstval` + +## 返回指令与虚拟化 + +### 相关指令及其作用 + +`mret`, `sret` 两条指令分别用于 trap 从 M-mode 和 S-mode 返回,这两条指令的执行也对应着特定 CSR 的修改,与 trap 陷入时的 CSR 修改行为有所区别,相关的主要的 CSR 修改参见 [Trap 相关 CSR](#相关-csr-简介) 一节。 + +### QEMU 中的实现 + +```C{.line-numbers} +// +// qemu/target/riscv/op_helper.c +// +target_ulong helper_sret(CPURISCVState *env) +{ + uint64_t mstatus; + target_ulong prev_priv, prev_virt; + + // exception handling + ... + + mstatus = env->mstatus; + + // with H-mode support and it is diabled + if (riscv_has_ext(env, RVH) && !riscv_cpu_virt_enabled(env)) { + /* We support Hypervisor extensions and virtulisation is disabled */ + target_ulong hstatus = env->hstatus; + + prev_priv = get_field(mstatus, MSTATUS_SPP); + prev_virt = get_field(hstatus, HSTATUS_SPV); + + // set mstatus and hstatus of env + hstatus = set_field(hstatus, HSTATUS_SPV, 0); + mstatus = set_field(mstatus, MSTATUS_SPP, 0); + mstatus = set_field(mstatus, SSTATUS_SIE, + get_field(mstatus, SSTATUS_SPIE)); + mstatus = set_field(mstatus, SSTATUS_SPIE, 1); + + env->mstatus = mstatus; + env->hstatus = hstatus; + + // check whether to swap vs, s, hs CSR values + if (prev_virt) { + riscv_cpu_swap_hypervisor_regs(env); + } + + // set VIRT_ONOFF to prev_virt + riscv_cpu_set_virt_enabled(env, prev_virt); + } else { + prev_priv = get_field(mstatus, MSTATUS_SPP); + + mstatus = set_field(mstatus, MSTATUS_SPP, PRV_U); + mstatus = set_field(mstatus, MSTATUS_SIE, + get_field(mstatus, MSTATUS_SPIE)); + mstatus = set_field(mstatus, MSTATUS_SPIE, 1); + env->mstatus = mstatus; + } + + // set env virt mode to prev_virt + riscv_cpu_set_mode(env, prev_priv); + + return retpc; +} +``` + +在上述的`if...else...`中有公共的关于`mstatus`的代码块: + +```C{.line-numbers}{.line-numbers} +mstatus = set_field(mstatus, MSTATUS_SPP, PRV_U); +mstatus = set_field(mstatus, MSTATUS_SIE, + get_field(mstatus, MSTATUS_SPIE)); +mstatus = set_field(mstatus, MSTATUS_SPIE, 1); +env->mstatus = mstatus; +``` + +他们被用于在执行`sret`时设置`mstatus`的`SPP`, `SIE`, `SPIE`区域,对应 [`mstatus`CSR](#mstatus) 部分。 + +每当 trap 被引入 HS-mode,`hstatus.SPV`就会被写入 trap 时的 V 值。此处代码中,若当前系统支持 H 指令集扩展且当前虚拟化未开启,将通过代码`hstatus = set_field(hstatus, HSTATUS_SPV, 0);`将`hstatus`的`SPV`(Supervisor Previous Virtualization)置零,然后依据 trap 之前的虚拟模式`prev_virt`判断是否要修改 VS/S/HS 模式的 CSR 的值,如下方代码所示: + +```C{.line-numbers} +// +// qemu/target/riscv/cpu_helper.c: line 465-525 +// +void riscv_cpu_swap_hypervisor_regs(CPURISCVState *env) +{ + uint64_t mstatus_mask = MSTATUS_MXR | MSTATUS_SUM | + MSTATUS_SPP | MSTATUS_SPIE | MSTATUS_SIE | + MSTATUS64_UXL | MSTATUS_VS; + + if (riscv_has_ext(env, RVF)) { + mstatus_mask |= MSTATUS_FS; + } + // true if H-extension is supported and virt_ONOFF is 1 + bool current_virt = riscv_cpu_virt_enabled(env); + + g_assert(riscv_has_ext(env, RVH)); + + if (current_virt) { + /* Current V=1 and we are about to change to V=0 */ + env->vsstatus = env->mstatus & mstatus_mask; + env->mstatus &= ~mstatus_mask; + env->mstatus |= env->mstatus_hs; + + env->vstvec = env->stvec; + env->stvec = env->stvec_hs; + + ... + } else { + /* Current V=0 and we are about to change to V=1 */ + env->mstatus_hs = env->mstatus & mstatus_mask; + env->mstatus &= ~mstatus_mask; + env->mstatus |= env->vsstatus; + + env->stvec_hs = env->stvec; + env->stvec = env->vstvec; + + ... + } +} +``` + +`riscv_cpu_swap_hypervisor_regs`函数实现了如下图所示的寄存器内容交换: + +```mermaid +graph BT + +subgraph From V=1 to V=0 + +S[S CSRs]--1--->VS1[VS CSRs] +HS1[HS CSRs]--2--->S + +end + +subgraph From V=0 to V=1 + +S--1--->HS2[HS CSRs] +VS2[VS CSRs]--2--->S + +end + +``` + +```C{.line-numbers} +// +// qemu/target/riscv/cpu_helper.c: line 558-583 +// +void riscv_cpu_set_virt_enabled(CPURISCVState *env, bool enable) +{ + if (!riscv_has_ext(env, RVH)) { + return; + } + + /* Flush the TLB on all virt mode changes. */ + if (get_field(env->virt, VIRT_ONOFF) != enable) { + tlb_flush(env_cpu(env)); + } + + env->virt = set_field(env->virt, VIRT_ONOFF, enable); + + if (enable) { + /* + * The guest external interrupts from an interrupt controller are + * delivered only when the Guest/VM is running (i.e. V=1). This means + * any guest external interrupt which is triggered while the Guest/VM + * is not running (i.e. V=0) will be missed on QEMU resulting in guest + * with sluggish response to serial console input and other I/O events. + * + * To solve this, we check and inject interrupt after setting V=1. + */ + riscv_cpu_update_mip(env_archcpu(env), 0, 0); + } +} +``` + +### Spike 中的实现 + +```C{.line-numbers} +// +// riscv-isa-sim/riscv/insns/sret.h +// +require_extension('S'); +reg_t prev_hstatus = STATE.hstatus->read(); +if (STATE.v) { + if (STATE.prv == PRV_U || get_field(prev_hstatus, HSTATUS_VTSR)) + // if (unlikely(STATE.v)) throw trap_virtual_instruction(insn.bits()) + require_novirt(); +} else { + // decide M-mode or S-mode to handle trap + require_privilege(get_field(STATE.mstatus->read(), MSTATUS_TSR) ? PRV_M : PRV_S); +} +reg_t next_pc = p->get_state()->sepc->read(); +set_pc_and_serialize(next_pc); +reg_t s = STATE.sstatus->read(); +reg_t prev_prv = get_field(s, MSTATUS_SPP); +s = set_field(s, MSTATUS_SIE, get_field(s, MSTATUS_SPIE)); +s = set_field(s, MSTATUS_SPIE, 1); +s = set_field(s, MSTATUS_SPP, PRV_U); +STATE.sstatus->write(s); +p->set_privilege(prev_prv); +if (!STATE.v) { + if (p->extension_enabled('H')) { + reg_t prev_virt = get_field(prev_hstatus, HSTATUS_SPV); + p->set_virt(prev_virt); + reg_t new_hstatus = set_field(prev_hstatus, HSTATUS_SPV, 0); + STATE.hstatus->write(new_hstatus); + } + + STATE.mstatus->write(set_field(STATE.mstatus->read(), MSTATUS_MPRV, 0)); +} +``` + +相较于 QEMU 的处理逻辑 (两种情况:支持 H 扩展且未开启虚拟化和其他),Spike 的处理逻辑有所不同: + +1. 先判断是否已经开启虚拟化,若已经开启且支持 trap (U-mode 或 H-mode 下 hstatus.vtsr=1) 则通过 `require_novirt()` 处理 trap,若当前为非虚拟化模式,则通过 `require_privilege()` 请求对应的处理 trap 的特权级,其中 `mstatus.TSR` 表示是否允许在 M-mode 下执行 trap 返回指令。 +2. 设置 trap 返回的 PC 值:`set_pc_and_serialize(next_pc)` +3. 修改并保存 `sstatus` 的 `SPP`, `SIE`, `SPIE` 位 +4. 根据系统对 H 扩展的支持情况和当前所在的虚拟模式设置 `hstatus` 和 `mstatus` 的值 + 1. 如已经处于虚拟模式 (V=1) ,无操作 + 2. 若 V=0,`mstatus.MPRV` 置为 0,如果支持 H 扩展,修改 `hstatus.SPV` 为 0,恢复 trap 之前的虚拟模式。 + +### Linux Kernel KVM 中模式切换的实现 + +KVM 可以帮助 Linux Kernel 完成管理 Guest 等归属于 supervisor 的任务,下面将结合 Linux 内核源码中关于 KVM 如何创建一个虚拟 CPU 并管理 Host/Guest 切换的代码实现,分析虚拟化模式的切换机制。 + +创建一个 vCPU,初始化其指令集、CSR(`sstatus`, `hstatus`): + +```C{.line-numbers} +// +// linux/arch/riscv/kvm/vcpu.c: line 97-130 +// +int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) +{ + struct kvm_cpu_context *cntx; + struct kvm_vcpu_csr *reset_csr = &vcpu->arch.guest_reset_csr; + + /* Mark this VCPU never ran */ + vcpu->arch.ran_atleast_once = false; + vcpu->arch.mmu_page_cache.gfp_zero = __GFP_ZERO; + + /* Setup ISA features available to VCPU */ + vcpu->arch.isa = riscv_isa_extension_base(NULL) & KVM_RISCV_ISA_ALLOWED; + + /* Setup VCPU hfence queue */ + spin_lock_init(&vcpu->arch.hfence_lock); + + /* Setup reset state of shadow SSTATUS and HSTATUS CSRs */ + cntx = &vcpu->arch.guest_reset_context; + // 按位与即为分别设置sstatus、hstatus各位 + cntx->sstatus = SR_SPP | SR_SPIE; + cntx->hstatus = 0; + cntx->hstatus |= HSTATUS_VTW; + cntx->hstatus |= HSTATUS_SPVP; + cntx->hstatus |= HSTATUS_SPV; + + /* By default, make CY, TM, and IR counters accessible in VU mode */ + reset_csr->scounteren = 0x7; + + /* Setup VCPU timer */ + kvm_riscv_vcpu_timer_init(vcpu); + + /* Reset VCPU */ + kvm_riscv_reset_vcpu(vcpu); + + return 0; +} +``` + +trap 时,通过如下代码实现 Host 和 Guest 的寄存器替换: + +```C{.line-numbers}{.line-numbers} +// +// arch/riscv/kvm/vcpu_switch.S: line 9-211 +// +ENTRY(__kvm_riscv_switch_to) + /* Save Host GPRs (except A0 and T0-T6) */ + REG_S ra, (KVM_ARCH_HOST_RA)(a0) + REG_S sp, (KVM_ARCH_HOST_SP)(a0) + // ... ra-s11 + + /* Load Guest CSR values */ + REG_L t0, (KVM_ARCH_GUEST_SSTATUS)(a0) + REG_L t1, (KVM_ARCH_GUEST_HSTATUS)(a0) + REG_L t2, (KVM_ARCH_GUEST_SCOUNTEREN)(a0) + la t4, __kvm_switch_return + REG_L t5, (KVM_ARCH_GUEST_SEPC)(a0) + + /* Save Host and Restore Guest SSTATUS */ + csrrw t0, CSR_SSTATUS, t0 + + /* Save Host and Restore Guest HSTATUS */ + csrrw t1, CSR_HSTATUS, t1 + + /* Save Host and Restore Guest SCOUNTEREN */ + csrrw t2, CSR_SCOUNTEREN, t2 + + /* Save Host STVEC and change it to return path */ + csrrw t4, CSR_STVEC, t4 + + /* Save Host SSCRATCH and change it to struct kvm_vcpu_arch pointer */ + csrrw t3, CSR_SSCRATCH, a0 + + /* Restore Guest SEPC */ + csrw CSR_SEPC, t5 + + /* Store Host CSR values */ + REG_S t0, (KVM_ARCH_HOST_SSTATUS)(a0) + REG_S t1, (KVM_ARCH_HOST_HSTATUS)(a0) + // ... t0-t4 + + /* Restore Guest GPRs (except A0) */ + REG_L ra, (KVM_ARCH_GUEST_RA)(a0) + REG_L sp, (KVM_ARCH_GUEST_SP)(a0) + // ... ra-s11, t3-t6 + + /* Restore Guest A0 */ + REG_L a0, (KVM_ARCH_GUEST_A0)(a0) + + /* Resume Guest */ + sret + + /* Back to Host */ + .align 2 +__kvm_switch_return: + /* Swap Guest A0 with SSCRATCH */ + csrrw a0, CSR_SSCRATCH, a0 + + /* Save Guest GPRs (except A0) */ + REG_S ra, (KVM_ARCH_GUEST_RA)(a0) + REG_S sp, (KVM_ARCH_GUEST_SP)(a0) + // ... ra-s11, t3-t6 + + /* Load Host CSR values */ + REG_L t1, (KVM_ARCH_HOST_STVEC)(a0) + REG_L t2, (KVM_ARCH_HOST_SSCRATCH)(a0) + REG_L t3, (KVM_ARCH_HOST_SCOUNTEREN)(a0) + REG_L t4, (KVM_ARCH_HOST_HSTATUS)(a0) + REG_L t5, (KVM_ARCH_HOST_SSTATUS)(a0) + + /* Save Guest SEPC */ + csrr t0, CSR_SEPC + + /* Save Guest A0 and Restore Host SSCRATCH */ + csrrw t2, CSR_SSCRATCH, t2 + + /* Restore Host STVEC */ + csrw CSR_STVEC, t1 + + /* Save Guest and Restore Host SCOUNTEREN */ + csrrw t3, CSR_SCOUNTEREN, t3 + + /* Save Guest and Restore Host HSTATUS */ + csrrw t4, CSR_HSTATUS, t4 + + /* Save Guest and Restore Host SSTATUS */ + csrrw t5, CSR_SSTATUS, t5 + + /* Store Guest CSR values */ + REG_S t0, (KVM_ARCH_GUEST_SEPC)(a0) + REG_S t2, (KVM_ARCH_GUEST_A0)(a0) + // t0, t2-t5 + + /* Restore Host GPRs (except A0 and T0-T6) */ + REG_L ra, (KVM_ARCH_HOST_RA)(a0) + REG_L sp, (KVM_ARCH_HOST_SP)(a0) + // ra-s11 + + /* Return to C code */ + ret +ENDPROC(__kvm_riscv_switch_to) +``` + +以上代码所实现的 Host 与 Guest 替换的过程可以整理为如下表格。第一列表示保存 Host 并加载 Guest 到硬件,第二列表示保存 trap 处理完毕的 Guest 并重新加载 Host 到硬件。 + +| Save Host From and Load Guest to Machine | Save Guest from and Load Host to Machine | +| ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| 1.1 Save Host GPR | 2.1 Save Guest GPRs | +| 1.2 Load Guest CSR to `t0-t2`, `t4-t5` | 2.2 Load Host CSRs to `t1-t5` | +| 1.3 Swap Physical CSR with `t0-t2`, `t4` to pre-store Host CSR and restore Guest CSR | 2.3 Swap Physical CSR with `t2-t5` to pre-store Guest CSRs and restore Host CSRs | +| 1.4 Save SSCRATCH of Host and change it to save Host A0 (`csrrw t3, CSR_SSCRATCH, a0`) | 2.4 Restore Host STVEC (`csrw CSR_STVEC, t1`) | +| 1.5 Restore Guest SEPC(`csrw CSR_SEPC, t5`) | 2.5 Save Guest SEPC(`csrr t0, CSR_SEPC`); | +| 1.6 Store Host CSRs(`t0-t4`) | 2.6 Store Guest CSRs(`t0`, `t2-t5`) | +| 1.7 Restore Guest GPRs and A0 | 2.7 Restore Host GPRs | +| Resume Guest: `sret` | Return to C code (ret) | +| Swap a0 and SSCRATCH-let SSCRATCH has Guest a0 and a0 has Host a0(`csrrw a0, CSR_SSCRATCH, a0`) | | + +单独考虑保存 Host 并加载 Guest 到硬件的过程,其细节如下图所示: + +```mermaid +graph LR + +A0[a0] +Gs[GPRs] +Cs[CSRs] +Ts[t0, t1, ...] +HG[Host GPRs] +HC[Host CSRs] +HA0[Host a0] +GG[Guest GPRs] +GC[Guest CSRs] +GA0[Guest a0] + + +subgraph Physical Registers +A0 +Gs +Cs +Ts +end + +subgraph Host Virtual Registers +HA0[Host a0] +HG +HC +end + +subgraph Guest Virtual Regsiters +GA0[Guest a0] +GG +GC +end + +Gs--1.1 save host GPR-->HG +GC--1.2 load gust CSR-->Ts + +Ts--1.3 csrrw to restore guest CSR-->Cs +Cs--1.3 csrrw to pre-store host CSR-->Ts + +Cs--1.4 csrrw to save host SSCRATCH in t3-->Ts +Ts--1.4 csrrw to save host a0 in SSCRATCH-->Cs +HC-.1.4 t3 = sscratch-.-Ts +HA0-.1.4 sscratch = a0-.-Cs + +Ts--1.5 csrw to restore guest SEPC-->Cs +Ts--1.6 store host CSR-->HC + +GG--1.7 restore guest GPR-->Gs +GA0--1.7 restore gust A0-->A0 + +``` + +## 结语 + +本文结合 QEMU、Spike、KVM 源码及特权指令级手册对于 RISC-V 虚拟化的实现、特权级 (虚拟化模式 V) 切换机制及其实现进行了简要分析,明确了模式切换的各类条件以及处理方式。 + +不足之处在于,当前对模式切换过程中涉及的诸多寄存器修改细节并不足够明确,例如,在 Spike 和 QEMU 中,都有对 `sret` 指令的实现,但是目前无法理解为什么两者对于 `mstatus`, `hstatus`, `sstatus`, `vsstatus` 等 CSR 的修改行为不同。这部分有待后续深入分析,或者向社区开发者咨询澄清。 + +## 参考资料 + +- [RISC-V 特权指令集手册](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf) +- [Cloud Lab](https://gitee.com/tinylab/cloud-lab) +- [Linux Lab](https://gitee.com/tinylab/linux-lab) +- [RISC-V Linux](https://gitee.com/tinylab/linux-lab) diff --git a/articles/images/20220723-virt-mode/hstatus.png b/articles/images/20220723-virt-mode/hstatus.png new file mode 100644 index 0000000000000000000000000000000000000000..a10f926fa3aacdeb91cd8df25891a743b33b65eb Binary files /dev/null and b/articles/images/20220723-virt-mode/hstatus.png differ diff --git a/articles/images/20220723-virt-mode/mstatus.png b/articles/images/20220723-virt-mode/mstatus.png new file mode 100644 index 0000000000000000000000000000000000000000..2135264dc55a8d41ac82defab5c0bbcb7249b9ab Binary files /dev/null and b/articles/images/20220723-virt-mode/mstatus.png differ diff --git a/articles/images/20220723-virt-mode/sstatus.png b/articles/images/20220723-virt-mode/sstatus.png new file mode 100644 index 0000000000000000000000000000000000000000..ab69f80d9e23df062f453b7256e81d0a1cadd145 Binary files /dev/null and b/articles/images/20220723-virt-mode/sstatus.png differ