概要
KVM 上の Windows 10 に PCI デバイスを直接割り当て、仮想マシンでは性能が貧弱になるグラフィックの改善と、KVM を介すと扱えない USB デバイス(iTunes から iPhoneの認識等)を仮想マシンから直接扱えるようにします。
モチベーション
こんな面倒なことをしなくても普通に Windows をインストールすればそもそも仮想化のオーバーヘッドが無くて良いのでは?というのはもっともなのですが、私の場合は下記が大きなモチベーションです。
- 個人的な開発や検証用に仮想化環境が必要なのですが、VMware Workstation, Hyper-V は Windows 上で稼働する為かかなり重くなります。KVM は複数の仮想マシンを稼働させても軽く感じます。
- 性能を無視すれば PCI パススルーを考える必要もないのですが、やはり仮想化でもベアメタルに近い状態で Windows は使いたいですね。
- 最近の仕事で、SR-IOV や CPU ピンニング等、仮想マシンやコンテナの性能向上を考えることが多くなりました。仕事で使うハードウェアを自宅には用意できませんが、多少なりそれらの学習環境に利用できます。
参考資料
基本情報
ハードウェア
ハードウェア構成は下記の通りです。以降の手順でグラフィックカード (DUAL-GTX1650-O4G) と USB3.0 カード (OWL-PCEXU3E4) を仮想マシンに割り当てます。
パーツ | メーカー | 型番 | 備考 |
---|---|---|---|
PC ケース | Cooler Master | MCS-S600-KN5N-S00 | ミドルタワーケース |
電源ユニット | Corsair | CP-9020195-JP | 80PLUS GOLD |
マザーボード | ASUS | PRIME B365-PLUS | ATX |
CPU | INTEL | i5-9600 | 6 cores, 6 threads |
CPU クーラー | サイズ | 虎徹 Mark II | |
メモリ | Crucial | W4U2666CM-8G | 8 GB x 4 |
M.2 SSD | シリコンパワー | SP512GBP34A60M28 | 512 GB |
SSD | Crucial | CT1000MX500SSD1 | 1 TB |
グラフィックカード | ASUS | DUAL-GTX1650-O4G | PCI パススルー対象 |
USB3.0 カード | オウルテック | OWL-PCEXU3E4 | PCI パススルー対象 |
ネットワークカード | TP-Link | TG-3468 |
ハードウェアの周辺環境
ディスプレイは 1 つで、PC のオンボードの HDMI、ASUSの HDMI をそれぞれ接続しており、ディスプレイの切り替えスイッチでホストの Ubuntu と仮想マシンの Windows を切り替えて使用します。その際にマウスとキーボードのレシーバーを物理的にそれぞれのUSBに差し替えます。
BIOS
Intel VT, Intel VT-d を有効にします。これが有効にできない場合は PCI パススルーを使用できません。
ソフトウェア
OS
Ubuntu Desktop 22.04.1 LTS を使用しました。
KVM
KVM の実行に必要なパッケージをインストールします。実際には環境に合わせたパッケージをいくつか追加でインストールしていますが、ここでは最小限のパッケージのみを記載しています。
myadmin@ubuntu:~$ sudo DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold \
install qemu-kvm libvirt-daemon-system libosinfo-bin virtinst virt-manager
ネットワーク設定
Ubuntu Desktop のデフォルトの netplan は下記となっており、network-manger が netplan の制御を行っています。
myadmin@ubuntu:~$ cat /etc/netplan/01-network-manager-all.yaml
# Let NetworkManager manage all devices on this system
network:
version: 2
renderer: NetworkManager
本稿では Ubuntu Server に合わせて network-manger の制御を無効にします。上記のファイルを削除して再起動すると全ての NIC が network-manager の制御から外すことができます。renderer: networkd としても良いようですが、この場合、ブリッジを再起動すると仮想マシンの通信が切れる問題があった為、renderer の設定は削除しています。これは Ubuntu Server と同じ状態です。
myadmin@ubuntu:~$ sudo rm /etc/netplan/01-network-manager-all.yaml myadmin@ubuntu:~$ sudo reboot
再起動後、nmcli で unmanaged を確認できます。
myadmin@ubuntu:~$ LANG=C nmcli device
DEVICE TYPE STATE CONNECTION
enp6s0 ethernet unmanaged --
enp7s0 ethernet unmanaged --
lo loopback unmanaged --
今回のネットワーク設定は下記としました。2 つの VLAN をそれぞれブリッジにしています。
myadmin@ubuntu:~$ sudo tee /etc/netplan/00-main.yaml <<"EOF" network: version: 2 ethernets: enp7s0: {} bridges: br3000: addresses: [10.0.0.37/24] nameservers: addresses: [172.16.64.71] interfaces: [vlan3000] routes: - {to: default, via: 10.0.0.1} br3544: interfaces: [vlan3544] vlans: vlan3000: id: 3000 link: enp7s0 vlans: vlan3544: id: 3544 link: enp7s0 EOF myadmin@ubuntu:~$ sudo netplan apply
IOMMU の有効化
OS で IOMMU を有効にします。この手順では、まず IOMMU を有効化が可能であるかを確認しており、最小限の手順としては「PCI デバイスの分離」から開始しても良いです。
/etc/default/grub
GRUB の設定ファイルを更新します。デフォルト値は下記の通りです。
myadmin@ubuntu:~$ grep -v -e '^\s*#' -e '^\s*$' /etc/default/grub | expand | tr -s [:space:] | sed 's/^\s/ /g'
GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""
下記の通り編集します。AMD の場合は別の設定値となります。インターネットの情報では、GRUB_CMDLINE_LINUX_DEFAULT に設定しているケースもありますが、どちらでも有効化に問題はなく、GRUB_CMDLINE_LINUX は標準モードのみで有効とのことですので、GRUB_CMDLINE_LINUX に設定しました。
https://documentation.suse.com/ja-jp/sles/12-SP4/html/SLES-all/cha-grub2.html
myadmin@ubuntu:~$ sudo mv /etc/default/grub /etc/default/grub.orig myadmin@ubuntu:~$ sudo tee /etc/default/grub <<"EOF" GRUB_DEFAULT=0 GRUB_TIMEOUT_STYLE=hidden GRUB_TIMEOUT=0 GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian` GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt" EOF
GRUB のアップデート
GRUB をアップデートして再起動します。
myadmin@ubuntu:~$ sudo update-grub myadmin@ubuntu:~$ sudo reboot
IOMMU 有効化の確認
再起動後、IOMMU が有効であることを確認します。この時点でうまく有効にならない場合、BIOS の設定値、そもそもハードウェアが IOMMU に対応しているかを確認する必要があります。
myadmin@ubuntu:~$ sudo dmesg | grep IOMMU [ 0.061643] DMAR: IOMMU enabled [ 0.157075] DMAR-IR: IOAPIC id 2 under DRHD base 0xfed91000 IOMMU 1 [ 0.421049] DMAR: IOMMU feature fl1gp_support inconsistent [ 0.421050] DMAR: IOMMU feature pgsel_inv inconsistent [ 0.421051] DMAR: IOMMU feature nwfs inconsistent [ 0.421052] DMAR: IOMMU feature pasid inconsistent [ 0.421053] DMAR: IOMMU feature eafs inconsistent [ 0.421053] DMAR: IOMMU feature prs inconsistent [ 0.421054] DMAR: IOMMU feature nest inconsistent [ 0.421055] DMAR: IOMMU feature mts inconsistent [ 0.421055] DMAR: IOMMU feature sc_support inconsistent [ 0.421056] DMAR: IOMMU feature dev_iotlb_support inconsistent
PCI デバイスの分離
ホストから仮想マシンに割り当てる PCI デバイスを切り離します。分離をしないと PCI デバイスを仮想マシンに割り当てできません。
PCI デバイスの確認
lspci に -nn オプションを付与すると、ベンダー ID とデバイス ID を得られます。
myadmin@ubuntu:~$ lspci -nn 00:00.0 Host bridge [0600]: Intel Corporation 8th Gen Core Processor Host Bridge/DRAM Registers [8086:3ec2] (rev 07) 00:01.0 PCI bridge [0604]: Intel Corporation 6th-10th Gen Core Processor PCIe Controller (x16) [8086:1901] (rev 07) 00:02.0 VGA compatible controller [0300]: Intel Corporation CoffeeLake-S GT2 [UHD Graphics 630] [8086:3e92] 00:14.0 USB controller [0c03]: Intel Corporation 200 Series/Z370 Chipset Family USB 3.0 xHCI Controller [8086:a2af] 00:16.0 Communication controller [0780]: Intel Corporation 200 Series PCH CSME HECI #1 [8086:a2ba] 00:17.0 SATA controller [0106]: Intel Corporation 200 Series PCH SATA controller [AHCI mode] [8086:a282] 00:1b.0 PCI bridge [0604]: Intel Corporation 200 Series PCH PCI Express Root Port #19 [8086:a2e9] (rev f0) 00:1b.4 PCI bridge [0604]: Intel Corporation 200 Series PCH PCI Express Root Port #21 [8086:a2eb] (rev f0) 00:1c.0 PCI bridge [0604]: Intel Corporation 200 Series PCH PCI Express Root Port #3 [8086:a292] (rev f0) 00:1d.0 PCI bridge [0604]: Intel Corporation 200 Series PCH PCI Express Root Port #9 [8086:a298] (rev f0) 00:1d.2 PCI bridge [0604]: Intel Corporation 200 Series PCH PCI Express Root Port #11 [8086:a29a] (rev f0) 00:1d.3 PCI bridge [0604]: Intel Corporation 200 Series PCH PCI Express Root Port #12 [8086:a29b] (rev f0) 00:1f.0 ISA bridge [0601]: Intel Corporation Device [8086:a2cc] 00:1f.2 Memory controller [0580]: Intel Corporation 200 Series/Z370 Chipset Family Power Management Controller [8086:a2a1] 00:1f.3 Audio device [0403]: Intel Corporation 200 Series PCH HD Audio [8086:a2f0] 00:1f.4 SMBus [0c05]: Intel Corporation 200 Series/Z370 Chipset Family SMBus Controller [8086:a2a3] 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation TU117 [GeForce GTX 1650] [10de:1f82] (rev a1) 01:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:10fa] (rev a1) 03:00.0 Non-Volatile memory controller [0108]: Silicon Motion, Inc. SM2263EN/SM2263XT SSD Controller [126f:2263] (rev 03) 04:00.0 USB controller [0c03]: Renesas Technology Corp. uPD720201 USB 3.0 Host Controller [1912:0014] (rev 03) 06:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 06) 07:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 15)
/etc/default/grub
VFIO を使用してホストから PCI デバイスを分離します。vfio-pci.ids に除外したいベンダー ID とデバイス ID を追加することでホストから対象の PCI デバイスを分離できます。この方法は同じベンダー ID とデバイス ID を持つデバイスは意図せず分離されてしまう可能性があることに注意が必要です。
※カーネルバージョン 5.4 以降(Ubuntu 22.04 の標準カーネルバージョンは 5.15)、vfio-pci ドライバはカーネルモジュールではなくカーネルに組み込まれています。この為、modprobe での vfio-pci ドライバの読み込みはできません。
※参考資料や他の類似の情報では USB カードは VFIO に含めずに KVM の設定で USB カードをホストデバイスとして割り当てています。理由は不明ですが、この状態ではホストとゲスト(Windows)を切り替える(画面切り替え、マウスとキーボード切り替え)際にホスト側でマウスとキーボードが動かない状態となりました。VFIO に含めることで解消しています。
myadmin@ubuntu:~$ sudo mv /etc/default/grub /etc/default/grub.orig myadmin@ubuntu:~$ sudo tee /etc/default/grub <<"EOF" GRUB_DEFAULT=0 GRUB_TIMEOUT_STYLE=hidden GRUB_TIMEOUT=0 GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian` GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt vfio-pci.ids=10de:1f82,10de:10fa,1912:0014" EOF
GRUB のアップデート
GRUB をアップデートして再起動します。
myadmin@ubuntu:~$ sudo update-grub myadmin@ubuntu:~$ sudo reboot
PCI デバイス分離の確認
Kernel driver が vfio-pci であれば PCI デバイスの分離に成功しています。
myadmin@ubuntu:~$ sudo lspci -nnv -s 01:00.0 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation TU117 [GeForce GTX 1650] [10de:1f82] (rev a1) (prog-if 00 [VGA controller]) Subsystem: ASUSTeK Computer Inc. TU117 [GeForce GTX 1650] [1043:86b6] Flags: bus master, fast devsel, latency 0, IRQ 16, IOMMU group 1 Memory at f6000000 (32-bit, non-prefetchable) [size=16M] Memory at e0000000 (64-bit, prefetchable) [size=256M] Memory at f0000000 (64-bit, prefetchable) [size=32M] I/O ports at e000 [size=128] Expansion ROM at f7000000 [disabled] [size=512K] Capabilities: [60] Power Management version 3 Capabilities: [68] MSI: Enable- Count=1/1 Maskable- 64bit+ Capabilities: [78] Express Legacy Endpoint, MSI 00 Capabilities: [100] Virtual Channel Capabilities: [258] L1 PM Substates Capabilities: [128] Power Budgeting > Capabilities: [420] Advanced Error Reporting Capabilities: [600] Vendor Specific Information: ID=0001 Rev=1 Len=024 > Capabilities: [900] Secondary PCI Express Capabilities: [bb0] Physical Resizable BAR Kernel driver in use: vfio-pci Kernel modules: nvidiafb, nouveau myadmin@ubuntu:~$ sudo lspci -nnv -s 01:00.1 01:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:10fa] (rev a1) Subsystem: ASUSTeK Computer Inc. Device [1043:86b6] Flags: bus master, fast devsel, latency 0, IRQ 17, IOMMU group 1 Memory at f7080000 (32-bit, non-prefetchable) [size=16K] Capabilities: [60] Power Management version 3 Capabilities: [68] MSI: Enable- Count=1/1 Maskable- 64bit+ Capabilities: [78] Express Endpoint, MSI 00 Capabilities: [100] Advanced Error Reporting Kernel driver in use: vfio-pci Kernel modules: snd_hda_intel myadmin@ubuntu:~$ sudo lspci -nnv -s 04:00.0 04:00.0 USB controller [0c03]: Renesas Technology Corp. uPD720201 USB 3.0 Host Controller [1912:0014] (rev 03) (prog-if 30 [XHCI]) Flags: bus master, fast devsel, latency 0, IRQ 18, IOMMU group 14 Memory at f7300000 (64-bit, non-prefetchable) [size=8K] Capabilities: [50] Power Management version 3 Capabilities: [70] MSI: Enable- Count=1/8 Maskable- 64bit+ Capabilities: [90] MSI-X: Enable+ Count=8 Masked- Capabilities: [a0] Express Endpoint, MSI 00 Capabilities: [100] Advanced Error Reporting Capabilities: [150] Latency Tolerance Reporting Kernel driver in use: vfio-pci Kernel modules: xhci_pci
Windows 10 をゲスト OS としてインストール
準備
事前に下記を入手して、/var/lib/libvirt/images に配置しておきます。
Windows 10
Windows 10 の ISO イメージをダウンロードします。ツールを使用してダウンロードする為、入手には Windows が必要になります。
https://www.microsoft.com/ja-jp/software-download/windows10
virtio-win drivers
virtio-win drivers の ISO イメージをダウンロードします。特に指定がなければ stable で良いでしょう。
https://github.com/virtio-win/virtio-win-pkg-scripts/blob/master/README.md
下記から直接 stable をダウンロードできます。
https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso
仮想マシンの作成
Windows のインストールには GUI が必要であることと、PCI パススルーの使用で少々手順が煩雑となることから、仮想マシンマネージャーから操作しました。
新しい仮想マシンの作成
ステップ 1/5
[ ローカルのインストールメディア ] を選択します。
ステップ 2/5
Windows 10 の ISO イメージを選択します。オペレーティングシステムが自動識別されない場合は手動で検索して設定します。
ステップ 3/5
CPU とメモリは後から設定するので一旦そのままとしました。
ステップ 4/5
ディスクサイズは 200GB にしました。
ステップ 5/5
[ インストールの前に設定をカスタマイズする ] にチェックを入れます。
インストール前のカスタマイズ
概要
チップセットを Q35、ファームウェアを UEFI に設定します。Windows 11 の要件の 1 つです。
CPU
[ ホスト CPU の設定をコピーする] にチェックを入れます。[ CPU トポロジーの手動設定 ] にチェックを入れ、ソケット数 1、コア数 6、スレッド数 1 を設定します。これは実際の CPU と同じにしました。今のところ他の仮想マシンとの兼ね合いは問題なく、性能面でも良いようです。
メモリ
12288 MB (12 GB) にしました。
NIC
事前に netplan で作成した br3000 を指定しました。
ハードウェアの追加
ストレージ
[ カスタムストレージの選択または作成 ] を選択し、[ 管理 ] から virtio の ISO イメージを指定します。[ デバイスの種類 ] に [ CD-ROM デバイス ] を指定して追加します。これは Windows のインストール中に virtio ドライバを読み込ませるためです。
TPM
種類 Emulated、モデル CRB、バージョン 2.0 を指定して追加します。Windows 11 の要件の 1 つです。
PCI ホストデバイス
前述の「PCI デバイスの分離」でホストから切り離した 01:00.0, 01:00.1, 04:00.0 を追加します。
OS インストール
仮想マシンマネージャーの画面を使用してインストールを進めます。基本的に通常の Windows インストールの操作と同じですが、ストレージの設定画面でディスクが認識されない為、virtio の ISO イメージから viostor\w10\amd64 を選択してドライバをインストールし、ディスクを認識させます。この時点では PCI パススルーしたグラフィックカードに切り替えても表示されませんでした。
PCI パススルーしたグラフィックカードのみに変更
Windows インストールが完了後、仮想マシンマネージャーから、SATA CDROM1, SATA CDROM2, ディスプレイ Spice, ビデオ QXL, チャンネル spice を削除して起動します。ディスプレイとビデオを削除しないと仮想マシンマネージャーの画面がデュアルディスプレイで優先されるため、PCI パススルーしたグラフィックカード側からは操作ができない状態になります。
削除後にディスプレイと、キーボードとマウスのレシーバーを PCI パススルーした側に切り替えると、普段の Windows を使用できます。
追加のチューニング
virsh edit で CPU pinning を試しました。多少動作が良くなった気がします。6 core しかないので、ちょっと心もとないですね。
<vcpu placement='static'>4</vcpu> <cputune> <vcpupin vcpu='0' cpuset='2'/> <vcpupin vcpu='1' cpuset='3'/> <vcpupin vcpu='2' cpuset='4'/> <vcpupin vcpu='3' cpuset='5'/> </cputune> <cpu mode='host-passthrough' check='none' migratable='on'> <topology sockets='1' dies='1' cores='4' threads='1'/> </cpu>
全体の負荷を下げる為、KVM ホストの GUI 起動を停止しました。
myadmin@ubuntu:~$ systemctl get-default myadmin@ubuntu:~$ sudo systemctl set-default multi-user myadmin@ubuntu:~$ shutdown -r now
再起動後は CUI で起動します。GUI を起動する場合は下記のコマンドを使用します。
myadmin@ubuntu:~$ sudo systemctl start gdm3
ヒュージページも効果的なようですが、Windows で効果を実感するには、少し面倒な作業が必要になりそうでしたので止めました。
感想
これまでの設定で、普通に VM として違和感なく Windows を使うことができます。ですが、私の環境では、他のVM の負荷が上がった際に、DTM で利用している Windows 上の USB オーディオインターフェイスの音が途切れる場合があり、それがどうしても改善せず、ネイティブ Windows に戻しました。
ただ、今回の検証で仮想化におけるチューニングの多くのことを学べました。
VM、コンテナ問わず、仮想化では利便性が高まる半面、性能面が当然失われますが、個人利用の範囲ではネットワーク、ストレージ速度は気にならないと思います。メモリも必要分を割り当てれば問題ないです。Windows のようなゴージャスな OS、つまり人の感覚に直結する OS を稼働させる場合は、グラフィック、サウンドはかなりシビアです。特にこだわる場合ですが、こういった部分の性能を求めるのであれば、GPU パススルーが可能ではあるものの、現状では普通に物理 PC に OS をインストールしたほうが良いですね。
通信キャリアなどでは、SR-IOV、DPDK 等でネットワーク性能のチューニングに労力を割くことが多いです。
いずれにせよ、現状では、仮想化環境でネイティブに近い性能を得る=仮想からハードウェアに直接アクセスする、という構図なので、仮想化の手軽さが失われ、レイヤが増える分、管理コストも増すのが、仮想化で性能を追求する際のデメリットです。なのでどっちかというと仮想化のメリットは忘れて取り組むほうが良いです。
というように結構気持ち悪い感じはするのですが、5G などもありトピックとしては活発ではあるようなので、今後はより整理されていく可能性はあると思っています。