深入解析 RISC-V 虚拟化中的 UEFI 固件配置:从 XML 到 NVRAM 的生命周期管理

一次 virsh create 失败引发的技术深潜:理解 libvirt 为何找不到 "master var store",以及 virsh undefine 背后删除 VARS.fd 的设计哲学。

一、引言:一个看似简单的错误背后

在 RISC-V 虚拟化实践中,你可能会遇到这样的场景:物理机下电前虚拟机一切正常,重启后执行 virsh create vm.xml 却得到:

复制代码
error: operation failed: unable to find any master var store for loader: /usr/share/edk2/riscv/RISCV_VIRT_CODE.fd

更令人困惑的是,当你尝试清理 domain 时,virsh undefine vm 会"贴心"地删除 /var/lib/libvirt/qemu/nvram/YOUR_VM_NAME_VARS.fd------这个文件里恰好保存了虚拟机的 UEFI 启动顺序、设备配置等关键状态。

作为 RISC-V OS 虚拟化工程师,理解这两个现象背后的底层机制,不仅能帮你快速修复故障,更能写出符合 libvirt 最佳实践的 domain XML。本文将从固件设计、pflash 模拟、libvirt 对象模型三个层次展开分析。

二、UEFI 固件的"二元性"与 RISC-V 的实践

2.1 传统 BIOS 与 UEFI 的根本差异

在传统 BIOS 虚拟化中,固件是一个只读的 ROM 镜像,虚拟机启动时直接映射到物理地址空间的高端(如 0xFFFF_FFF0)。所有配置(启动顺序、时间、硬件状态)存储在 CMOS 或主板上的独立 EEPROM 中。这种设计简单,但扩展性差。

UEFI (Unified Extensible Firmware Interface) 则将固件分为两个逻辑部分:

  • 代码区 (Code):只读的 PE/COFF 可执行映像,包含驱动、协议栈、启动管理器。
  • 变量存储区 (Variable Store) :可读写区域,存储 EFI_VARIABLE 结构(如 BootOrder、Boot####、SecureBoot keys 等)。

在硬件上,这两部分通常对应两个独立的 SPI Flash 芯片,或者一个 Flash 的两个不同分区。QEMU 模拟这种设计时,使用 pflash (Parallel Flash) 设备,通过两条独立的 -drive 命令行来模拟:

bash 复制代码
-drive file=RISCV_VIRT_CODE.fd,if=pflash,format=raw,unit=0,readonly=on
-drive file=vm_vars.fd,if=pflash,format=raw,unit=1

2.2 RISC-V "virt" 平台的特殊性

RISC-V 架构的 virt 机器类型在 QEMU 中是一个纯虚拟平台(无真实硬件对应),其固件加载方式借鉴了 ARM virt 平台的设计:UEFI 固件放置于 Flash 设备 0(代码)和 Flash 设备 1(变量)。与 x86 的 OVMF 不同,RISC-V 使用的 EDK II 固件(RISCV_VIRT_CODE.fd)是一个专门为 -M virt 构建的独立二进制。

关键事实RISCV_VIRT_CODE.fd 本身不包含 内嵌的变量存储区域。它必须与一个可写的 VARS.fd 文件配对才能工作。这与某些平台(如早期的 ARM 固定虚拟磁盘)不同,后者可能将代码和变量打包在同一个 Flash 映像中。

三、libvirt 的固件抽象:从 XML 到 QEMU 命令行

3.1 传统手动配置方式(已不推荐)

在较旧的 libvirt 版本中,用户需要在 domain XML 的 <os> 块中显式指定 loader 和 nvram:

xml 复制代码
<os>
  <type arch='riscv64' machine='virt'>hvm</type>
  <loader readonly='yes' type='pflash'>/usr/share/edk2/riscv/RISCV_VIRT_CODE.fd</loader>
  <nvram>/var/lib/libvirt/qemu/nvram/vm1_VARS.fd</nvram>
  <boot dev='hd'/>
</os>

libvirt 会做两件事:

  1. loader 映射到 pflash unit 0(只读)。
  2. nvram 映射到 pflash unit 1(读写),如果该文件不存在,则尝试从某个"master var store"模板复制。

问题出现了 :这里的 "master var store" 是什么?libvirt 需要找到一个与 loader 配套的、未经修改的原始变量模板 ,用来初始化每个虚拟机的私有 nvram 文件。这个模板通常应该与 CODE.fd 同目录,命名为 RISCV_VIRT_VARS.fd。但在你的系统中,要么该文件缺失,要么 libvirt 的配置文件 qemu.conf 中未注册该配对关系。

报错 "unable to find any master var store for loader" 正是 libvirt 尝试从模板复制创建 nvram 文件时,找不到模板的明确表现。

3.2 现代化的固件自动选择机制

libvirt 6.5.0 之后引入了 <os firmware='efi'> 的声明式配置,彻底解决了手工配对的痛苦:

xml 复制代码
<os firmware='efi'>
  <type arch='riscv64' machine='virt'>hvm</type>
  <boot dev='hd'/>
</os>

此时 libvirt 会查询内部注册的固件列表(来自 /usr/share/libvirt/firmware/ 下的 JSON 描述文件),自动匹配架构、机器类型,并返回包含 codevars 模板的完整固件定义。用户无需关心 loadernvram 的具体路径。

底层实现 :libvirt 内部维护了 virFirmware 对象,每个固件包含:

  • name:如 "UEFI RISC-V Virt"
  • features:如 "pflash", "nvram-template"
  • mapping:指向 code 文件和 vars 模板的绝对路径。

当虚拟机启动时,libvirt 会为每个 domain 创建一个独立的 nvram 文件(路径通常为 /var/lib/libvirt/qemu/nvram/${domain}_VARS.fd),从模板复制内容,然后传给 QEMU。

四、virsh undefine 与 NVRAM 的生命周期

4.1 预期的行为:是否应该删除 VARS.fd

当一个 domain 被 undefine(即从 libvirt 配置数据库中移除)时,以下文件如何处理?

  • domain XML 定义文件(通常 /etc/libvirt/qemu/${domain}.xml)→ 删除
  • 磁盘镜像(*.qcow2)→ 默认保留 (除非用 --remove-all-storage
  • UEFI 变量存储 nvram 文件 → 默认删除

这个设计令人惊讶------为何磁盘保留而 NVRAM 删除?答案在于 libvirt 将 NVRAM 视为 domain 配置的一部分,而非用户数据。

4.2 深入 libvirt 的对象模型

libvirt 区分两种资源:

  • 持久性配置 :包括 domain XML、网络定义、存储池定义等,由 define 命令管理。
  • 运行时数据:包括 domain 的 NVRAM 文件、临时快照、日志等,依附于具体 domain 对象。

当你执行 virsh undefine vm(不带额外标志)时,libvirt 认为你希望完全删除这个虚拟机的配置。UEFI 变量存储区中保存的 BootOrder、MAC 地址池、安全启动密钥等,在 libvirt 看来属于配置范畴而非用户数据,因此被一并清理。磁盘镜像(如 OS 根分区)则被视为独立资源,不会被删除,以便后续可以重新定义 domain 并重用。

4.3 为什么用户会感到困惑?

根本原因是 NVRAM 状态与 domain XML 的不对称

  • 用户在 XML 中看到的 <nvram> 只是指向一个文件路径,文件内容(如 BootOrder=0001,0002)并未体现在 XML 中。
  • 执行 undefine 后,这些状态完全丢失。如果你用同样的磁盘重新定义 domain(XML 中不指定 <nvram>),libvirt 会从 master var store 模板创建一个全新的变量区 ,导致:
    • 启动顺序重置(可能从网络或 EFI Shell 开始)
    • 安全启动状态丢失
    • 固件配置回到出厂设置

4.4 保留 NVRAM 的正确方法

如果你想在 undefine 后保留 UEFI 变量状态,有两种方式:

  1. 显式保留 NVRAM 文件

    bash 复制代码
    virsh undefine vm --keep-nvram

    该标志告诉 libvirt 只删除 XML 定义,保留 /var/lib/libvirt/qemu/nvram/vm_VARS.fd

  2. 手动管理 NVRAM 路径 :在 XML 中将 <nvram> 指向一个独立于 libvirt 默认目录的路径(如 /srv/nvram/vm_VARS.fd),undefine 时 libvirt 不会触及该文件(它只自动删除默认路径下的 nvram)。

五、RISC-V 场景的特殊注意事项

5.1 固件社区现状

RISC-V 虚拟化固件仍在快速演进。截至 2026 年,主流发行版提供的 EDK II 包可能包含:

  • /usr/share/edk2/riscv/RISCV_VIRT_CODE.fd
  • /usr/share/edk2/riscv/RISCV_VIRT_VARS.fd

但早期版本或某些构建可能只提供一个 RISCV_VIRT.fd(代码+变量合并)。当 libvirt 检测到 CODE.fd 的命名模式但没有对应的 VARS.fd 模板时,就会触发 "unable to find master var store" 错误。

5.2 推荐的最佳实践

  1. 永远优先使用 <os firmware='efi'>,不要手动写 <loader>。这能让 libvirt 自动匹配正确的固件对,并正确处理模板复制。
  2. 如果需要调试,使用 virsh domcapabilities --machine virt --arch riscv64 查看支持的固件列表。
  3. 在备份虚拟机时,不仅要备份磁盘镜像,还要备份对应的 NVRAM 文件(如 /var/lib/libvirt/qemu/nvram/*_VARS.fd),否则恢复后可能无法启动。
  4. 使用 virsh undefine 前,明确是否需要保留 UEFI 配置,酌情添加 --keep-nvram

六、总结:从故障现象到设计哲学

现象 底层原因 解决方案
unable to find master var store libvirt 找不到与 CODE 配对的 VARS 模板文件 使用 <os firmware='efi'> 或手动指定 <nvram template='...'>
undefine 后 VARS 文件丢失 libvirt 将 NVRAM 视为 domain 配置,默认清理 使用 --keep-nvram 或自定义路径

理解这两个问题的关键,在于认识到 UEFI 固件的二元性 (只读 code + 可写 vars)以及 libvirt 的资源管理边界(配置 vs 数据)。RISC-V 作为新兴平台,其固件工具链仍在完善,遵循 libvirt 的声明式固件接口是避免踩坑的最稳健方式。

下一次当你编辑 domain XML 时,请记住:那一行 <os firmware='efi'/> 背后,是 libvirt 为你自动处理的模板查找、文件复制与生命周期管理。而 virsh undefine --keep-nvram,则体现了虚拟化平台对"配置与数据分离"这一古老原则的忠诚。


延伸阅读

相关推荐
道川贤林2 小时前
OrangePi 系统启动优先级修改
linux·linux驱动·orangepi·u-boot
xsc-xyc2 小时前
用 Tailscale + Syncthing 实现手机、电脑与 NAS 的跨网络文件同步
linux·网络·网络安全·智能手机·电脑
IsJunJianXin2 小时前
pdd小程序 cdp 保存响应体
linux·服务器·小程序·pdd小程序·拼多多响应体解密·小程序cdp·拼多多rpc取响应体
爱就是恒久忍耐3 小时前
现代CMake的build方式
linux·运维·服务器
古城小栈4 小时前
Python 的主流Ai框架为什么优先适配 Linux 系统?
linux·人工智能·python
字节高级特工5 小时前
【Linux】C语言进程地址空间分布
linux·c++·后端·算法
黑白园5 小时前
【环境搭建】Ubuntu安装(一)
linux·ubuntu
aFakeProgramer5 小时前
S-CORE Docker 环境
linux
error:(5 小时前
Ubuntu 22.04 GNOME远程桌面配置问题排查与解决全流程
linux·运维·ubuntu