Open Virtual Machine Firmware(OMVF)

Open Virtual Machine Firmware(OMVF)是在虚拟机内部激活UEFI支持的开源项目,从Linux 3.9和最新QEMU开始,可以将图形卡直通给虚拟机,提供VM原生图形性能,对图形性能敏感对任务非常有用。

准备工作

显示直通(VGA passthrough)依赖很多比较少见的技术,并且有可能需要你的硬件支持才能实现。要完成直通,需要以下条件:

  • CPU支持硬件虚拟化(kvm)和IOMMU(用于passthrough自身,即把cpu完整透传给虚拟机)
  • 主板支持IOMMU
  • Guest虚拟机GPU ROM必须支持 UEFI
    • 可以从 techpowerup Video BIOS Collection 查询特定GPU是否支持UEFI。不过,从2012年开始所有GPU应该都支持UEFI,因为微软将UEFI作为兼容Windows 8的必要条件。

注解

你需要有其他通道来观察系统,例如其他显卡或者带外管理,因为passthrough GPU会导致该GPU在物理主机上不可使用。

设置IOMMU

注解

  • IOMMU是Intel VT-d 和 AMD-Vi 的通用名称
  • VT-d 是Intel Virtualization Technology for Directed I/O ,所以不要和 VT-x Intel Virtualization Technology混淆。 VT-x 允许一个硬件平台功能表现为多个虚拟化平台,而 VT-d 则是为了提高安全和系统可靠性并且提升虚拟化环境的I/O设备性能。

使用 IOMMU 可以开启新功能,例如 PCI passthrough 和 内存保护。请参考 Wikipedia:Input-output memory management unit#AdvantagesMemory Management (computer programming): Could you explain IOMMU in plain English?

激活IOMMU

AMD-Vi/Intel VT-d 是CPU内置支持,只需要通过BIOS设置激活。通常在BIOS中配置CPU功能部分可找到对应 Virtualization technology 之类配置。

在BIOS激活了 “VT-d” or “AMD-Vi” 之后,需要在内核参数中,根据CPU厂商不同配置内核参数:

  • Intel CPU( VT-d )配置 intel_iommu=on
  • AMD CPU( AMD-Vi )通常内核检测到BIOS支持 IOMMU HW 就会激活,为确定激活可配置 iommu=pt iommu=1

重启主机,然后检查 dmesg 确保IOMMU正确设置:

dmesg | grep -i -e DMAR -e IOMMU

输出类似:

ovmf/dmesg_dmar_iommu.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[    0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-5.4.0-90-generic root=UUID=caa4193b-9222-49fe-a4b3-89f1cb417e6a ro intel_iommu=on
[    0.009994] ACPI: DMAR 0x000000007B7E7000 000294 (v01 HP     ProLiant 00000001 HP   00000001)
[    0.010030] ACPI: Reserving DMAR table memory at [mem 0x7b7e7000-0x7b7e7293]
[    0.252650] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-5.4.0-90-generic root=UUID=caa4193b-9222-49fe-a4b3-89f1cb417e6a ro intel_iommu=on
[    0.252714] DMAR: IOMMU enabled
[    0.508460] DMAR: Host address width 46
[    0.508462] DMAR: DRHD base: 0x000000fbffc000 flags: 0x0
[    0.508469] DMAR: dmar0: reg_base_addr fbffc000 ver 1:0 cap d2078c106f0466 ecap f020df
[    0.508471] DMAR: DRHD base: 0x000000c7ffc000 flags: 0x1
[    0.508476] DMAR: dmar1: reg_base_addr c7ffc000 ver 1:0 cap d2078c106f0466 ecap f020df
[    0.508478] DMAR: RMRR base: 0x00000079174000 end: 0x00000079176fff
[    0.508481] DMAR: RMRR base: 0x000000791f4000 end: 0x000000791f7fff
[    0.508483] DMAR: RMRR base: 0x000000791de000 end: 0x000000791f3fff
[    0.508485] DMAR: RMRR base: 0x000000791cb000 end: 0x000000791dbfff
[    0.508486] DMAR: RMRR base: 0x000000791dc000 end: 0x000000791ddfff
[    0.508490] DMAR-IR: IOAPIC id 10 under DRHD base  0xfbffc000 IOMMU 0
[    0.508492] DMAR-IR: IOAPIC id 8 under DRHD base  0xc7ffc000 IOMMU 1
[    0.508494] DMAR-IR: IOAPIC id 9 under DRHD base  0xc7ffc000 IOMMU 1
[    0.508496] DMAR-IR: HPET id 0 under DRHD base 0xc7ffc000
[    0.508498] DMAR-IR: Queued invalidation will be enabled to support x2apic and Intr-remapping.
[    0.509028] DMAR-IR: Enabled IRQ remapping in x2apic mode
[    0.815054] iommu: Default domain type: Translated
[    1.068333] DMAR: No ATSR found
[    1.068769] DMAR: dmar0: Using Queued invalidation
[    1.068775] DMAR: dmar1: Using Queued invalidation
[    1.069672] pci 0000:00:00.0: Adding to iommu group 0
[    1.070272] pci 0000:00:01.0: Adding to iommu group 1
[    1.070667] pci 0000:00:01.1: Adding to iommu group 2
...
[    1.125619] pci 0000:ff:1e.3: Adding to iommu group 92
[    1.125691] pci 0000:ff:1e.4: Adding to iommu group 92
[    1.126288] pci 0000:ff:1f.0: Adding to iommu group 93
[    1.126363] pci 0000:ff:1f.2: Adding to iommu group 93
[    1.126369] DMAR: Intel(R) Virtualization Technology for Directed I/O

检查groups是否正确

以下脚本 check_iommu.sh 脚本可以查看系统中不同的PCI设备被映射到IOMMU组,如果没有返回任何信息,则表明系统没有激活IOMMU支持或者硬件不支持IOMMU

intel_vt-d_startup/check_iommu.sh
1
2
3
4
5
6
7
8
#!/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 32:
        00:1f.0 ISA bridge [0601]: Intel Corporation C610/X99 series chipset LPC Controller [8086:8d44] (rev 05)
        00:1f.2 SATA controller [0106]: Intel Corporation C610/X99 series chipset 6-Port SATA Controller [AHCI mode] [8086:8d02] (rev 05)
        00:1f.3 SMBus [0c05]: Intel Corporation C610/X99 series chipset SMBus Controller [8086:8d22] (rev 05)
IOMMU Group 33:
        01:00.0 System peripheral [0880]: Hewlett-Packard Company Integrated Lights-Out Standard Slave Instrumentation & System Support [103c:3306] (rev 06)
        01:00.1 VGA compatible controller [0300]: Matrox Electronics Systems Ltd. MGA G200EH [102b:0533] (rev 01)
        01:00.2 System peripheral [0880]: Hewlett-Packard Company Integrated Lights-Out Standard Management Processor Support and Messaging [103c:3307] (rev 06)
        01:00.4 USB controller [0c03]: Hewlett-Packard Company Integrated Lights-Out Standard Virtual USB Controller [103c:3300] (rev 03)
IOMMU Group 34:
        02:00.0 Ethernet controller [0200]: Broadcom Inc. and subsidiaries NetXtreme BCM5719 Gigabit Ethernet PCIe [14e4:1657] (rev 01)
        02:00.1 Ethernet controller [0200]: Broadcom Inc. and subsidiaries NetXtreme BCM5719 Gigabit Ethernet PCIe [14e4:1657] (rev 01)
        02:00.2 Ethernet controller [0200]: Broadcom Inc. and subsidiaries NetXtreme BCM5719 Gigabit Ethernet PCIe [14e4:1657] (rev 01)
        02:00.3 Ethernet controller [0200]: Broadcom Inc. and subsidiaries NetXtreme BCM5719 Gigabit Ethernet PCIe [14e4:1657] (rev 01)
IOMMU Group 35:
        04:00.0 Ethernet controller [0200]: Intel Corporation I350 Gigabit Network Connection [8086:1521] (rev 01)
IOMMU Group 36:
        04:00.1 Ethernet controller [0200]: Intel Corporation I350 Gigabit Network Connection [8086:1521] (rev 01)
IOMMU Group 37:
        04:00.2 Ethernet controller [0200]: Intel Corporation I350 Gigabit Network Connection [8086:1521] (rev 01)
IOMMU Group 38:
        04:00.3 Ethernet controller [0200]: Intel Corporation I350 Gigabit Network Connection [8086:1521] (rev 01)
IOMMU Group 39:
        05:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
IOMMU Group 40:
        08:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
IOMMU Group 41:
        0b:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
...
IOMMU Group 79:
        82:00.0 3D controller [0302]: NVIDIA Corporation Device [10de:1b39] (rev a1)

上述输出中有不同的 IOMMU Group ,这个意义是:

  • IOMMU Group 是直通给虚拟机的最小物理设备集合。举例,上述 IOMMU Group 32 这组设备只能完整分配给一个虚拟机,这个虚拟机将同时获得 ISA bridge [0601] SATA controller [0106]SMBus [0c05] ;同理, IOMMU Group 34 包含了4个Broadcom 以太网接口(实际上就是主板集成4网口)也只能同时分配给一个虚拟机;而独立添加的Intel 4网口千兆网卡则是完全独立的4个 IOMMU Group ,意味着可以独立分配个4个虚拟机
  • 我在 HPE ProLiant DL360 Gen9服务器 上通过 PCIe bifurcation 将PCIe 3.0 Slot 1(x16)划分成2个独立的x8通道,安装了2块 三星PM9A1 NVMe存储 ,加上在 Slot 2(x8) 上安装的第3块 三星PM9A1 NVMe存储 ,所以服务器上一共有3块 NVMe 存储。在这里可以看到有3个 IOMMU Group 分别是 39-40 对应了3个NVMe控制器,可以分别分配给3个虚拟机
  • IOMMU Group 79 是安装在PCIe 3.0 Slot 3(x16)上的 Nvidia Tesla P10 GPU运算卡 ,可以直接透传给一个虚拟机。我仅做技术实践,最终我希望采用 NVIDIA Virtual GPU (vGPU) 划分为更多GPU设备分配给虚拟化集群,来模拟大规模云计算

隔离GPU

要实现将一个设备指定给虚拟机,这个设备以及所有在相同IOMMU组的设备必须将它们的驱动替换成 stub 驱动 或者 VFIO 驱动,这样才能避免物理主机访问设备。并且需要注意,大多数设备需要在VM启动前完成这个替换。

但是,GPU驱动往往不倾向于支持动态绑定,所以可能难以实现一些用于物理主机的GPU被透明直通给虚拟机,因为这样的两种驱动相互冲突。通常建议手工绑定这些驱动,然后再启动虚拟机。

从Linux内核4.1开始,内核包含了 vfio-pci ,这个VFIO驱动可以完全替代 pci-stub 而且提供了控制设备的扩展,例如在不使用设备时将设备切换到 D3 状态。

通过设备ID来绑定 vfio-pci

vfio-pci 通常使用ID来找寻PCI设备,也就是只需要指定passthrough的设备ID。举例,前面显示的我的服务器上设备:

IOMMU Group 39:
        05:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
IOMMU Group 79:
        82:00.0 3D controller [0302]: NVIDIA Corporation Device [10de:1b39] (rev a1)

只需要使用 144d:a80a10de:1b39 这两个设备ID来绑定 vfio-pci

注解

要同时输出设备ID和 vfio-pci 绑定ID,可以使用 lspci -nn 命令,可以显示详细信息:

lspci -nn | grep -i samsung

就可以看到详细信息:

05:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
08:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
0b:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]

可以直接知道内核需要传递的 vfio-pci.ids 就是 144d:a80a ,同时也能够知道如何配置设备 .xml 文件用于 virsh attach-device

有两种方式将设备 remove 或者 break :

  • 使用启动内核参数:

    vfio-pci.ids=144d:a80a,10de:1b39
    
  • 另一种方式是使用 modprobe 配置,添加一个 /etc/modprobe.d/vfio.conf

    options vfio-pci ids=144d:a80a,10de:1b39
    

提前加载 vfio-pci

需要分辨 vfio-pci 是作为内核直接编译的还是作为内核模块加载的:

  • 如果是直接静态编译到内核,则只需要内核启动参数中传递参数阻止host主机使用PCI设备: 如 Red Hat Enterprise Linux 和 Ubuntu 都是直接内核静态编译 vfio
  • 如果是内核模块加载 vfio-pci 则还需要重建 initramfs 确保提前加载模块: 如 arch linux就是模块方式加载 vfio-pci 等模块

检查方法是查看 /boot 目录下对应内核版本 config-xxx-xxx 配置,例如 Ubuntu 20.04.3 LTS 当前使用内核 使用 uname -r 查看是 5.4.0-90-generic ,则检查 /boot/config-5.4.0-90-generic 文件,可以看到各个 vfio 相关模块都是静态编译(对应值都是 Y ):

CONFIG_VFIO_IOMMU_TYPE1=y
CONFIG_VFIO_VIRQFD=y
CONFIG_VFIO=y
CONFIG_VFIO_NOIOMMU=y
CONFIG_VFIO_PCI=y
CONFIG_VFIO_PCI_VGA=y
CONFIG_VFIO_PCI_MMAP=y
CONFIG_VFIO_PCI_INTX=y
CONFIG_VFIO_PCI_IGD=y

注解

参考 Red Hat Virtualization 文档,通过内核传递参数 pci-stub.ids=144d:a80a,10de:1b39 rdblacklist=nouveau 就可以阻止host主机使用GPU设备(并拒绝加载显卡驱动) ,然后只需要重新生成引导装载程序:

grub2-mkconfig -o /etc/grub2.cfg

然后重启系统就可以从 cat /proc/cmdline 确认主机设备被添加到 pci-stub.ids 列表中,Nouveau 已列入黑名单

对于内核模块方式加载 VFIO 相关模块(arch linux就是如此),则采用以下3个方法之一来定制 intiramfs 确保内核首先加载 VFIO 模块,这样才能屏蔽掉host主机使用需要分配给虚拟机的PCI设备:

注解

dracut 是一个制作 提前加载需要访问根文件系统的块设备模块(例如IDE,SCSI,RAID等) 初始化镜像 initramfs 工具。 mkinitcpio 是相同功能的工具。目前,大多数发行版,如Fedora, RHEL, Gentoo, Debian都使用 dracut ,不过 Arch Linux 默认使用 mkinitcpio

由于我的实践是在Ubuntu上完成, VFIO 相关支持都是直接静态编译进内核,所以并不需要执行以下内核模块加入 initiramfs 的需求,我并没有执行以下3个方法的任意一个。不过,对于arch linux需要执行。

mkinitcpio(方法一)

由于操作系统使用 vfio-pci 内核模块,需要确保 vfio-pci 模块提前加载,这样才能避免图形驱动绑定到这个GPU卡上。要实现这点,需要使用 mkinitcpio 帮助把 vfio_cpi 等相关内核模块加入到 initramfs

  • 编辑 /etc/mkinitcpio.conf 配置文件:

    MODULES=(... vfio_pci vfio vfio_iommu_type1 vfio_virqfd ...)
    

并且确认 mkinitcpio.conf 已经包含了 modconf hook:

HOOKS=(... modconf ...)
  • 然后执行:

    mkinitcpio -P
    

booster(方法二)

  • 编辑 /etc/booster.yaml

    modules_force_load: vfio_pci,vfio,vfio_iommu_type1,vfio_virqfd
    
  • 重新生成initramfs:

    /usr/lib/booster/regenerate_images
    

dracut(方法三)

dracut的早期加载机制是通过内核参数。

  • 要提前加载 vfio-pci ,在内核参数上添加设备ID以及 rd.driver.pre 配置:

    vfio-pci.ids=144d:a80a,10de:1b39 rd.driver.pre=vfio_pci
    
  • 同样也需要将所有vfio驱动加入到initramfs,所以在 /etc/dracut.conf.d 目录下添加配置文件 10-vfio.conf

    add_drivers+=" vfio_pci vfio vfio_iommu_type1 vfio_virqfd "
    
  • 为当前运行内核生成initramfs:

    dracut --hostonly --no-hostonly-cmdline /boot/initramfs-linux.img
    

我的屏蔽host pcie实践

这段是我实际操作实践:

  • 实践是在 Ubuntu Linux 20.04.3 LTS上完成,配置 Ubuntu修订Grub内核启动参数 - 修改 /etc/default/grub

    GRUB_CMDLINE_LINUX_DEFAULT="intel_iommu=on pci-stub.ids=144d:a80a,10de:1b39 rdblacklist=nouveau"
    
  • 然后执行:

    sudo update-grub
    
  • 重启操作系统,然后执行以下命令检查:

    cat /proc/cmdline
    

验证是否实现提前加载 vfio-pci

  • 重启系统

  • 检查 vfio-pci 是否正确加载,并且绑定到正确设备:

    dmesg | grep -i vfio
    

我的实践看到输出内容是:

[    1.434041 ] VFIO - User Level meta-driver version: 0.3

并没有看到设备

  • 在host主机上检查设备:

    lspci -nnk -d 144d:a80a
    

很不幸,物理主机依然能够访问:

05:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
     Subsystem: Samsung Electronics Co Ltd Device [144d:a801]
     Kernel driver in use: nvme
     Kernel modules: nvme
08:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
     Subsystem: Samsung Electronics Co Ltd Device [144d:a801]
     Kernel driver in use: nvme
     Kernel modules: nvme
0b:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
     Subsystem: Samsung Electronics Co Ltd Device [144d:a801]
     Kernel driver in use: nvme
     Kernel modules: nvme

检查GPU设备也是如此:

lspci -nnk -d 10de:1b39

输出:

82:00.0 3D controller [0302]: NVIDIA Corporation Device [10de:1b39] (rev a1)
     Subsystem: NVIDIA Corporation Device [10de:1217]
     Kernel driver in use: nouveau
     Kernel modules: nvidiafb, nouveau
  • 我发现错误了,内核配置应该是 vfio-pci.ids=144d:a80a,10de:1b39 ,而不是以前旧格式配置 pci-stub.ids=144d:a80a,10de:1b39 ,所以重新修订 /etc/default/grub

    GRUB_CMDLINE_LINUX_DEFAULT="intel_iommu=on vfio-pci.ids=144d:a80a,10de:1b39"
    

注解

后续我实践 配置SR-IOV网络虚拟化 ,还增加一个 iommu=pt 参数,以提高 SR-IOV pass-through 性能。

警告

如果要在物理主机上 安装NVIDIA Linux驱动 ,则必须重新修订上述 vfio-pci.ids 参数,去除掉 10de:1b39 ,也就是修改为:

GRUB_CMDLINE_LINUX_DEFAULT="intel_iommu=on vfio-pci.ids=144d:a80a"

否则会出现非常奇怪的报错:

NVRM: The NVIDIA probe routine was not called for 1 device(s).
...
NVRM: No NVIDIA devices probed.

然后重新生成grub:

sudo update-grub

然后再次重启系统,并检查 dmesg | grep -i vfio 输出,这次正常了,可以看到 vfio_pci 正确绑定了指定PCIe设备:

[    0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-5.4.0-90-generic root=UUID=caa4193b-9222-49fe-a4b3-89f1cb417e6a ro intel_iommu=on vfio-pci.ids=144d:a80a,10de:1b39
[    0.252622] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-5.4.0-90-generic root=UUID=caa4193b-9222-49fe-a4b3-89f1cb417e6a ro intel_iommu=on vfio-pci.ids=144d:a80a,10de:1b39
[    1.457708] VFIO - User Level meta-driver version: 0.3
[    1.516284] vfio_pci: add [144d:a80a[ffffffff:ffffffff]] class 0x000000/00000000
[    1.536263] vfio_pci: add [10de:1b39[ffffffff:ffffffff]] class 0x000000/00000000
  • 此时根据设备ID检查PCIe设备,可以看到已经被 vfio-pci 驱动:

    lspci -nnk -d 144d:a80a
    

显示:

05:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
     Subsystem: Samsung Electronics Co Ltd Device [144d:a801]
     Kernel driver in use: vfio-pci
     Kernel modules: nvme
08:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
     Subsystem: Samsung Electronics Co Ltd Device [144d:a801]
     Kernel driver in use: vfio-pci
     Kernel modules: nvme
0b:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
     Subsystem: Samsung Electronics Co Ltd Device [144d:a801]
     Kernel driver in use: vfio-pci
     Kernel modules: nvme

注解

请注意, vfio-pci.ids=144d:a80a 是对同一种类设备进行屏蔽,所有在这个服务器上安装的 三星PM9A1 NVMe存储 都是相同的设备ID,也就是会同时被屏蔽掉。目前看,不能单独只屏蔽一块NVMe存储,因为都是相同的三星NVMe,会一起被 vfio-pci 接管。可以看到上面 lspci -nnk -d 144d:a80a 显示3块三星NVMe。

检查GPU:

lspci -nnk -d 10de:1b39

显示:

82:00.0 3D controller [0302]: NVIDIA Corporation Device [10de:1b39] (rev a1)
     Subsystem: NVIDIA Corporation Device [10de:1217]
     Kernel driver in use: vfio-pci
     Kernel modules: nvidiafb, nouveau

设置OVMF guest VM

OVMF是开源UEFI firmware,用于QEMU虚拟机。

配置libvirt

Libvirt虚拟机管理器 是一系列虚拟化工具包装,可以大幅简化配置和部署过程。在KVM和QEMU案例中,使用libvirt可以避免我们处理QEMU权限并容易添加和移除虚拟机设备。不过,由于libvirt是包装器,所以也有部分qemu功能不能支持,需要使用定制脚本来提供QEMU的扩展参数。

注解

arch linux 文档 arch linux: PCI passthrough via OVMF 只介绍了使用图形化工具 virt-manager 设置 UEFI 虚拟机。不过,可以从 Red Hat Enterprise Linux 虚拟化文档以及SUSE虚拟化文档提供了命令行方式操作。我的实践以命令行为主。

virt-install

  • virt-install 命令添加 --boot uefi--cpu host-passthrough 参数安装操作系统。注意,虚拟机系统磁盘采用 libvirt LVM卷管理存储池 ,所以首先创建LVM卷:

    virsh vol-create-as images_lvm z-fedora35 6G
    
  • 创建虚拟机:

    virt-install \
      --network bridge:virbr0 \
      --name z-fedora35 \
      --ram=2048 \
      --vcpus=1 \
      --os-type=Linux --os-variant=fedora31 \
      --boot uefi --cpu host-passthrough \
      --disk path=/dev/vg-libvirt/fedora35,sparse=false,format=raw,bus=virtio,cache=none,io=native \
      --graphics none \
      --location=http://mirrors.163.com/fedora/releases/35/Server/x86_64/os/ \
      --extra-args="console=tty0 console=ttyS0,115200"
    

Fedora Workstation版本只能从iso安装

安装注意点:

  • 安装过程启用VNC使用图形界面安装,这样可以选择文件系统分区等高级配置,方便安装

  • vda 需要分配一个独立UEFI分区挂载为 /boot/efi ,这个分区不需指定文件系统类型,系统会自动选择 vfat 类型,我分配了 256MB;其余磁盘全部分配给 / ,设置为 xfs 文件系统:

    Filesystem      Size  Used Avail Use% Mounted on
    devtmpfs        964M     0  964M   0% /dev
    tmpfs           983M     0  983M   0% /dev/shm
    tmpfs           393M  952K  392M   1% /run
    /dev/vda2       5.8G  1.8G  4.0G  31% /
    tmpfs           983M  4.0K  983M   1% /tmp
    /dev/vda1       256M  6.1M  250M   3% /boot/efi
    tmpfs           197M     0  197M   0% /run/user/0
    
  • 安装完成后,重启系统,继续可以通过控制台维护,首次启动后就可以通过 dnf upgrade 更新系统

  • 按照 私有云架构 规划配置主机IP,然后clone出测试服务器 ( libvirt LVM卷管理存储池 ):

    virt-clone --original z-fedora35 --name z-iommu --auto-clone
    
  • clone之后,对容器内部进行配置修订: Fedora Server使用 NetworkManager 管理网络,所以通过以下命令修订静态IP地址和主机名:

    nmcli general hostname z-iommu
    nmcli connection modify "enp1s0" ipv4.method manual ipv4.address 192.168.6.242/24 ipv4.gateway 192.168.6.200 ipv4.dns "192.168.6.200,192.168.6.11"
    

虚拟机添加设备

设备xml配置文件

我们前面已经通过 check_iommu.sh 脚本找出了需要指派给虚拟机的PCIe设备:

IOMMU Group 39:
        05:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a80a]
IOMMU Group 79:
        82:00.0 3D controller [0302]: NVIDIA Corporation Device [10de:1b39] (rev a1)

并通过内核启动参数 vfio-pci.ids=144d:a80a,10de:1b39 屏蔽了HOST物理主机使用这两个设备,现在我们可以将这两个设备attach到虚拟机。

注解

我被Red Hat的文档绕糊涂了,实际上只需要执行 virsh nodedev-list --cap pci 没有必要执行 virsh nodedev-dumpxml :

  • lspci | grep -i Samsunglspci | grep -i nvidia 实际上已经获得了16进制设备的完整id了:

    05:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd Device a80a
    08:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd Device a80a
    0b:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd Device a80a
    
    82:00.0 3D controller: NVIDIA Corporation Device 1b39 (rev a1)
    

第一列就是设备十六进制的ID,可以直接用来配置设备的 .xml 文件,3个数字字段分别对应了 bus= slot= function= 举例第三个NVMe设备 0b:00.0

<hostdev mode='subsystem' type='pci' managed='yes'>
  <source>
     <address domain='0x0' bus='0xb' slot='0x0' function='0x0'/>
  </source>
</hostdev>

后面我写的这段实践可以省略

  • 使用 lspci 命令检查:

    # lspci | grep -i Samsung
    05:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd Device a80a
    # lspci | grep -i nvidia
    82:00.0 3D controller: NVIDIA Corporation Device 1b39 (rev a1)
    
  • 使用 virsh nodedev-list 命令检查 pci 类型设备:

    virsh nodedev-list --cap pci
    

此时设备显示会将 lspci 输出的分隔符号 :. 替换成 _ ,所以列出设备是:

pci_0000_05_00_0
pci_0000_82_00_0
  • 再次检查设备信息

NVMe设备:

virsh nodedev-dumpxml pci_0000_05_00_0

输出显示:

Samsung PM9A1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<device>
  <name>pci_0000_05_00_0</name>
  <path>/sys/devices/pci0000:00/0000:00:02.0/0000:05:00.0</path>
  <parent>pci_0000_00_02_0</parent>
  <driver>
    <name>vfio-pci</name>
  </driver>
  <capability type='pci'>
    <class>0x010802</class>
    <domain>0</domain>
    <bus>5</bus>
    <slot>0</slot>
    <function>0</function>
    <product id='0xa80a'/>
    <vendor id='0x144d'>Samsung Electronics Co Ltd</vendor>
    <iommuGroup number='39'>
      <address domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
    </iommuGroup>
    <numa node='0'/>
    <pci-express>
      <link validity='cap' port='0' speed='16' width='4'/>
      <link validity='sta' speed='8' width='4'/>
    </pci-express>
  </capability>
</device>

GPU设备:

virsh nodedev-dumpxml pci_0000_82_00_0

输出显示:

NVIDIA Tesla P10
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<device>
  <name>pci_0000_82_00_0</name>
  <path>/sys/devices/pci0000:80/0000:80:02.0/0000:82:00.0</path>
  <parent>pci_0000_80_02_0</parent>
  <driver>
    <name>vfio-pci</name>
  </driver>
  <capability type='pci'>
    <class>0x030200</class>
    <domain>0</domain>
    <bus>130</bus>
    <slot>0</slot>
    <function>0</function>
    <product id='0x1b39'/>
    <vendor id='0x10de'>NVIDIA Corporation</vendor>
    <iommuGroup number='79'>
      <address domain='0x0000' bus='0x82' slot='0x00' function='0x0'/>
    </iommuGroup>
    <numa node='1'/>
    <pci-express>
      <link validity='cap' port='0' speed='8' width='16'/>
      <link validity='sta' speed='8' width='16'/>
    </pci-express>
  </capability>
</device>
  • 转换设备配置参数:

在上述 nodedev-dumpxml 中高亮的3行参数配置是10进制,需要转换成16进制配置到设备添加xml配置中

NVMe设备:

<bus>5</bus>
<slot>0</slot>
<function>0</function>

执行以下命令得到对应16进制值:

$ printf %x 5
5
$ printf %x 0
0
$ printf %x 0
0

则对应配置 samsung_pm9a1_1.xml :

Samsung PM9A1 #1
1
2
3
4
5
<hostdev mode='subsystem' type='pci' managed='yes'>
  <source>
     <address domain='0x0' bus='0x5' slot='0x0' function='0x0'/>
  </source>
</hostdev>

GPU设备:

<bus>130</bus>
<slot>0</slot>
<function>0</function>

执行以下命令得到对应16进制值:

$ printf %x 130
82
$ printf %x 0
0
$ printf %x 0
0

则对应配置 tesla_p10.xml :

NVIDIA Tesla P10
1
2
3
4
5
<hostdev mode='subsystem' type='pci' managed='yes'>
  <source>
     <address domain='0x0' bus='0x82' slot='0x0' function='0x0'/>
  </source>
</hostdev>

添加NVMe设备

  • 执行以下命令将第一个NVMe Samsung PM9A1 添加到虚拟机 z-iommu 上:

    virsh attach-device z-iommu samsung_pm9a1_1.xml
    

此时在物理主机上提示信息:

Device attached successfully

在虚拟机 z-iommu 终端控制台可以看到信息:

Samsung PM9A1 #1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[ 6621.990244] pcieport 0000:00:01.6: pciehp: Slot(0-6) Powering on due to button press
[ 6621.993421] pcieport 0000:00:01.6: pciehp: Slot(0-6): Card present
[ 6621.995719] pcieport 0000:00:01.6: pciehp: Slot(0-6): Link Up
[ 6622.124045] pci 0000:07:00.0: [144d:a80a] type 00 class 0x010802
[ 6622.126753] pci 0000:07:00.0: reg 0x10: [mem 0x00000000-0x00003fff 64bit]
[ 6622.130056] pci 0000:07:00.0: Max Payload Size set to 128 (was 256, max 256)
[ 6622.134642] pci 0000:07:00.0: 31.504 Gb/s available PCIe bandwidth, limited by 8.0 GT/s PCIe x4 link at 0000:00:01.6 (capable of 63.012 Gb/s with 16.0 GT/s PCIe x4 link)
[ 6622.138779] pci 0000:07:00.0: BAR 0: assigned [mem 0xc0000000-0xc0003fff 64bit]
[ 6622.140297] pcieport 0000:00:01.6: PCI bridge to [bus 07]
[ 6622.141349] pcieport 0000:00:01.6:   bridge window [io  0x6000-0x6fff]
[ 6622.145734] pcieport 0000:00:01.6:   bridge window [mem 0xc0000000-0xc01fffff]
[ 6622.148817] pcieport 0000:00:01.6:   bridge window [mem 0x800700000-0x8008fffff 64bit pref]
[ 6622.195537] nvme nvme0: pci function 0000:07:00.0
[ 6622.196524] nvme 0000:07:00.0: enabling device (0140 -> 0142)
[ 6622.233264] nvme nvme0: Shutdown timeout set to 10 seconds
[ 6622.256580] nvme nvme0: 1/0/0 default/read/poll queues

此时在虚拟机 z-iommmu 中检查磁盘:

fdisk -l

可以看到:

Disk /dev/nvme0n1: 953.87 GiB, 1024209543168 bytes, 2000409264 sectors
Disk model: SAMSUNG MZVL21T0HCLR-00B00
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

注解

这种live方式添加nvme磁盘所在的pci设备可以立即使用存储,无需重启虚拟机。但是虚拟机重启后,该磁盘未自动添加。所以我后来还是采用 --config 参数运行 virsh attach-device ,然后重启虚拟机。重启以后可以保持使用NVMe设备:

virsh attach-device z-iommu samsung_pm9a1_1.xml --config
  • 删除设备是反向操作:

    virsh detach-device z-iommu samsung_pm9a1_1.xml --config
    

添加GPU设备

  • 执行以下命令将NVIDIA Tesla P10 GPU运算卡 添加到虚拟机 z-iommu 上:

    virsh attach-device z-iommu tesla_p10.xml
    

这里出现一个报错:

error: Failed to attach device from tesla_p10.xml
error: internal error: No more available PCI slots

这个问题在 libvirtd: No more available PCI slots 提到了解决方法: 添加一个 --config 参数,让libvirt来自动添加需要的 pcie-root-port 配置。然后就需要shutdown虚拟机,并再次启动虚拟机。这个设备就会正确添加。

所以改为执行:

virsh attach-device z-iommu tesla_p10.xml --config

virsh destory z-iommu
virsh start z-iommu
  • 重启完虚拟机,登录虚拟机中执行:

    lspci
    

可以看到GPU设备:

07:00.0 3D controller: NVIDIA Corporation GP102GL [Tesla P10] (rev a1)

但是没有找到前面live方式添加的NVMe设备,所以使用 --config 参数再重新添加一次NVMe设备:

virsh attach-device z-iommu samsung_pm9a1_1.xml --config

重启以后再次检查可以看到添加的2个pci设备:

07:00.0 3D controller: NVIDIA Corporation GP102GL [Tesla P10] (rev a1)
08:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd NVMe SSD Controller PM9A1/PM9A3/980PRO

Windows OVMF虚拟机安装

注解

本段我尚未实践,目前暂未有 Windows Atlas 实践机会,不过考虑到Windows的广泛使用,我在后续实践中会采用混合部署Linux+Windows,学习Windows体系。目前这段仅记录供后续参考 Virtualizing Windows 7 (or Linux) on a NVMe drive with VFIO

在YouTube上,有很多技术爱好者展示了通过Windows虚拟机玩游戏的,主要的思路就是通过VFIO实现显卡和存储pass-through,既是炫技也是玩乐。我的思路是后续在 私有云架构 融入部署完整的Windows Active Directory Domain,构建一个企业级网络。

  • Intel的UEFI实现称为 tianocore ,在qeumu支持 tianocore 的发行包就是 ovmf ,所以同样我们需要在物理主机上安装 ovmf

    apt install ovmf
    
  • 构建驱动ISO

Windows安装国晨够可以选择磁盘驱动,由于我们是在虚拟机中安装Windows,所以只需要安装过程中在VM添加包含驱动的ISO文件挂载就可以。Fedora提供了 virto 驱动镜像 ,下载后将内容复制出来:

# mkdir /mnt/loop
# mount /path/to/virtio.iso -o loop /mnt/loop
$ cp -r /mnt/loop/* /home/user/virtio
  • 三星NVMe驱动:

三星官方提供了NVMe驱动(可执行文件,待后续实践,应该是压缩包可以用来提取实际驱动文件),后续实践可以尝试将驱动文件提取出来:

secnvme.cat
secnvme.inf
secnvme.sys
secnvmeF.sys

复制到 /home/user/virtio/samsung

  • 在复制了需要的驱动文件到 virtio/ 目录之后,可以将这个目录重建成iso文件:

    geniso -o virtio.iso virtio/
    
  • 然后参考 创建KVM虚拟机 创建虚拟机,注意需要启用 uefi

其他内容待后续实践

下一步

现在上述两个PCIe设备已经属于虚拟机独占使用,和常规的设备使用一致,接下来就是:

参考