Index Tags About

在 Linux 下获得性能更好的 Windows 虚拟机

如果在 GNU/Linux 上使用 QQ, 微信 和 Office 这些软件并不是一件容易事。并且我不知道其中的这些软件会不会偷偷扫描我的硬盘,查看我的文件。所以准备一个隔离区,在这里面使用这些软件是一个更加自然和安全的方案。

隔离区中可用的方案目前来看也就是 Windows 虚拟机,在这里我们选择使用 Windows 10 LTSC。

在 Linux 上当然使用 KVM 这种内核支持的虚拟机方案比较好,不需要自己操心编译内核版本和模块还是比较舒服的。同时我们可以使用 VirtIO 驱动来增强虚拟机性能,包括但不限于磁盘,网络等,但是无法增强图形性能(Windows 虚拟机)1,下文将主要介绍如何通过显卡直通来增强图形性能。

1. 准备阶段

1.1. CPU 虚拟化验证

如果 CPU 硬件级别支持虚拟化技术,就可以直接为虚拟机提供安全高效的硬件资源。

$ egrep -o 'vmx|svm|0xc0f' /proc/cpuinfo

vmx 对应 Intel, svm 对应 AMD。 有输出则证明支持虚拟化技术.

1.2. 安装 qemu 和 virt-manager

$ sudo pacman -S virt-manager qemu bridge-utils
$ sudo systemctl enable --now libvirtd
$ sudo gpasswd -a $USER libvirt

2. 安装系统

系统可以选择创建虚拟磁盘,也可以选择直接使用之前安装在物理磁盘上的系统,后者也被称为磁盘直通。虚拟磁盘也可以通过使用 VirtIO 驱动来增强性能,但是我选择了直通硬盘,所以就不细致介绍前者了。

磁盘直通具有两个优点,一是性能更佳,二是随时直接使用虚拟机内的系统启动,减少磁盘消耗和数据同步的工作。缺点是一旦直通进来以后,该硬盘就被虚拟机独占了(但是我的 Windows 装到了单独的磁盘上,所以这方面不存在问题)。

3. 显卡直通

安装完成系统以后就到了要介绍的重点,显卡直通部分。

显卡直通和磁盘直通一样,也是将显卡直接暴露到虚拟机内。

除了显卡直通,Intel 今年(2023)下半年会推出基于 SR-IOV 的 vGPU 方案,但是目前不知道是否稳定,性能也不如直通一个独立显卡比较好。所以目前我们只考虑直通独立显卡。

3.1. 设置 IOMMU

IOMMU is a generic name for Intel VT-d and AMD-Vi.

如果编译 Linux 时, CONFIG_INTEL_IOMMU_DEFAULT_ON 没有被设置,那么需要手动设置 intel_iommu=on.
仍然需要设置 iommu=pt ,该选项可以避免 Linux 创建不能被直通的设备。

总结一下,我们需要将上述两个选项添加到 Linux 启动参数中。

intel_iommu=on iommu=pt

使用 systemd-boot 的话,直接将上面两个项目加到对应启动文件中即可。

$ cat /boot/loader/entries/arch-xanmod.conf
title Archlinux Xanmod
linux /vmlinuz-linux-xanmod-15iah7
initrd /intel-ucode.img
initrd /initramfs-linux-xanmod-15iah7.img
options root=UUID=xxxxxxxxxxxxx rw iommu=pt intel_iommu=on

重启后可以使用 dmesg | grep -i -e DMAR -e IOMMU 检查输出,判断是否启用 IOMMU 成功。

3.2. 直通显卡到虚拟机中

该动作需要隔离 Nvidia 显卡相关的 IOMMU 分组,并且可以有动态和静态两种方式,我目前不需要 N 卡执行任何非虚拟机上的动作,所以使用静态方式即可。

3.2.1. 静态绑定设备孤立 GPU 及其 IOMMU 分组

该方法在系统启动时便使用 vfio-pci 托管 GPU 设备。

  1. 获取 nvidia 相关卡 id
    使用如下脚本2 获取相关信息:

    #!/bin/bash
    shopt -s nullglob
    for g in $(find /sys/kernel/iommu_groups/* -maxdepth 0 -type d | sort -V); do
        echo "IOMMU Group ${g##*/}:"
        for d in $g/devices/*; do
            echo -e "\t$(lspci -nns ${d##*/})"
        done;
    done;
    

    在我的机器上或取得信息如下:

    IOMMU Group 19:
    01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA107BM [GeForce RTX 3050 Ti Mobile] [10de:25e0] (rev a1)
    01:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:2291] (rev a1)
    

    或者使用如下命令也可以

    lspci -nnk | grep nvidia -i -A 2
    01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA107BM [GeForce RTX 3050 Ti Mobile] [10de:25e0] (rev a1)
    Subsystem: Lenovo GA107BM [GeForce RTX 3050 Ti Mobile] [17aa:382f]
    Kernel driver in use: vfio-pci
    Kernel modules: nvidiafb
    01:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:2291] (rev a1)
    Subsystem: Lenovo Device [17aa:382f]
    Kernel driver in use: vfio-pci
    
  2. 写入 modprobe 配置文件

    $ cat /etc/modprobe.d/vfio.conf
    options vfio-pci ids=10de:25e0,10de:2291
    
  3. 尽早加载 vfio-pci 驱动
    为了避免 Linux 中的其他驱动抢占 GPU,我们需要确保 VFIO-PCI 最早加载以获取 GPU。
    1. 查看 vfio-pci 以何形式存在

      $ find /lib/modules -name '*vfio*'
      /lib/modules/6.3.4-xanmod1-15iah7/kernel/drivers/vfio
      /lib/modules/6.3.4-xanmod1-15iah7/kernel/drivers/vfio/pci/vfio-pci-core.ko
      /lib/modules/6.3.4-xanmod1-15iah7/kernel/drivers/vfio/pci/vfio-pci.ko
      /lib/modules/6.3.4-xanmod1-15iah7/kernel/drivers/vfio/vfio.ko
      /lib/modules/6.3.4-xanmod1-15iah7/kernel/drivers/vfio/vfio_iommu_type1.ko
      

      上图显示 vfio 被编译成了模块。所以我们需要使用一些方法来提前加载 vfio。

    2. 在 udev 加载 GPU 驱动时加载 vfio3

      Linux 系统启动加载顺序

      继续在 /etc/modprobe.d/vfio.conf 后面追加内容

      softdep nouveau pre: vfio-pci
      softdep nvidiafb pre: vfio-pci
      softdep drm pre: vfio-pci
      

      值得指出的是,我在编译 Linux 内核时已经去掉了 nouveau 等依赖(准确地说我买含有双显卡的笔记本一大目的就是方便显卡直通),所以只添加 softdep drm pre: vfio-pci 是不能生效的,需要增加第二行。

  4. 更新 initramfs
    因为所有的 modprobe 文件都被嵌入到了 initramfs 中,所以修改 modprobe 配置文件后都需要重新生成 initramfs 文件4

    # mkinitcpio -P
    
  5. 验证 nvidia 显卡是否被 vfio-pci 驱动托管

    $ sudo dmesg | grep -i vfio
    [    2.809629] VFIO - User Level meta-driver version: 0.3
    [    2.822845] vfio-pci 0000:01:00.0: vgaarb: changed VGA decodes: olddecodes=io+mem,decodes=io+mem:owns=none
    [    2.825759] vfio_pci: add [10de:25e0[ffffffff:ffffffff]] class 0x000000/00000000
    [    3.534716] vfio_pci: add [10de:2291[ffffffff:ffffffff]] class 0x000000/00000000
    

    如果能获得上面的输出救证明显卡已经被 vfio 驱动托管了。

  6. 为了离电续航考虑,vfio-pci 应该在不使用该显卡时自动将其置为 d3-cold 状态
    可以使用5

    $ cat /sys/bus/pci/devices/0000:01:00.0/power_state
    

    来查看具体状态。

    $ sudo lspci -vv | grep -i 01:00 -A 50
    01:00.0 VGA compatible controller: NVIDIA Corporation GA107BM [GeForce RTX 3050 Ti Mobile] (rev a1) (prog-if 00 [VGA controller])
    Subsystem: Lenovo GA107BM [GeForce RTX 3050 Ti Mobile]
    Physical Slot: 1
    Control: I/O- Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
    Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
    Interrupt: pin A routed to IRQ 0
    IOMMU group: 19
    Region 0: Memory at 60000000 (32-bit, non-prefetchable) [disabled] [size=16M]
    Region 1: Memory at 6000000000 (64-bit, prefetchable) [disabled] [size=4G]
    Region 3: Memory at 6100000000 (64-bit, prefetchable) [disabled] [size=32M]
    Region 5: I/O ports at 5000 [disabled] [size=128]
    Expansion ROM at 61000000 [virtual] [disabled] [size=512K]
    Capabilities: [60] Power Management version 3
    Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0+,D1-,D2-,D3hot+,D3cold-)
    Status: D0 NoSoftRst+ PME-Enable- DSel=0 DScale=0 PME-
    Capabilities: [68] MSI: Enable- Count=1/1 Maskable- 64bit+
    Address: 0000000000000000  Data: 0000
    Capabilities: [78] Express (v2) Legacy Endpoint, MSI 00
    DevCap:    MaxPayload 256 bytes, PhantFunc 0, Latency L0s unlimited, L1 <64us
    ExtTag+ AttnBtn- AttnInd- PwrInd- RBE+ FLReset+
    DevCtl:    CorrErr- NonFatalErr- FatalErr- UnsupReq-
    RlxdOrd+ ExtTag+ PhantFunc- AuxPwr- NoSnoop+ FLReset-
    MaxPayload 256 bytes, MaxReadReq 512 bytes
    DevSta:    CorrErr- NonFatalErr- FatalErr- UnsupReq- AuxPwr- TransPend-
    LnkCap:    Port #0, Speed 16GT/s, Width x16, ASPM L0s L1, Exit Latency L0s <1us, L1 <4us
    ClockPM+ Surprise- LLActRep- BwNot- ASPMOptComp+
    LnkCtl:    ASPM Disabled; RCB 64 bytes, Disabled- CommClk-
    ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt-
    LnkSta:    Speed 16GT/s, Width x8 (downgraded)
    TrErr- Train- SlotClk+ DLActive- BWMgmt- ABWMgmt-
    DevCap2: Completion Timeout: Range AB, TimeoutDis+ NROPrPrP- LTR+
    10BitTagComp+ 10BitTagReq+ OBFF Via message, ExtFmt- EETLPPrefix-
    EmergencyPowerReduction Not Supported, EmergencyPowerReductionInit-
    FRS-
    AtomicOpsCap: 32bit- 64bit- 128bitCAS-
    DevCtl2: Completion Timeout: 50us to 50ms, TimeoutDis- LTR+ 10BitTagReq- OBFF Disabled,
    AtomicOpsCtl: ReqEn-
    LnkCap2: Supported Link Speeds: 2.5-16GT/s, Crosslink- Retimer+ 2Retimers+ DRS-
    LnkCtl2: Target Link Speed: 16GT/s, EnterCompliance- SpeedDis-
    Transmit Margin: Normal Operating Range, EnterModifiedCompliance- ComplianceSOS-
    Compliance Preset/De-emphasis: -6dB de-emphasis, 0dB preshoot
    LnkSta2: Current De-emphasis Level: -6dB, EqualizationComplete+ EqualizationPhase1+
    EqualizationPhase2+ EqualizationPhase3+ LinkEqualizationRequest-
    Retimer- 2Retimers- CrosslinkRes: unsupported
    Capabilities: [b4] Vendor Specific Information: Len=14 <?>
    Capabilities: [100 v1] Virtual Channel
    Caps:  LPEVC=0 RefClk=100ns PATEntryBits=1
    Arb:   Fixed- WRR32- WRR64- WRR128-
    Ctrl:  ArbSelect=Fixed
    Status:    InProgress-
    VC0:   Caps:   PATOffset=00 MaxTimeSlots=1 RejSnoopTrans-
    

3.2.2. 动态绑定设备6

该方法平时使用 nvidia 驱动管理设备,但是需要使用虚拟机时,先解绑 nvidia 驱动,再绑定到 vfio 上。

3.3. 在 guest 中添加虚拟显示器7, 8

  1. https://github.com/ge9/IddSampleDriver/releases/tag/0.0.1.2 下载文件。
  2. 将上面下载的文件解压到 c:\ 目录中。
  3. 编辑 C:\IddSampleDriver\option.txt ,该文件位置不能被修改。
    除了第一行的 1 不需要被修改,后面的分辨率可以删除不需要的分辨率并且添加自己需要的分辨率。
  4. 安装证书文件
    打开提权的 CMD 窗口,并定位到 C:\IddSampleDriver 目录。
    运行里面的 *.bat 文件,即可正常安装证书。
  5. 打开设备管理器。
    在操作中添加过时设备,定位 inf 文件后安装。
  6. 此时设备管理器中应该出现了一个虚拟显示器。

3.4. 虚拟机启动远程桌面服务

设置该远程桌面服务是为了避免 KVM 中禁用 video 时仍有办法访问虚拟机及查看内部状态。

在虚拟机内只需打开设置,定位到远程桌面服务并勾选确认即可。

然后在宿主机上通过

#!/usr/bin/env bash

start_rdp() {
    wlfreerdp /u:chin /v:192.168.122.89:3389 \
              /bpp:32 \
              /dynamic-resolution \
              +clipboard +fonts \
              /gdi:hw \
              /rfx /rfx-mode:video \
              +menu-anims +window-drag  \
              +auto-reconnect
}


start_rdp

来连接虚拟机。

3.5. 虚拟机安装 spice 相关软件

  • 安装 spice-guest-tools
    spice-guest-tools 中下载最新的 exe 文件并安装。
  • 安装 virtio 相关驱动
    见下文 键鼠配置 中的内容

3.6. 安装 looking-glass

3.6.1. 宿主机配置 looking-glass

我目前仍然在使用 Archlinux,直接在 aur 中安装 looking-glass 即可。

3.6.2. 虚拟机内安装 looking-glass

looking-glass 中下载适合 Windows 的安装文件,并进行安装。

3.6.3. 创建 looking-glass 用来共享画面的临时文件

执行下面的命令,就可以在不重启的情况下成功创建临时文件。

$ cat /etc/tmpfiles.d/looking-glass.conf
# Type Path               Mode UID  GID Age Argument
f /dev/shm/looking-glass 0660 chin kvm -
$ sudo systemd-tmpfiles /etc/tmpfiles.d/looking-glass.conf --create

3.6.4. 修改 libvirt 虚拟机配置

  • 共享内存

此内容用来绑定 looking-glass 的共享内存配置。

<shmem name='looking-glass'>
  <model type='ivshmem-plain'/>
  <size unit='M'>64</size>
</shmem>
  • 视频设置

需要保证配置内存在一个类似 <graphics type='spice'> 的配置。

此时,将 <video><model ...></video> 的内容改为如下配置,以设置一个 null 视频设备。

<video>
  <model type="vga"/>
</video>

虽然此时启用了 vga 模式,但是不要使用 virt-manager 提供的 viewer 来访问虚拟机,因为虚拟机会自动创建一个虚拟显示器, looking-glass 会因为这个设备无法连接。

  • 键鼠设置
    此时需要保证安装了 virtio 中的 vioinput 驱动。
    • 移除 <input type='tablet'/> 设备
    • 添加设备 <input type='mouse' bus='virtio'/>
    • 添加设备 <input type='keyboard' bus='virtio'/>
  • 关闭 memballoon
    将 memballoon 修改为如下内容 <memballoon model="none"/> ,因为 memballoon 事实上比较影响性能。

3.6.5. 连接虚拟机

启动虚拟机后使用

$ looking-glass-client -f /dev/shm/looking-glass

来连接到虚拟机。

3.7. TODO 为 looking-glass 启用 NvFBC 技术

NvFBC(NVIDIA Frame Buffer Capture)是一种基于 NVIDIA GPU 的高性能、低延迟的屏幕捕获技术。它可用于将计算机的屏幕实时捕获并传输给另一个显示设备或录制下来以供未来回放。

NvFBC 可以在硬件层面上对帧进行编码和压缩,从而在捕获期间减少 CPU 和内存的使用量。这意味着它可以提供高质量、低功耗的屏幕捕获体验,适用于游戏直播、视频录制和远程桌面等各种应用场景。

4. 其他微调9

4.1. CPU Pining

$ lscpu -e
CPU SOCKET CORE L1d:L1i:L2:L3 ONLINE    MAXMHZ   MINMHZ       MHZ
0      0    0 0:0:0:0          yes 4600.0000 400.0000  725.7240
1      0    0 0:0:0:0          yes 4600.0000 400.0000 3550.5720
2      0    1 4:4:1:0          yes 4600.0000 400.0000 2934.6440
3      0    1 4:4:1:0          yes 4600.0000 400.0000  554.1360
4      0    2 8:8:2:0          yes 4700.0000 400.0000  638.0720
5      0    2 8:8:2:0          yes 4700.0000 400.0000  619.2180
6      0    3 12:12:3:0        yes 4700.0000 400.0000 2700.0000
7      0    3 12:12:3:0        yes 4700.0000 400.0000  623.5170
8      0    4 16:16:4:0        yes 4600.0000 400.0000 1821.7490
9      0    4 16:16:4:0        yes 4600.0000 400.0000 2495.8931
10      0    5 20:20:5:0        yes 4600.0000 400.0000 2042.0060
11      0    5 20:20:5:0        yes 4600.0000 400.0000 1516.5330

可以知道物理核心和逻辑核心的对应关系。

xml 中类似的配置如下

<iothreads>1</iothreads>
<cputune>
  <vcpupin vcpu='0' cpuset='6'/>
  <vcpupin vcpu='1' cpuset='7'/>
  <vcpupin vcpu='2' cpuset='8'/>
  <vcpupin vcpu='3' cpuset='9'/>
  <vcpupin vcpu='4' cpuset='10'/>
  <vcpupin vcpu='5' cpuset='11'/>
  <emulatorpin cpuset='0,1'/>
  <iothreadpin iothread='1' cpuset='0,1'/>
</cputune>
<cpu mode='host-passthrough' check='none' migratable='on'>
  <topology sockets='1' dies='1' cores='3' threads='2'/>
</cpu>

iothreads 代表专用的事件循环线程,用于处理受支持的磁盘设备的块I/O请求,以提高可扩展性。 There should be only 1 or 2 IOThreads per host CPU. 更多的配置可以参考 10

4.2. Hyper-V enlightenments11 , 12

有时候使用软件来模拟一些硬件接口时会很慢,对于 Windows 来讲,他们实现了被称为 Hyper-V enlightenments 的技术来缓解这个问题。QEMU 的开发者们也实现了这一功能。简单来说该功能可以降低 CPU 使用并增加性能。

可以参考如下配置

<features>
  <acpi/>
  <apic/>
  <hyperv mode='custom'>
    <relaxed state='on'/>
    <vapic state='on'/>
    <spinlocks state='on' retries='8191'/>
    <vpindex state='on'/>
    <synic state='on'/>
    <stimer state='on'/>
    <reset state='on'/>
  </hyperv>
  <vmport state='off'/>
</features>
<clock offset='localtime'>
  <timer name='rtc' tickpolicy='catchup'/>
  <timer name='pit' tickpolicy='delay'/>
  <timer name='hpet' present='no'/>
  <timer name='hypervclock' present='yes'/>
</clock>

各项意义大概如下

  • relaxed
    用来关闭 watchdog。
  • vapic
    提高虚拟机对硬件资源的访问效率,让虚拟器在处理中断时更加高效。
  • spinlocks
    在虚拟机中加速自旋锁的获取,提高虚拟机的性能表现。
  • hypervclock
    降低 Windows 虚拟机在空闲时刻的负载。
  • hpet
    可以来降低虚拟机 IDLE 时的 cpu 消耗。

5. 疑难问题

5.1. 无法正常关机

通常情况下,安装完成 windows 10 虚拟机后,是无法通过直接通过 virsh 命令来关闭系统的,网上搜到部分文章说可以通过修改 windows 的组策略来实现,但是我没用实验成功,我准备使用 agent 方式来实现该功能。

  1. 在虚拟机中安装 qemu-guest-agent
    在这个页面中下载最新的镜像文件,打开该镜像找到 exe 文件进行安装。
  2. 修改 windows 10 配置文件13

    <channel type='unix'>
      <source mode='bind' />
      <target type='virtio' name='org.qemu.guest_agent.0'/>
    </channel>
    
  3. 之后使用如下命令来验证 windows 是否可以被正常关闭。

    $ sudo virsh shutdown --mode agent win10
    

5.2. 主机睡眠后受虚拟机影响,主机会 lockup 然后死掉

主要原因是 vfio 情况下,设备状态乱掉了,下面提供一个 workaround。

archlinux wiki14 提到添加一个 hook 使虚拟机启动时主机不睡眠,但是更理想的情况应该是 guest 同样进入睡眠状态。

下面 Roliga15 提出了一个我认为更能接受的方案

  1. 修改虚拟机配置
    首先开启睡眠功能,注意这个功能也需要安装 qemu-guest-agent,见“无法正常关机” 节。

    <pm>
      <suspend-to-mem enabled='yes'/>
      <suspend-to-disk enabled='yes'/>
    </pm>
    
  2. 在 windows 中开启睡眠功能
    除了 qemu,虚拟机同样需要支持睡眠功能
  3. 验证
    使用如下命令来休眠虚拟机。

    sudo virsh dompmsuspend [VM name] disk
    

    虽然引用中提到了 dompmsuspend 是一个 non-blocked 命令,但是我的实际测试结果中为阻塞的。
    然后使用 sudo virsh domstate win10 来查看当前虚拟机的状态,如果为 shut off ,认为休眠成功。
    再次检查 gpu 状态, cat /sys/bus/pci/devices/0000:01:00.0/power_state ,正常情况下应该为 D3cold

  4. 自动化操作
    制作睡眠脚本 /usr/local/bin/hibernate-vm
#!/bin/bash

#
# Usage: hibernate-vm NAME
#
# Hibernates the VM specified in NAME and waits for it to finish shutting down
#

if virsh dompmsuspend "$1" disk; then
    echo "Waiting for domain to finish shutting down.." >&2
    while ! [ "$(virsh domstate "$1")" == 'shut off' ]; do
        sleep 1
    done
    echo "Domain finished shutting down" >&2
fi

需要对该脚本赋予执行权限 chmod a+x /usr/local/bin/hibernate-vm

添加系统服务 /etc/systemd/system/hibernate-vm-sleep@.service

[Unit]
Description=Hibernate VM %I when host goes to sleep
Before=sleep.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/hibernate-vm %i

[Install]
WantedBy=sleep.target

配置完成后需要使用 systemctl daemon-reload 来重载配置 systemctl 和 systemctl enable --now hibernate-vm-shutdown@SOME_VM.service 来启动服务

6. 完整配置

<domain type='kvm'>
  <name>win10</name>
  <uuid>258d8e26-016d-4012-a52c-e7974a949130</uuid>
  <metadata>
    <libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
      <libosinfo:os id="http://microsoft.com/win/10"/>
    </libosinfo:libosinfo>
  </metadata>
  <memory unit='KiB'>16384000</memory>
  <currentMemory unit='KiB'>16384000</currentMemory>
  <vcpu placement='static'>6</vcpu>
  <iothreads>1</iothreads>
  <cputune>
    <vcpupin vcpu='0' cpuset='6'/>
    <vcpupin vcpu='1' cpuset='7'/>
    <vcpupin vcpu='2' cpuset='8'/>
    <vcpupin vcpu='3' cpuset='9'/>
    <vcpupin vcpu='4' cpuset='10'/>
    <vcpupin vcpu='5' cpuset='11'/>
    <emulatorpin cpuset='0-1'/>
    <iothreadpin iothread='1' cpuset='0-1'/>
  </cputune>
  <os firmware='efi'>
    <type arch='x86_64' machine='pc-q35-8.0'>hvm</type>
    <firmware>
      <feature enabled='no' name='enrolled-keys'/>
      <feature enabled='no' name='secure-boot'/>
    </firmware>
    <loader readonly='yes' type='pflash'>/usr/share/edk2/x64/OVMF_CODE.fd</loader>
    <nvram template='/usr/share/edk2/x64/OVMF_VARS.fd'>/var/lib/libvirt/qemu/nvram/win10_VARS.fd</nvram>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <hyperv mode='custom'>
      <relaxed state='on'/>
      <vapic state='on'/>
      <spinlocks state='on' retries='8191'/>
      <vpindex state='on'/>
      <synic state='on'/>
      <stimer state='on'/>
      <reset state='on'/>
    </hyperv>
    <vmport state='off'/>
  </features>
  <cpu mode='host-passthrough' check='none' migratable='on'>
    <topology sockets='1' dies='1' cores='3' threads='2'/>
  </cpu>
  <clock offset='localtime'>
    <timer name='rtc' tickpolicy='catchup'/>
    <timer name='pit' tickpolicy='delay'/>
    <timer name='hpet' present='no'/>
    <timer name='hypervclock' present='yes'/>
  </clock>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <pm>
    <suspend-to-mem enabled='yes'/>
    <suspend-to-disk enabled='yes'/>
  </pm>
  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='block' device='disk'>
      <driver name='qemu' type='raw' cache='none' io='native' discard='unmap'/>
      <source dev='/dev/disk/by-id/nvme-WDS500G3X0C-00SJG0_21032Y800617'/>
      <target dev='sda' bus='sata'/>
      <address type='drive' controller='0' bus='0' target='0' unit='0'/>
    </disk>
    <controller type='usb' index='0' model='qemu-xhci' ports='15'>
      <address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/>
    </controller>
    <controller type='pci' index='0' model='pcie-root'/>
    <controller type='pci' index='1' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='1' port='0x10'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0' multifunction='on'/>
    </controller>
    <controller type='pci' index='2' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='2' port='0x11'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x1'/>
    </controller>
    <controller type='pci' index='3' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='3' port='0x12'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x2'/>
    </controller>
    <controller type='pci' index='4' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='4' port='0x13'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x3'/>
    </controller>
    <controller type='pci' index='5' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='5' port='0x14'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x4'/>
    </controller>
    <controller type='pci' index='6' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='6' port='0x15'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x5'/>
    </controller>
    <controller type='pci' index='7' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='7' port='0x16'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x6'/>
    </controller>
    <controller type='pci' index='8' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='8' port='0x17'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x7'/>
    </controller>
    <controller type='pci' index='9' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='9' port='0x18'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0' multifunction='on'/>
    </controller>
    <controller type='pci' index='10' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='10' port='0x19'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x1'/>
    </controller>
    <controller type='pci' index='11' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='11' port='0x1a'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x2'/>
    </controller>
    <controller type='pci' index='12' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='12' port='0x1b'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x3'/>
    </controller>
    <controller type='pci' index='13' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='13' port='0x1c'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x4'/>
    </controller>
    <controller type='pci' index='14' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='14' port='0x1d'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x5'/>
    </controller>
    <controller type='pci' index='15' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='15' port='0x8'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0'/>
    </controller>
    <controller type='pci' index='16' model='pcie-to-pci-bridge'>
      <model name='pcie-pci-bridge'/>
      <address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
    </controller>
    <controller type='sata' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
    </controller>
    <controller type='virtio-serial' index='0'>
      <address type='pci' domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
    </controller>
    <interface type='network'>
      <mac address='52:54:00:e5:8e:11'/>
      <source network='default'/>
      <model type='e1000e'/>
      <address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
    </interface>
    <serial type='pty'>
      <target type='isa-serial' port='0'>
        <model name='isa-serial'/>
      </target>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <channel type='spicevmc'>
      <target type='virtio' name='com.redhat.spice.0'/>
      <address type='virtio-serial' controller='0' bus='0' port='1'/>
    </channel>
    <channel type='unix'>
      <target type='virtio' name='org.qemu.guest_agent.0'/>
      <address type='virtio-serial' controller='0' bus='0' port='2'/>
    </channel>
    <input type='mouse' bus='virtio'>
      <address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
    </input>
    <input type='keyboard' bus='virtio'>
      <address type='pci' domain='0x0000' bus='0x07' slot='0x00' function='0x0'/>
    </input>
    <input type='mouse' bus='ps2'/>
    <input type='keyboard' bus='ps2'/>
    <graphics type='spice' port='-1' autoport='no'>
      <listen type='address'/>
    </graphics>
    <sound model='ich9'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x1b' function='0x0'/>
    </sound>
    <audio id='1' type='spice'/>
    <video>
      <model type='vga' vram='16384' heads='1' primary='yes'/>
      <address type='pci' domain='0x0000' bus='0x10' slot='0x02' function='0x0'/>
    </video>
    <hostdev mode='subsystem' type='pci' managed='yes'>
      <source>
        <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
      </source>
      <address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x0'/>
    </hostdev>
    <redirdev bus='usb' type='spicevmc'>
      <address type='usb' bus='0' port='2'/>
    </redirdev>
    <redirdev bus='usb' type='spicevmc'>
      <address type='usb' bus='0' port='3'/>
    </redirdev>
    <watchdog model='itco' action='reset'/>
    <memballoon model='none'/>
    <shmem name='looking-glass'>
      <model type='ivshmem-plain'/>
      <size unit='M'>64</size>
      <address type='pci' domain='0x0000' bus='0x10' slot='0x01' function='0x0'/>
    </shmem>
  </devices>
</domain>

Footnotes:

天玄而地黄。