快轉到主要內容

如何在Windows虛擬機玩遊戲 ~ Linux QEMU/KVM雙GPU直通 + Looking Glass安裝過程

分類   Linux系統 虛擬機與容器技術
標籤   GPU Passthrough QEMU Linux Windows
🗓️ 民國112年 癸卯年
✍ 切換正體/簡體字
目錄

Linux系統雖在 Steam Proton的支援下,能玩的Windows遊戲數目大幅增加,然而仍有許多遊戲和程式無法用Proton執行,必須得用Windows虛擬機跑。

本文Ivon將討論在Linux系統跑Windows虛擬機的方法。於虛擬機啟用硬體加速,順暢低延遲的玩遊戲。

設定過程不雙重開機,不接第二個螢幕,不準備二個滑鼠,不登出目前的使用者,直接在單一螢幕的Linux桌面環境操作Windows。

使用例1:在虛擬機玩Windows專屬的Minecraft基岩版
使用例1:在虛擬機玩Windows專屬的Minecraft基岩版
使用例2:在虛擬機低延遲看高畫質影片
使用例2:在虛擬機低延遲看高畫質影片

在英文圈,本文使用的技術可以概括的稱為"VFIO Gaming on Linux"。

以下為實際啟動過程的演示影片

1. 安裝前先論破
#

為什麼不雙重開機就好?雙系統會影響穩定度,例如Windows更新有機率弄壞GRUB(雖然Arch也會自己弄壞就是了)。另,我不想讓Windows觸碰到我的系統,且虛擬機彈性大,方便備份。

GPU直通,乃利用Linux核心的VFIO功能,讓虛擬機存取實體顯示卡(GPU),增進圖形效能。GPU直通目前只有QEMU/KVM虛擬機技術支援。由於是直通給Windows虛擬機,不論顯示卡品牌為何,Windows應該都抓得到驅動。

此外,透過Libvirt的指令,虛擬機在關機後可解除綁定VFIO,將顯示卡「還給」host使用。

螢幕問題
#

畫面部份,在GPU直通後,Virt Manager的QXL圖形效能僅是尚可,無法低延遲顯示Windows畫面,要玩遊戲或影片剪輯的話根本不敷需求,故我們要採用其他技術。

接兩個螢幕是最簡單的作法,畫面效率最高。不過我只想放一個螢幕,第二個螢幕插入HDMI欺騙器代替使顯示卡正常運作。在這樣的配置下,最終選用"Looking Glass"顯示Windows畫面,,它使用共享記憶體(IVSHMEM)繪製緩衝區,從而達到幾乎無延遲的畫面顯示效果。

Looking Glass的伺服端只適用Windows,客戶端則是支援Linux/Windows/macOS。

為何不裝 Moonlight然後遠端存取?Moonlight太吃系統的GPU資源,除非您真的有「遠端玩虛擬機遊戲」的需求再裝。至於Windows RDP,那是遠端桌面,用途不一樣。

音訊問題
#

音訊部份有3個方案:Looking Glass內建的SPICE、Scream、PulseAudio。

Scream是一款虛擬音效卡程式,該程式會在Windows虛擬出一個音效輸出裝置,Linux Host再跑一個接收器來接受音訊。儘管Scream延遲最低,可是安裝有點複雜,最新版我還遇到驅動簽名過期的問題。

PulseAudio延遲跟SPICE半斤八兩,所以我採用SPICE,它可以讓虛擬機存取實體機的喇叭和麥克風,但延遲就不太樂觀。

其實,你還可以考慮音效卡直通的做法,買個USB音效卡,直通給虛擬機用就沒有延遲問題了。

2. 環境
#

雖然這裡寫Arch Linux,但我測試Ubuntu 22.04環境一樣可以用。

  • CPU:Intel I5-7400
  • GPU:Intel UHD 630
  • GPU:Nvidia GTX-1050Ti
  • SSD:1TB
  • Host OS:Arch Linux
  • Guest OS:Windows 11 22H2
  • Host OS桌面環境:KDE Plasma X11
  • Host OS音訊伺服器:Pipewire
  • QEMU版本:8.0.2

雖是雙GPU直通,但電腦只有一個螢幕,為了讓另一個直通給虛擬機的GPU正常運作,我購置了「HDMI欺騙器」插在獨顯上,使其能在虛擬機裡面正常運作。

使用HDMI顯卡欺騙器前務必先裝好Nvidia驅動,以及設定Looking Glass開機自動啟動。可以先用實體雙螢幕方案,確認Looking Glass能運作了之後再改回HDMI欺騙器。

3. 安裝Windows虛擬機
#

首先,給電腦啟用虛擬化,安裝QEMU/KVM套件,再安裝Windows 11虛擬機,TPM可以用軟體模擬。

建議分配8GB RAM 、4核心以上CPU、128GB以上虛擬硬碟。必要情況下掛載SSHFS共享Host OS的目錄。

安裝虛擬機不需真的啟用Secure Boot,但建議隱藏KVM虛擬化狀態。

參見: 安裝Windows 11的QEMU/KVM虛擬機

4. 直通GPU給Windows虛擬機
#

請準備第二個實體螢幕,或是HDMI欺騙器,使顯示卡能正常運作。

依照個人情況,看要直通Intel內顯或Nvidia獨顯都可以,通常我是選擇後者。

參見:

5. 強化虛擬機CPU性能
#

本節啟用的三個東西:1. Hugepage,增加記憶體利用效率。 2. Hyper-V保留系統資源 3. 讓vCPU獨佔實體CPU核心。

  1. 首先啟用hugepage,可以用cat /proc/meminfo | grep Huge查看有無啟用Hugepage,若Hugetlb為0kb代表沒啟用。

  2. 編輯sysctl設定

sudo vim /etc/sysctl.d/99-sysctl.conf
  1. 將8GB RAM (1 hugepage = 2mb)保留給虛擬機使用(會直接從宿主機扣除)
vm.nr_hugepages=4096
vm.hugetlb_shm_group=48
  1. 重開機,開啟Virt Manager,編輯XML,在<memory>區塊的下方加入<memoryBacking>
<memory unit="KiB">8388608</memory>
<currentMemory unit="KiB">8388608</currentMemory>

<memoryBacking>
<hugepages/>
</memoryBacking>
  1. 再來調整Hyper-V設定,找到<features></features>區塊,在那之間填入如下內容:
<features>
...

  <hyperv mode="custom">
      <relaxed state="on"/>
      <vapic state="on"/>
      <spinlocks state="on" retries="8191"/>
      <vpindex state="on"/>
      <runtime state="on"/>
      <synic state="on"/>
      <stimer state="on"/>
      <reset state="on"/>
      <vendor_id state="on" value="123456789123"/>
      <frequencies state="on"/>
    </hyperv>

...
</features>
  1. 開啟Virt Manager → 編輯CPU,勾選複製主機CPU配置。再依情況手動設定CPU拓樸,例如我是1通訊端4核心1執行緒

  2. 最後,讓vCPU獨佔實體CPU核心。找到<vcpu></vcpu>區塊,在該區塊下方填入要獨佔的核心,例如我這裡有4個核心。

  <cpu mode="host-passthrough" check="none" migratable="on">
    <topology sockets="1" dies="1" cores="4" threads="1"/>
  </cpu>

  <cputune>
    <vcpupin vcpu="0" cpuset="0"/>
    <vcpupin vcpu="1" cpuset="1"/>
    <vcpupin vcpu="2" cpuset="2"/>
    <vcpupin vcpu="3" cpuset="3"/>
  </cputune>
  1. 有時候可能會遇到CPU佔用異常高的情況,這時請試著設定migratable="off"
  <cpu mode="host-passthrough" check="none" migratable="off">
    <topology sockets="4" dies="1" cores="1" threads="1"/>
  </cpu>

6. 設定Looking Glass,低延遲存取Windows桌面
#

將Windows虛擬機關機,接著參考 Linux Looking Glass安裝教學

7. 設定SPICE音訊
#

此處的SPICE音訊為Looking Glass最新版內建的服務,支援存取麥克風。

  1. 將虛擬機關機。

  2. 安裝QEMU PulseAudio驅動套件

sudo pacman -S qemu-audio-pa
  1. 編輯QEMU設定檔
sudo vim /etc/libvirt/qemu.conf
  1. 找到此段,將Libvirt設定為以一般使用者執行(user為您的使用者名稱)
user = "user"
  1. 重新啟動Libvirt
sudo systemctl restart libvirtd
  1. 開啟Virt Manager,編輯Windows 11硬體,新增音效卡ICH9。

8. 防止遊戲反作弊機制偵測到虛擬機環境
#

依實際情況做選擇,不是每個遊戲都有反作弊機制。

參考 QEMU/KVM繞過Easy Anti-cheat

9. 測試連線到虛擬機
#

您可能需要準備2個滑鼠,1個給虛擬機專用防止漂移。您可以用 KDE Connect的遠端控制功能虛擬一個滑鼠出來給宿主機使用。

  1. 開啟Virt Manager,編輯Windows 11虛擬機硬體,將顯示卡設定為「無」。這樣Windows開機後會進入只有獨顯輸出畫面的單螢幕狀態。

  2. 點選新增硬體 → 新增USB裝置,將滑鼠新增給虛擬機用

  3. 將虛擬機開機,關閉Virt Manager視窗。

  4. 從終端機啟動Looking Glass客戶端,參考 官方文件參數,我-F用讓Looking Glass全螢幕。加入-k顯示FPS

looking-glass-client -F -k
  1. 此時就會看到Windows畫面,按Scroll lock + F開關全螢幕。

可以測試音效有無正常運作,如果Windows的音效有動畫,Host OS卻聽不到,請試著到KDE的系統設定調整音訊輸出模式(調整雙工模式)。

對音效點選右鍵 → 音效設定即可存取麥克風。

  1. 由於Looking Glass採用SPICE的緣故,按鍵盤的Windows鍵就能回到Host OS。非全螢幕模式下,用第二個滑鼠移動也能回到Host OS。

Looking Glass的視窗可以隨時關掉重開,Windows程式仍會繼續執行。

10. 建立快速啟動的桌面捷徑
#

  1. 將自己加入libvirt群組
sudo usermod -a -G libvirt $(whoami)
  1. 編輯/etc/libvirt/libvirtd.conf,將unix_sock_rw_perms設定為0770
unix_sock_rw_perms = "0770"
  1. 重啟libvirtd服務
sudo systemctl restart libvirtd
  1. 新增指令稿
touch ~/.local/share/startlookinglass.sh
chmod +x ~/.local/share/startlookinglass.sh
vim ~/.local/share/startlookinglass.sh
  1. 貼上以下內容,啟動名為win11的虛擬機(用指令virsh -c qemu:///system list --all查看),等10秒後再啟動Looking Glass
#!/bin/bash
/usr/bin/virsh -c qemu:///system start win11
sleep 10
/usr/bin/looking-glass-client -F
  1. 新增桌面捷徑
vim ~/Desktop/Looking Glass.desktop
  1. 設定啟動指令稿。這樣桌面點二下該捷徑即會自動啟動虛擬機與Looking Glass。
[Desktop Entry]
Name=Looking Glass (Full Screen)
Comment=
Exec=bash /home/user/.local/share/startlookinglass.sh
Icon=
Terminal=false
Type=Application
MimeType=

附錄:我的完整虛擬機XML
#

在這裡附上我Windows 11的虛擬機XML供參考,請勿完全照抄。

<domain type="kvm">
  <name>Windows11</name>
  <uuid>UUID</uuid>
  <metadata>
    <libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
      <libosinfo:os id="http://microsoft.com/win/11"/>
    </libosinfo:libosinfo>
  </metadata>
  <memory unit="KiB">8388608</memory>
  <currentMemory unit="KiB">8388608</currentMemory>
  <memoryBacking>
    <hugepages/>
  </memoryBacking>
  <vcpu placement="static">4</vcpu>
  <cputune>
    <vcpupin vcpu="0" cpuset="0"/>
    <vcpupin vcpu="1" cpuset="1"/>
    <vcpupin vcpu="2" cpuset="2"/>
    <vcpupin vcpu="3" cpuset="3"/>
  </cputune>
  <sysinfo type="smbios">
    <bios>
      <entry name="vendor">LENOVO</entry>
    </bios>
    <system>
      <entry name="manufacturer">Microsoft</entry>
      <entry name="product">Windows11</entry>
      <entry name="version">22H2</entry>
    </system>
    <baseBoard>
      <entry name="manufacturer">LENOVO</entry>
      <entry name="product">20BE0061MC</entry>
      <entry name="version">0B98401 Pro</entry>
      <entry name="serial">W1KS427111E</entry>
    </baseBoard>
    <chassis>
      <entry name="manufacturer">Dell Inc.</entry>
      <entry name="version">2.12</entry>
      <entry name="serial">65X0XF2</entry>
      <entry name="asset">40000101</entry>
      <entry name="sku">Type3Sku1</entry>
    </chassis>
    <oemStrings>
      <entry>myappname:some arbitrary data</entry>
      <entry>otherappname:more arbitrary data</entry>
    </oemStrings>
  </sysinfo>
  <os firmware="efi">
    <type arch="x86_64" machine="pc-q35-8.0">hvm</type>
    <firmware>
      <feature enabled="no" name="enrolled-keys"/>
      <feature enabled="yes" name="secure-boot"/>
    </firmware>
    <loader readonly="yes" secure="yes" type="pflash">/usr/share/edk2/x64/OVMF_CODE.secboot.fd</loader>
    <nvram template="/usr/share/edk2/x64/OVMF_VARS.fd">/var/lib/libvirt/qemu/nvram/Windows11_VARS.fd</nvram>
    <smbios mode="sysinfo"/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <hyperv mode="custom">
      <relaxed state="on"/>
      <vapic state="on"/>
      <spinlocks state="on" retries="8191"/>
      <vpindex state="on"/>
      <runtime state="on"/>
      <synic state="on"/>
      <stimer state="on"/>
      <reset state="on"/>
      <vendor_id state="on" value="123456789123"/>
      <frequencies state="on"/>
    </hyperv>
    <kvm>
      <hidden state="on"/>
    </kvm>
    <vmport state="off"/>
    <smm state="on"/>
    <ioapic driver="kvm"/>
  </features>
  <cpu mode="host-passthrough" check="none" migratable="on">
    <topology sockets="1" dies="1" cores="4" threads="1"/>
  </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="no"/>
    <suspend-to-disk enabled="no"/>
  </pm>
  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type="file" device="disk">
      <driver name="qemu" type="qcow2"/>
      <source file="/var/lib/libvirt/images/windows11.qcow2"/>
      <target dev="vda" bus="virtio"/>
      <boot order="2"/>
      <address type="pci" domain="0x0000" bus="0x04" slot="0x00" function="0x0"/>
    </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="0x1e"/>
      <address type="pci" domain="0x0000" bus="0x00" slot="0x03" function="0x6"/>
    </controller>
    <controller type="pci" index="16" model="pcie-to-pci-bridge">
      <model name="pcie-pci-bridge"/>
      <address type="pci" domain="0x0000" bus="0x08" 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:25:0c:bf"/>
      <source network="default"/>
      <model type="virtio"/>
      <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>
    <input type="tablet" bus="usb">
      <address type="usb" bus="0" port="1"/>
    </input>
    <input type="mouse" bus="ps2"/>
    <input type="keyboard" bus="ps2"/>
    <tpm model="tpm-tis">
      <backend type="emulator" version="2.0"/>
    </tpm>
    <graphics type="spice" autoport="yes">
      <listen type="address"/>
      <image compression="off"/>
    </graphics>
    <sound model="ich9">
      <address type="pci" domain="0x0000" bus="0x00" slot="0x1b" function="0x0"/>
    </sound>
    <audio id="1" type="spice"/>
    <video>
      <model type="none"/>
    </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>
    <hostdev mode="subsystem" type="pci" managed="yes">
      <source>
        <address domain="0x0000" bus="0x01" slot="0x00" function="0x1"/>
      </source>
      <address type="pci" domain="0x0000" bus="0x07" 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="virtio">
      <address type="pci" domain="0x0000" bus="0x05" slot="0x00" function="0x0"/>
    </memballoon>
    <shmem name="looking-glass">
      <model type="ivshmem-plain"/>
      <size unit="M">32</size>
      <address type="pci" domain="0x0000" bus="0x10" slot="0x01" function="0x0"/>
    </shmem>
  </devices>
</domain>

參考資料
#

相關文章

用RDP+ZeroTier存取遠端Linux的Windows虛擬機的桌面
分類   Linux系統 虛擬機與容器技術
標籤   Remote Desktop Windows Linux ZeroTier QEMU
Linux系統Intel內顯直通給QEMU/KVM虛擬機的方法,不採用GVT-g
分類   Linux系統 虛擬機與容器技術
標籤   GPU Passthrough Arch Linux QEMU
在Linux安装Windows 11 QEMU虛擬機時啟用Secure Boot和TPM 2.0
分類   Linux系統 虛擬機與容器技術
標籤   Windows QEMU Libvirt

留言板

此處提供二種留言板。點選按鈕,選擇您覺得方便的留言板。要討論程式碼請用Giscus,匿名討論請用Disqus。

這是Giscus留言板,需要Github帳號才能留言。支援markdown語法,若要上傳圖片請貼Imgur或Postimages。您的留言會在Github Discussions向所有人公開。

這是Disqus留言板,您可能會看到Disqus強制投放的廣告。有時留言可能會被系統判定需審核,導致延遲顯示,請見諒。若要上傳圖片請貼Imgur或Postimages。