手当たり次第に書くんだ

飽きっぽいのは本能

Ubuntu 22.04 KVM PCI パススルー

目次に戻る

1. 概要

KVM 上の Windows 10 に PCI デバイス(GPU/USB コントローラ) を直接割り当て、仮想 GPU の描画性能不足を補い、標準の USB リダイレクトでは不安定・非対応になりがちな機器(例:iPhone の認識など)を ゲストから“素の PCI デバイス”として扱える ようにします。

ここでは Ubuntu 22.04(Intel VT-d 前提)での手順と注意点をまとめます。

2. モチベーション

「素直に物理 Windows を入れれば仮想化のオーバーヘッドはゼロ」。これはその通りです。

それでも本構成に踏み切る動機は、次のとおりです。

  • Windows を KVM の仮想マシンとして動かしたいが、可能な限り、性能をベアメタルに近づけたい。
  • SR-IOV/CPU ピンニング/HugePages など、近年の性能チューニングを自宅環境で再現・検証したい。
  • 画面描画や USB オーディオのような 人間の感覚に直結する処理 は、仮想 GPU や USB リダイレクトだと限界があるため、直結(PCI パススルー)で底上げ したい。

3. 基本情報

3.1. ハードウェア

以下の構成で構築しました。

パーツメーカー型番備考
PC ケースCooler MasterMCS-S600-KN5N-S00ミドルタワーケース
電源ユニットCorsairCP-9020195-JP80PLUS GOLD
マザーボードASUSPRIME B365-PLUSATX
CPUINTELi5-96006cores, 6threads
CPU クーラーサイズ虎徹 Mark II
メモリCrucialW4U2666CM-8G8GB x 4
M.2 SSDシリコンパワーSP512GBP34A60M28512GB
SSDCrucialCT1000MX500SSD11TB
グラフィックカードASUSDUAL-GTX1650-O4GPCI パススルー対象
USB3.0 カードオウルテックOWL-PCEXU3E4PCI パススルー対象
ネットワークカードTP-LinkTG-3468

以降の手順では グラフィックカード(DUAL-GTX1650-O4G)USB3.0 カード(OWL-PCEXU3E4) を仮想マシンにパススルーします。

3.2. ハードウェアの周辺環境

ディスプレイは 1 台を使用し、オンボードの HDMI と GPU の HDMI をそれぞれ接続しています。

切替スイッチでホスト(Ubuntu)とゲスト(Windows)を手動で切り替え、キーボード・マウスの USB レシーバーも物理的に差し替えます。

この構成により、ホストとゲストを完全に独立した操作系として扱えます。

3.3. BIOS

BIOSで Intel VTIntel VT-d を有効にします。これが無効の場合、PCI パススルーは動作しません。

3.4. ソフトウェア

3.4.1. OS

Ubuntu Desktop 22.04.1 LTS を使用しました。Server 版でも手順は概ね同一です。

3.4.2. KVM

KVM の基本パッケージをインストールします。他の補助ツール(例:bridge-utils, cpu-checker 等)は必要に応じて追加してください。

sudo apt -y install qemu-kvm libvirt-daemon-system libosinfo-bin virtinst virt-manager

3.5. ネットワーク設定

Ubuntu Desktop のデフォルトの netplan は下記となっており、network-manger が netplan の制御を行っています。

cat /etc/netplan/01-network-manager-all.yaml
network:
  version: 2
  renderer: NetworkManager

本稿では Ubuntu Server に合わせて network-manger の制御を無効にします。上記のファイルを削除して再起動すると全ての NIC が network-manager の制御から外すことができます。renderer: networkd としても良いようですが、この場合、ブリッジを再起動すると仮想マシンの通信が切れる問題があった為、renderer の設定は削除しています。これは Ubuntu Server と同じ状態です。

sudo rm /etc/netplan/01-network-manager-all.yaml
sudo reboot

再起動後、nmcli で unmanaged を確認できます。

LANG=C nmcli device
DEVICE    TYPE      STATE      CONNECTION
enp6s0    ethernet  unmanaged  --
enp7s0    ethernet  unmanaged  --
lo        loopback  unmanaged  --

今回のネットワーク設定は、下記にように VLAN をブリッジする構成としています。

sudo tee /etc/netplan/00-main.yaml <<"EOF"
network:
  version: 2
  ethernets:
    enp7s0: {}
  vlans:
    vlan3000:
      id: 3000
      link: 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
EOF
sudo netplan apply

4. IOMMU の有効化

KVM で PCI パススルーを行うには、まず IOMMU(Input–Output Memory Management Unit) を有効にする必要があります。

これは CPU によるデバイスアクセスのメモリ空間分離を提供し、ゲスト OS が特定の PCI デバイスを直接制御できるようにする仕組みです。

4.1. GRUB 設定の編集

Intel 環境の場合、以下のように /etc/default/grub を編集します。

sudo cp /etc/default/grub /etc/default/grub.orig
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
  • intel_iommu=on:Intel IOMMU 機能(VT-d)を有効化
  • iommu=pt:ホスト上でのIOMMU処理を最小化(パススルー性能を向上)

AMD 環境の場合、intel_iommu=on の代わりに amd_iommu=on を指定します。

4.2. GRUB の更新と再起動

sudo update-grub
sudo reboot

4.3. 有効化の確認

再起動後、以下のコマンドで IOMMU が認識されているかを確認します。

sudo dmesg | grep -i iommu
[    0.061643] DMAR: IOMMU enabled
[    0.157075] DMAR-IR: IOAPIC id 2 under DRHD base  0xfed91000 IOMMU 1

IOMMU enabled と表示されていれば有効化されています。

4.4. トラブルシューティング

  • BIOS で Intel VT-d / AMD-Vi が無効になっていないか確認
  • 一部の古いチップセットでは VT-d がサポートされない場合があります
  • dmesgIOMMU disabledDMAR: No ATSR found などが出る場合、BIOS 設定またはカーネル引数の誤りが疑われます。

5. PCI デバイスの分離

IOMMU が有効になったら、仮想マシンに割り当てたい PCI デバイスを VFIO(Virtual Function I/O) でホスト OS から切り離します。

これにより、Linux カーネルがそのデバイス用の標準ドライバをロードせず、仮想マシン専用として扱えるようになります。

5.1. デバイス ID の確認

まず、対象デバイスの ベンダーIDデバイスID を確認します。

lspci -nn
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation TU117 [GeForce GTX 1650] [10de:1f82]
01:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:10fa]
04:00.0 USB controller [0c03]: Renesas Technology Corp. uPD720201 USB 3.0 Host Controller [1912:0014]

ここでは以下の 3 デバイスをパススルー対象とします。

デバイスベンダーID:デバイスID備考
NVIDIA GPU10de:1f82グラフィック出力用
NVIDIA Audio10de:10faGPU 搭載オーディオ機能
Renesas USB3.01912:0014USB コントローラー

5.2. GRUB で VFIO を指定

VFIO はカーネル起動時に対象デバイスをバインドします。

/etc/default/grubvfio-pci.ids= を追記します。

sudo cp /etc/default/grub /etc/default/grub.bak
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
sudo update-grub
sudo reboot

Ubuntu 22.04 の標準カーネル(5.15 以降)では vfio-pci はモジュールではなくカーネルに組み込まれています。そのため、modprobe vfio-pci は不要です。

5.3. バインドの確認

再起動後、lspci コマンドで各デバイスが vfio-pci にバインドされていることを確認します。

sudo lspci -nnv -s 01:00.0
sudo lspci -nnv -s 01:00.1
sudo lspci -nnv -s 04:00.0

出力の中に次の行があれば成功です。

Kernel driver in use: vfio-pci

5.4. 注意点と補足

  • 同一 ID デバイスの巻き込みに注意
    同一ベンダー ID/デバイス ID を持つデバイスが複数存在する場合、意図しないデバイスも分離される可能性があります。
  • USB カードを VFIO に含める理由
    他の情報では USB カードを KVM 設定で直接割り当てる例もありますが、その方法ではホスト・ゲストの切り替え時にマウス・キーボード入力が一時的に失われる問題がありました。VFIO に含めてパススルーすることで、この問題を回避できます。

5.5. トラブルシューティング

  • Kernel driver in usenvidiaxhci_pci のまま
    GRUB の編集が反映されていない可能性があります。update-grub の実行漏れや /boot の空き容量不足を確認。
  • vfio-pci がロードされない
    BIOS の VT-d / IOMMU 設定を再確認、または iommu=pt の指定を削除して再試行。

6. Windows 10 をゲスト OS としてインストール

6.1. 準備

以下の ISO イメージを取得し、/var/lib/libvirt/images に配置しておきます。

種類入手先備考
Windows 10Microsoft 公式サイトツール経由でダウンロード(Windows 環境が必要)
virtio-win driversvirtio-win GitHub または Fedora People Direct Downloadstable 版を推奨

6.2. 仮想マシンの作成(virt-manager 使用)

virt-install でもインストール可能だと思いますが、PCI パススルーなどを調べるのが面倒でしたので、virt-manager(GUI)を使用しました。

6.2.1. 新規作成

  1. ローカルのインストールメディア を選択
  2. Windows 10 の ISO を指定
  3. OS が自動認識されない場合は手動で「Windows 10」を選択
  4. CPU・メモリは後で調整するためデフォルトのまま
  5. 「インストール前に設定をカスタマイズする」 にチェックを入れる

6.2.2. インストール前のカスタマイズ

概要:

  • チップセット:Q35
  • ファームウェア:UEFI(Windows 11 にも対応)

CPU:

  • 「ホスト CPU の設定をコピー」を有効化
  • 「CPU トポロジーを手動設定」→ ソケット数 1、コア数 6、スレッド数 1
    (実機 CPU と同じ構成にするのが安定)

メモリ:

  • 12GB(12288MB)を割り当て

ネットワーク:

  • netplan で作成したブリッジ br3000 を指定

TPM:

  • 種類:Emulated
  • モデル:CRB
  • バージョン:2.0
    → Windows 11 の要件に対応

virtio ドライバ:

  • デバイスの追加 → 「CD-ROM デバイス」
  • virtio-win の ISO イメージを指定(ストレージドライバ用)

PCI ホストデバイス:

  • 「PCI ホストデバイスを追加」から
    以下の 3 デバイスを選択:
    • 01:00.0(GPU)
    • 01:00.1(GPU Audio)
    • 04:00.0(USB コントローラ)

6.3. OS インストール

  1. 仮想マシンを起動し、通常の Windows インストール手順を進めます。
  2. ストレージが認識されない場合は、virtio-win.iso 内の viostor\w10\amd64 を指定してドライバを読み込みます。
  3. Windows のセットアップを完了させます。この時点では、まだ仮想ディスプレイ(QXL)経由で操作します。

6.4. PCI パススルーした GPU のみを使用する

Windows の初期設定が完了したら、仮想ディスプレイを削除して物理 GPU のみで起動 させます。

  1. 仮想マシンを停止
  2. 以下のデバイスを削除
    • SATA CD-ROM1(Windows ISO)
    • SATA CD-ROM2(virtio ISO)
    • ディスプレイ:Spice
    • ビデオ:QXL
    • チャンネル:spice
  3. モニター入力を GPU 側 HDMI に切り替え
  4. USBレシーバーをパススルーした側へ接続

起動後、GPUドライバを自動認識しない場合は NVIDIA の公式ドライバを手動インストールします。デバイスマネージャで GPU と USB コントローラが正しく認識されれば設定完了です。

6.5. 追加のチューニング

仮想マシンはこの時点で実用的に動作しますが、負荷分散や遅延対策のためにいくつかの調整を行うとより安定します。

6.5.1. CPU ピニング(CPU 割り当ての固定)

CPU コアを仮想マシン専用に固定することで、ホスト側のスレッドスケジューリングによるジッタ(処理の揺らぎ)を減らします。

virsh edit <VM 名> で設定を編集します。

<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>
  • <vcpu>:仮想 CPU 数(ここでは 4)
  • <vcpupin>:各 vCPU をホストのどのコアに割り当てるかを指定
  • host-passthrough:CPU 機能をほぼそのままゲストに渡す

6.5.2. ホストの GUI を無効化(リソース節約)

ホスト側で GUI 操作が不要である場合、テキストモードで起動することで、リソースが節約され、安定性が向上します。

systemctl get-default
sudo systemctl set-default multi-user.target
sudo reboot

必要時のみ GUI を起動する場合:

sudo systemctl start gdm3

6.5.3. HugePages

HugePages は仮想マシンのメモリアクセスを高速化する仕組みです。ただし、Windows 側での体感差は小さいため、効果を検証する場合のみ設定します。

ホスト側で HugePages を確保する例:

grep Huge /proc/meminfo
sudo sysctl -w vm.nr_hugepages=1024

libvirt ドメイン XML に以下を追加:

<memoryBacking>
  <hugepages/>
</memoryBacking>

6.5.4. 音声処理(USB オーディオ)の安定化

オーディオインターフェイスなどリアルタイム性が高いデバイスは、他の VM やホストプロセスの割り込み競合で途切れることがあります。

改善策として:

  • GRUB_CMDLINE_LINUXthreadirqs を追加し、IRQ をスレッド化
  • USB カードを別 IOMMU グループに配置(PCI スロットを変更)
  • isolcpus / rcu_nocbs でVM 専用コアを隔離

これでも断続的なノイズが出る場合は、物理環境での利用を検討した方が現実的です。

6.6. まとめ

これらの調整によって、KVM 環境でも体感的に滑らかな Windows 操作が可能になります。ただし、「仮想化で性能を追う=管理コストが増える」というトレードオフは常に意識しておくと良いでしょう。

7. 感想

ここまでの設定で、Windows は違和感なく動作しました。GPU 描画や USB 機器のレスポンスも十分で、通常利用であれば物理環境との差はほとんど感じません。

ただし、私の環境では他の仮想マシンの負荷が高まると、Windows 上で使用している USB オーディオインターフェイスの音が途切れることがあり、この問題だけは満足いく解消できませんでした。

このため、最終的にはネイティブ Windows に戻しています。

とはいえ、この構成を通じて 仮想化における性能チューニングの考え方 を多く学ぶことができました。

CPU ピンニングや IOMMU、SR-IOV といった仕組みがどのように重なり合い、ハードウェア資源をどの層まで仮想化で抽象化できるのか。その限界点がよく見えました。

7.1. 実用面の整理

仮想化は利便性に優れますが、グラフィックやオーディオのようなリアルタイム性の高い処理では遅延やノイズが発生しやすく、完全にベアメタルと同等にするのは現実的ではありません。

ネットワークやストレージは十分な性能を得やすい一方で、人間の感覚に直結する入出力デバイスは特にシビアです。

7.2. 総括

結局のところ、仮想環境でネイティブに近い性能を得るということは、「仮想化の抽象層を一枚ずつ剥いでハードウェアに直接触れる」ことでもあります。

その分、手軽さや柔軟性は失われ、管理コストが増していきます。性能を突き詰めるほど、仮想化の恩恵からは遠ざかる。それがこの検証で得た一番の教訓でした。

とはいえ、SR-IOV や DPDK のような技術が実用化されているように、仮想化とハードウェア直結の境界は今後さらに整理・洗練されていくでしょう。

個人レベルでも、それを追うこと自体がすでに楽しい実験です。

8. 参考資料

All you need to know about PCI passthrough in Windows 11 virtual machines on Ubuntu 22.04 based distributions

目次に戻る

Ubuntu 22.04 KVM PCI パススルー

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

トップへ戻る