[Virtualization](三):RISC-V H-extension 与 Guest 执行模式

第二篇从 QEMU 启动路径出发,看了一台 RISC-V 虚拟机如何被 QEMU 拼出来、如何通过 device tree 交给 Guest Linux、以及 TCG 和 KVM 在 CPU 执行路径上的差异。第三篇进入架构层:RISC-V Hypervisor extension,简称 H-extension,究竟怎样让 Host Linux 管理 Guest Linux?

1. 本篇要回答的问题

虚拟化最核心的问题之一是权限。

Guest Linux 需要像普通内核一样运行:

  • 管理自己的页表
  • 处理中断和异常
  • 调度自己的进程
  • 访问 supervisor CSR
  • 执行 sret 等特权指令

但它又不能真的拥有整台机器的最高控制权。

所以问题变成:

当 Guest Linux 以为自己运行在 S-mode 时,RISC-V 硬件和 Host Linux 究竟为它制造了怎样的"虚拟 supervisor 世界"?

RISC-V H-extension 的目标,就是让 guest kernel 尽量保持普通 supervisor kernel 的运行模型,同时让 host kernel 可以截获、隔离和管理它。

2. 没有虚拟化时的 RISC-V 执行模式

先回到没有 H-extension 的普通 RISC-V 系统。

常见执行模式可以简化成三层:

text 复制代码
+-----------------------------+
| U-mode                      |
| user applications           |
+-----------------------------+
              ^
              |
              v
+-----------------------------+
| S-mode                      |
| supervisor OS kernel        |
+-----------------------------+
              ^
              |
              v
+-----------------------------+
| M-mode                      |
| machine firmware / SBI      |
+-----------------------------+

每一层大致承担不同职责。

M-mode 是最高权限,通常运行 firmware 或 SBI implementation,例如 OpenSBI。它可以访问 machine-level CSR,管理最底层的 trap、timer、IPI、hart 启停等能力。

S-mode 通常运行操作系统内核,例如 Linux。它管理用户进程、虚拟内存、中断和设备驱动,但在需要更底层平台服务时,会通过 SBI 调用 M-mode。

U-mode 运行普通用户态程序。它不能直接执行 supervisor 或 machine 级别的特权操作。

在这个模型里,Linux kernel 运行在 S-mode,它看到的 supervisor 状态是真实 supervisor 状态。

3. 加入 H-extension 后的新模式

引入 H-extension 后,RISC-V 增加了面向虚拟化的执行语义。

最重要的是多出两类 guest 视角的模式:

text 复制代码
+-----------------------------+
| VU-mode                     |
| guest user applications     |
+-----------------------------+
              ^
              |
              v
+-----------------------------+
| VS-mode                     |
| guest supervisor kernel     |
+-----------------------------+
              ^
              |
              v
+-----------------------------+
| HS-mode                     |
| host supervisor kernel      |
+-----------------------------+
              ^
              |
              v
+-----------------------------+
| M-mode                      |
| machine firmware / SBI      |
+-----------------------------+

这里有两个关键概念。

第一,HS-mode 不是一个全新的独立 privilege mode,而是启用 hypervisor 能力后的 host supervisor 运行环境。Host Linux kernel 通常运行在 HS-mode。

第二,VS-mode 是 guest supervisor mode。Guest Linux kernel 以为自己运行在 S-mode,但硬件会把它放在 virtual supervisor 上下文中。

这句话很重要:

Guest Linux 看到的是 supervisor 世界,Host Linux 看到的是被虚拟化的 supervisor 世界。

因此 guest kernel 可以继续执行很多原本属于 supervisor kernel 的逻辑,而 host kernel 仍能在关键边界上拥有最终控制权。

4. Host 与 Guest 的状态分离

虚拟化不能只靠"多一个模式"。真正麻烦的是状态。

普通 Linux kernel 会使用很多 supervisor 状态,例如:

  • sstatus
  • sie
  • stvec
  • sscratch
  • sepc
  • scause
  • stval
  • satp

如果 host kernel 和 guest kernel 共用同一组状态,就会互相踩踏。

H-extension 的做法是引入 virtual supervisor state。

Guest Linux 访问的很多 supervisor CSR,在虚拟化上下文里会对应到 VS 版本,例如:

  • vsstatus
  • vsie
  • vstvec
  • vsscratch
  • vsepc
  • vscause
  • vstval
  • vsatp

这样一来,Host Linux 可以保留自己的 supervisor 状态,Guest Linux 则拥有一套看起来像 supervisor 的虚拟状态。

可以画成这样:

text 复制代码
Host Linux in HS-mode
  uses: sstatus, sie, stvec, satp, ...

Guest Linux in VS-mode
  uses: vsstatus, vsie, vstvec, vsatp, ...

从 guest kernel 的角度,它仍然像普通 Linux 一样设置 trap vector、打开中断、切换页表。

从 host/KVM 的角度,这些状态属于某个 vCPU context,需要在 vCPU 运行前恢复,在 vCPU 退出后保存。

5. vCPU context:Guest CPU 的软件影子

在 KVM 里,一个虚拟 CPU 不只是一个线程。

它至少需要保存:

  • 通用寄存器
  • guest supervisor CSR
  • virtual supervisor CSR
  • guest timer 状态
  • guest interrupt 状态
  • floating point/vector 状态
  • 当前 guest PC
  • trap/exit 相关信息

可以把 vCPU context 理解成 Guest CPU 的软件影子。

text 复制代码
QEMU vCPU thread
        |
        | ioctl(KVM_RUN)
        v
Linux KVM vCPU context
        |
        | restore guest state
        v
RISC-V CPU runs Guest Linux
        |
        | trap / exit
        v
Linux KVM saves guest state

当 QEMU 调用 KVM_RUN 时,KVM 会把 vCPU context 装载到硬件可见的状态里,然后进入 guest。

当 guest 发生 exit 时,KVM 再把相关状态保存回来,并决定是在内核里处理,还是返回 QEMU。

6. hstatus:控制虚拟化执行环境

hstatus 是 H-extension 中非常关键的 CSR 之一。

它保存和 hypervisor 执行状态有关的信息,用来控制或反映 guest 运行时的一些行为。

具体 bit 很多,第一篇不需要死记。先建立几个方向:

  • 当前是否处在 virtualized execution 相关上下文中
  • guest 访问某些状态时如何解释
  • trap 返回时要回到什么虚拟化状态
  • guest 使用的 XLEN 等模式信息

对读代码来说,更重要的不是背下每个 bit,而是知道 hstatus 属于"host 用来控制 guest 执行环境"的那组寄存器。

后续读 Linux KVM/RISC-V 时,看到 KVM 在 vCPU run 前后设置或保存 hstatus,要把它理解成:

Host 正在为 Guest Linux 准备或恢复一个可以运行的 virtual supervisor 世界。

7. hedeleghideleg:哪些 trap 交给 Guest 自己处理

虚拟化并不意味着所有 trap 都要回到 host。

如果 Guest Linux 自己能够处理某些异常或中断,最好让它直接处理,否则每次都陷入 host 会非常昂贵。

RISC-V 使用 delegation 机制控制 trap 的流向。

在 hypervisor 场景里,两个关键 CSR 是:

  • hedeleg:hypervisor exception delegation
  • hideleg:hypervisor interrupt delegation

它们的作用是决定哪些异常或中断可以交给 VS-mode 的 guest supervisor 处理。

简化理解:

text 复制代码
trap happens while guest is running
        |
        +--> delegated to VS-mode Guest Linux
        |
        +--> trapped to HS-mode Host Linux/KVM

如果一个 trap 被委派给 guest,Guest Linux 会像普通 kernel 一样进入自己的 trap handler。

如果没有被委派,它会陷入 HS-mode,由 Host Linux/KVM 判断该如何处理。

这种机制的目标是减少不必要的 host 介入,同时保留隔离边界。

8. hgatp:第二阶段地址翻译的入口

内存隔离离不开地址翻译。

在虚拟化场景中,Guest Linux 管理自己的页表,但它管理的是 guest physical address,不是真正的 host physical address。

完整路径是:

text 复制代码
Guest virtual address
        |
        | stage 1: controlled by Guest Linux, via vsatp
        v
Guest physical address
        |
        | stage 2: controlled by Host Linux/KVM, via hgatp
        v
Host physical address

这里 vsatphgatp 的分工很清楚。

vsatp 属于 guest 的第一阶段地址翻译。Guest Linux 以为自己像普通内核一样切换页表。

hgatp 属于 host 管理的第二阶段地址翻译。Host Linux/KVM 用它限制 guest physical address 能映射到哪里。

这带来几个关键性质:

  • Guest 可以正常管理自己的用户进程地址空间
  • Host 可以把 guest 内存限制在指定 memory slot 中
  • Guest 不能直接访问 host kernel 或其他 VM 的内存
  • KVM 可以通过 stage-2 fault 进行缺页处理、dirty logging、write protection

所以 hgatp 可以看成 RISC-V 虚拟化内存隔离的核心入口。

9. Guest trap 的几种走向

当 Guest Linux 正在运行时,可能发生很多类型的事件。

例如:

  • guest 用户态程序触发 page fault
  • guest kernel 访问一个不存在的 MMIO 地址
  • guest timer 到期
  • guest 执行 SBI call
  • guest 访问某个需要 host 介入的 CSR
  • stage-2 page fault
  • 外部中断到达

这些事件不一定走同一条路径。

可以粗略分成三类。

9.1 Guest 自己处理

例如 guest 用户态进程的普通 page fault。

这类异常属于 guest OS 的正常职责,理想情况下可以直接交给 Guest Linux 处理。

text 复制代码
Guest user page fault
        |
        v
Guest Linux page fault handler

9.2 KVM 在内核态处理

例如某些虚拟 timer、部分 CSR、stage-2 fault。

这类事件需要 host 介入,但不一定需要返回 QEMU。

text 复制代码
Guest trap
        |
        v
Host Linux KVM
        |
        | update vCPU state / page table / timer
        v
Resume guest

9.3 返回 QEMU 用户态处理

例如访问 QEMU 负责模拟的 MMIO 设备。

text 复制代码
Guest MMIO access
        |
        v
KVM exit to userspace
        |
        v
QEMU device model handles access
        |
        v
KVM_RUN again

理解这三类走向非常重要。

因为虚拟化性能不是只取决于"有没有硬件辅助",还取决于 exit 频率、exit 去哪里处理、能不能减少往返用户态的次数。

10. SBI 在 Guest 虚拟化中的位置

RISC-V 系统里,SBI 是 supervisor binary interface。

普通 Linux 通过 SBI 请求底层 firmware 提供某些服务。

在 guest 场景里,Guest Linux 也可能执行 SBI call,例如:

  • 设置 timer
  • 发送 IPI
  • console putchar,取决于环境
  • hart state management
  • system reset

问题是:Guest Linux 看到的 SBI 服务未必直接来自真实 M-mode firmware。

可能的路径包括:

text 复制代码
Guest Linux SBI call
        |
        +--> handled/emulated by KVM
        |
        +--> forwarded to host SBI / firmware
        |
        +--> returned to QEMU for userspace handling

具体走哪条路径取决于 SBI extension、KVM 实现和平台能力。

所以读 RISC-V KVM 时,SBI call 是一个很好的观察点:它能同时牵出 guest trap、KVM emulation、timer/IPI、以及 host firmware 的边界。

11. H-extension 与 QEMU/KVM 的分工

现在可以把三层关系重新压缩一下。

text 复制代码
QEMU
  - creates VM and vCPU through /dev/kvm
  - builds machine model and devices
  - handles userspace exits

Linux KVM/RISC-V
  - owns vCPU context
  - configures hstatus/hedeleg/hideleg/hgatp
  - enters and exits guest
  - handles traps that can stay in kernel

RISC-V H-extension
  - provides VS/VU execution semantics
  - provides virtual supervisor state
  - provides two-stage address translation
  - provides trap routing controls

QEMU 不需要自己模拟所有 privileged behavior。

Linux KVM 也不需要自己实现完整设备模型。

RISC-V 硬件也不需要知道某个 virtio block 背后是哪个 host 文件。

这三者的边界清楚,虚拟化系统才容易扩展。

12. 读 Linux KVM/RISC-V 代码时可以带着哪些问题

进入源码前,建议先带着问题读,而不是按文件顺序硬啃。

几个适合作为入口的问题:

  • vCPU 创建时,KVM 初始化了哪些 guest CSR?
  • KVM_RUN 前,哪些状态会被写入硬件 CSR?
  • guest exit 后,KVM 如何判断 exit reason?
  • 哪些 trap 会被 KVM 直接处理?
  • 哪些 trap 会返回 QEMU?
  • stage-2 page table 在哪里创建和更新?
  • timer 和 IPI 是如何注入给 guest 的?
  • SBI call 是在哪里被解析和处理的?

对应源码入口可以先看:

  • arch/riscv/kvm/vcpu.c
  • arch/riscv/kvm/vcpu_exit.c
  • arch/riscv/kvm/vcpu_sbi.c
  • arch/riscv/kvm/vcpu_timer.c
  • arch/riscv/kvm/mmu.c
  • arch/riscv/include/asm/kvm_host.h
  • virt/kvm/kvm_main.c

这些文件的具体函数路径留到后续文章展开。

13. 本篇小结

这一篇的重点是把 RISC-V H-extension 的作用放进 QEMU/KVM 的整体路径里。

第一,Guest Linux 并不是直接运行在真实 S-mode 中。

它运行在 VS-mode 中,看到的是一套 virtual supervisor state,例如 vsstatusvstvecvsepcvsatp

第二,Host Linux/KVM 运行在 HS-mode 中。

它负责保存和恢复 vCPU context,配置 hstatushedeleghideleghgatp,并决定 guest trap 的处理路径。

第三,内存隔离依赖 two-stage translation。

Guest Linux 通过 vsatp 管理自己的第一阶段页表,Host Linux/KVM 通过 hgatp 管理第二阶段页表。

第四,不同 trap 有不同归宿。

有些交给 Guest Linux 自己处理,有些由 KVM 在内核态处理,有些返回 QEMU 用户态设备模型。

可以把本篇压缩成一句话:

RISC-V H-extension 为 Guest Linux 制造了一个可以相信的 supervisor 世界,同时让 Host Linux/KVM 在这个世界之外保留最终控制权。

14. 下一篇预告:Linux KVM/RISC-V 的 vCPU 运行路径

下一篇可以从 Linux kernel 源码视角进入 KVM_RUN

会讨论:

  • QEMU 如何通过 ioctl 进入 KVM
  • KVM 如何创建 VM 和 vCPU
  • RISC-V vCPU context 在 kernel 中如何表示
  • KVM_RUN 前后 guest state 如何保存和恢复
  • guest exit reason 如何被分类
  • 哪些 exit 留在 kernel,哪些返回 QEMU
  • arch/riscv/kvm/vcpu.cvcpu_exit.c 时应该抓哪条主线
相关推荐
caishenzhibiao1 小时前
期货先行者主图 同花顺期货通指标
java·c语言·c#
爱笑鱼2 小时前
Binder(七):getCallingUid() 读到的是谁?clearCallingIdentity() 又清掉了什么?
android
史呆芬2 小时前
分布式事务实战:微服务跨服务数据一致性解决方案
java·后端·spring cloud
IT界的老黄牛2 小时前
限流命中后该怎么办:直接丢、阻塞等待、延迟重投三种姿势的取舍
java·rocketmq·redisson·令牌桶·削峰·分布式限流
daad7773 小时前
记录matlab状态机demo
java·网络·matlab
牡丹雅忻13 小时前
AES 加密模式演进:从 ECB、CBC 到 GCM 的 C# 深度实践
java·开发语言·c#
学者猫头鹰3 小时前
Java 8 核心新特性
java
冰心孤城4 小时前
Excel: xls与xlsx格式转换排坑指南
java·前端·excel
自强的小白4 小时前
maven高级(聚合和私服)以及私服的上传和下载
java·maven