Linux移动端设备SR-IOV实践
1 前言
之前写过一篇QEMU/KVM下通过GPU穿透提升VM图形性能的文章,实际使用下来体验良好。但GPU穿透更适合在台式机/服务器平台进行,移动端受硬件设计的影响,例如iGPU+dGPU笔记本的Optimus MUXless结构或只有单dGPU的笔记本,往往会导致GPU穿透出现兼容性问题。由于是硬件设计导致的问题,一旦出现不兼容基本上就解决不了。那么有没有一种能够提升Linux移动端平台VM图形性能的通用方案呢?答案是显而易见的,提升VM性能主要有两个思路——硬件加速和硬件虚拟化。
硬件加速做得比较好的有VMware这个平台,以及QEMU/KVM支持的spice协议。这些方案确实能够在多数使用场景下提供良好的体验,但在一些对图形能力要求稍高的场景也显得力不从心。由于上手门槛低、配置简单,该方案在本文不作探讨。
硬件虚拟化主要是由Intel主导的两种技术——GVT-g和SR-IOV实现,二者的目的是实现GPU虚拟化:将一个物理GPU划分为多个虚拟GPU(又称vGPU),通过vGPU为VM提供接近物理GPU的图形性能。从目的上来看,这些技术类似于VT-x和VT-d,都是通过虚拟化硬件来提高VM运行效率的技术。GVT-g/SR-IOV虚拟化的硬件是GPU,VT-x虚拟化的硬件是CPU,而VT-d虚拟化的硬件则是I/O设备。关于GVT-g和SR-IOV,Intel宣称SR-IOV是取代GVT-g的新一代GPU虚拟化技术,支持Tiger Lake及之后的CPU平台(对Meteor Lake平台的支持情况暂时未知),GVT-g则是支持从Skylake到Rocket Lake的CPU平台。注意这里说支持的CPU,实际指代这些CPU对应的iGPU,无iGPU的那些CPU自然不被支持。由于笔者使用的设备搭载一颗i9-13900H处理器,故本文针对SR-IOV技术的配置和使用进行探讨,GVT-g请参考其它文章。
有个观点须事先声明,笔者对于图形性能的要求(至少在笔记本这种形态的设备上)并不高。能够在Windows VM中进行一些轻量的图形操作,如视频编解码、图片编辑以及流畅渲染Windows动效即可。这些要求没有超出i9-13900H上这颗Iris Xe核显(设备ID:0xA7A0)的硬件处理能力,个人认为这也可以算作多数人日常使用Windows系统对GPU性能的正常需求。SR-IOV技术在目前的消费级产品中,只有Intel CPU提供支持,因此用户对其性能期望不应过高,毕竟这些CPU搭载的iGPU能力不是太强。SR-IOV不是某些人口中《GPU Turbo》那样的神仙PPT“黑科技”,可以超越硬件自身能力进行“优化”。SR-IOV毕竟是虚拟化技术,对硬件进行一分为多,最好的表现也就是为VM提供(相较于物理GPU)接近无损的图形性能而已。如果你对图形性能要求很高,那么你可以:
- 配备消费级高性能GPU,并直接使用Windows操作系统(99+%用户适用);
- 购买专业级或面向服务器的、官方支持vGPU的高性能GPU(因价格过高,个人将这些设备归类为非消费级产品),然后使用专业的GPU驱动(如Nvidia GRID)和授权,配置vGPU并搭建VM使用;
- 在QEMU/KVM中安装Windows VM,通过GPU穿透(GPU Passthrough)将消费级高性能GPU穿透给Windows VM使用。
作为个人用户,选项2因为价格因素无疑没有多少机会尝试,当然也有机房淘汰的专业GPU可供选择,如Tesla P4。但这样又与“高性能”的需求有所冲突,因此选项3是个人用户可随手尝试的(如果非要用Linux的话)务实之举。本文的关注点在于为搭载Linux的移动端设备配置SR-IOV,并使其Windows VM成功使用vGPU进行工作,达到提升VM图形性能的目的。
2 开启SR-IOV特性
2.1 准备工作——编译Intel Linux LTS内核
截至目前,SR-IOV特性还没有合入到Linux内核主线中,至少在笔者使用的发行版Ubuntu 24.04(内核版本:6.8.0-35)中没有。有消息称,Linux内核从6.11开始将会合入Intel开发的SR-IOV特性。目前要使用SR-IOV特性,需要获取一份Intel提供的LTS内核源码自行编译使用,本文编译配置主要参考这篇文章。先获取源码,后续操作都在源码目录中进行:
git clone https://github.com/intel/linux-intel-lts.git
接下来选择要编译的目标分支,建议选择与当前发行版内核最接近的版本进行编译,如:
git checkout lts-v6.6.34-linux-240621T171440Z
安装编译依赖和工具包:
sudo apt install build-essential bison flex gnupg libncurses-dev libelf-dev libssl-dev wget dwarves fakeroot debhelper
进行编译配置,最省事且不易出错的配置就是使用当前内核的配置为基础进行修改:
cp /boot/config-$(uname -r) .config
make olddefconfig
根据这个帖子,要关闭内核模块的ZSTD压缩配置,否则编译将会报错,确保.config
文件中对应两行的内容如下:
CONFIG_MODULE_COMPRESS_NONE=y
# CONFIG_MODULE_COMPRESS_ZSTD is not set
然后将内核签名相关的配置关闭(不需要签名内核,建议直接在BIOS中关闭secure boot即可),运行如下命令:
scripts/config --disable SYSTEM_TRUSTED_KEYS
scripts/config --disable SYSTEM_REVOCATION_KEYS
scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS ""
scripts/config --set-str CONFIG_SYSTEM_REVOCATION_KEYS ""
停用DEBUG_INFO
参数:
scripts/config --disable DEBUG_INFO
自5.18+版本开始,停用DEBUG_INFO
参数需要将DEBUG_INFO_NONE
参数打开:
scripts/config --enable DEBUG_INFO_NONE
在.config
文件中设定一下后续编译出来的内核版本名称(可选):
CONFIG_LOCALVERSION="-sriov"
接下来开始进行内核编译(-j参数的线程数请保持和CPU物理线程数相等):
make clean
fakeroot make -j20 deb-pkg
编译过程中CPU和内存开销如图所示,合适的并发线程数设置可以跑满CPU,使编译效率最大化:
编译完成后,在当前路径的父级路径中可以找到编译出来的deb包,进行安装:
至此,系统已成功安装支持SR-IOV的Intel Linux内核。目前Intel提供的Linux内核最新仅支持到6.6(LTS分支),比Ubuntu 24.04的6.8(非LTS分支)更旧,因此在Intel新内核发布前,需修改grub配置,让方才编译的内核作为默认启动内核,否则grub默认使用最新内核启动操作系统。首先用sudo grub-mkconfig | less
命令查看所有grub启动项(menuentry),部分输出如下:
...
submenu 'Advanced options for Ubuntu' $menuentry_id_option 'gnulinux-advanced-d767ea47-de15-427a-b9c2-c029b2e0a973' {
...
menuentry 'Ubuntu, with Linux 6.6.34-sriov+' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.6.34-sriov+-advanced-d767ea47-de15-427a-b9c2-c029b2e0a973' {
...
}
...
}
...
按照GRUB Manual,将/etc/default/grub
的GRUB_DEFAULT属性改为:
GRUB_DEFAULT='Advanced options for Ubuntu>Ubuntu, with Linux 6.6.34-sriov+'
执行sudo update-grub
即可应用配置。
2.2 配置vGPU
准备好了内核,接下来需要开启vGPU,这部分主要参考i915-sriov-dkms的文档。首先修改/etc/default/grub
中GRUB_CMDLINE_LINUX_DEFAULT属性,开启对应的内核功能:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=on iommu=pt i915.enable_guc=3 i915.max_vfs=7"
再创建vGPU(通过PCIe设备的function字段标识不同的vGPU,因此也可以叫做创建virtual function):
echo 2 > /sys/devices/pci0000:00/0000:00:02.0/sriov_numvfs
注意写入的时候要找对GPU,可以用lspci -v
查看iGPU对应的PCIe地址。这里创建了2个vGPU,即把物理GPU一分为二,上面的i915.max_vfs=7,说明最多可以创建7个vGPU,因笔者只能用上2个vGPU(Host+VM),故vGPU是否能设置为大于7个笔者未进行验证。到这一步再执行sudo update-grub
应用配置,重启系统即可。
重启后验证vGPU是否生效,直接查看系统的GPU配置即可:
可以看到系统上有两个型号完全一样的GPU,只是其PCIe地址最后的function不同,0代表主GPU,该GPU为Host独占,不能分配给VM使用,否则会导致Host崩溃,大于0的function就是可供VM使用的vGPU。这里只分了2个vGPU,后续如果分更多,就是0,1,...,7这样的function编号,同样也是只有function 0不能分配给VM使用,其余function都可以。
2.3 配置VM
QEMU/KVM搭建的VM使用vGPU的原理是GPU穿透,与物理GPU穿透配置是一样的,只是这里穿透的是vGPU而已。由于之前写过物理GPU穿透的文章,这里不再赘述,只提几个重点配置:
<domain type="kvm">
<name>win10</name>
...
<features>
...
<hyperv mode="custom">
...
<!--解决Code 43-->
<vendor_id state="on" value="Linux"/>
...
</hyperv>
...
</features>
...
<devices>
...
<!--穿透vGPU function1到VM-->
<hostdev mode="subsystem" type="pci" managed="yes">
<source>
<address domain="0x0000" bus="0x00" slot="0x02" function="0x1"/>
</source>
<address type="pci" domain="0x0000" bus="0x06" slot="0x00" function="0x0"/>
</hostdev>
...
</devices>
...
</domain>
接下来启动Windows VM,给vGPU装上驱动即可使用,装好驱动后的效果如下:
3 后续配置
3.1 配置Dummy Display
Dummy display最开始其实是一种“诱骗器”,形态一般是一个HDMI/DP的物理接口,将其插在GPU上可以让GPU以为接上了显示器,从而一直保持图像输出而不进入节能模式。本文说的dummy display则是一个软件模拟的显示器,使用效果和物理诱骗器一样。由于VM使用的是vGPU,显然无法将物理诱骗器“插”到它上面,因此只能使用dummy display软件。
关于dummy display软件,本文推荐IddSampleDriver。安装非常简单:
- 首先从release页获取最新版本,并解压;
- 将
option.txt
复制到C:\IddSampleDriver\option.txt
(只能是这个路径,不能更改); - 然后按照下图的指示进行安装即可(懒得翻译,见谅):
安装成功后,VM就能够使用这个dummy display了,在设备管理器的显示适配器中可以看到IddSampleDriver,在Windows的显示设置中也可以当作一个正常的显示器进行配置,效果如下图:
方才复制过去的C:\IddSampleDriver\option.txt
文件是一个配置文件,用于修改IddSampleDriver的分辨率和帧率,将想要的分辨率写入到该文件列表中即可在Windows VM的显示设置里设定为该分辨率,配置文件自带注释,规则简单,不言而喻,看图即可,不再赘述:
有网友测试称IddSampleDriver的3D性能较弱,但2D性能及视频加速都正常工作,这也是本人的主要使用场景,故用这个dummy display足矣。如果需要更好的3D性能,应该还有其他替代品可供选择,本文选择IddSampleDriver是由于其安装简单、方便演示。总之,你需要一个软件实现的dummy display让vGPU不进入睡眠状态,如此方可进行后续配置。
3.2 配置推流客户端
有了dummy display,VM的图像流就输出到了dummy display,怎么让dummy display的图像流能够在Host上显示,就是接下来要实现的目标。
3.2.1 Looking Glass
Looking Glass是一个基于共享内存、针对KVM工况优化的本地推流软件,采用Host/Client架构,需要在Host和VM上分别安装软件配合使用。 其官网给出的特性描述和配置要求如下:
配置方面基本上可以说是没什么要求,只要不是古早硬件都支持,都用上SR-IOV这种较新的技术了,支持情况不用担心。值得一提的是上图的Host和Guest与本文一直提到的Host和VM对应,而Looking Glass提供的两个应用——Host端应用和Client端应用,则刚好安装在相反的位置,即Host端应用安装在VM上,Client端应用安装在Host上,简便起见,下文用Host端和Client端表示。
Looking Glass的安装建议去看官方文档。由于是基于共享内存且对KVM进行过深度优化的软件,因此在安装之前需要先对VM的配置进行一些修改。重点要配置一个IVSHMEM设备,该设备用于为VM和Host之间共享内存,Looking Glass使用这些内存将Host端采集的画面同步到Client端,以完成画面在Host上的输出。至于要配置多少共享内存,则取决于需要使用的VM最高分辨率、色彩动态范围以及是否开启HDR等要素。这里把官方文档中的内存计算方法及典型的内存配置截出来,具体请以最新文档为准:
笔者的配置如下,仅供参考:
<domain>
...
<devices>
...
<memballoon model="none"/>
<shmem name="looking-glass">
<model type="ivshmem-plain"/>
<size unit="M">256</size>
<address type="pci" domain="0x0000" bus="0x10" slot="0x03" function="0x0"/>
</shmem>
...
</devices>
...
</domain>
共享内存需要为当前用户(使用VM的用户)配置访问权限,它对应的文件默认只能被QEMU这个用户访问,因此需要为目标用户授予读写权限才能使Looking Glass正常工作,配置方法参考文档:
此外,如果libvirt版本低于5.10.0,且又在使用AppArmor(Ubuntu默认使用)则需要在AppArmor中开放权限,让QEMU能够访问共享内存文件。并且Looking Glass建议将virtio默认开启的memballoon设备关闭,因其会影响VFIO穿透的性能。具体配置方式如下图,其中memballoon的样例配置笔者已在上面配置IVSHMEM的样例中给出:
笔者使用的libvirt版本高于5.10.0,故无需配置AppArmor,关于共享内存权限总体的参考配置如下:
其它的一些零星配置,例如为VM配置spice协议,Looking Glass以此实现一些高级功能之类就不再赘述,参考官方文档即可。
到这里Looking Glass对VM配置的修改就完成了,接下来只需要在VM上安装Host端,在Host上安装Client端即可使用Looking Glass了。值得注意的是Client需要自行编译,官方不提供现成的二进制包。编译非常简单,按照文档指示按部就班即可,不再赘述。Looking Glass的使用效果可以参考前文中两张VM的运行图,那是使用Looking Glass的Client端显示画面的效果。关于Host和Client端,还有很多使用上的技巧,如快捷键配置、配置文件和命令行参数等,具体请查阅官方文档。
3.2.2 Sunshine/Moonlight
Sunshine/Moonlight是一个网络推流软件,同样采用Host/Client架构,和Looking Glass一样,Host端安装在VM上,Client端安装在Host上。Looking Glass采用共享内存推流,设计目标是让Host上的Client端只接收本地VM上的Host端发送的图像流,不具备网络推流的功能;而Sunshine/Moonlight这套软件的应用场景主要是基于网络的推流,适合用于操作局域网中的高性能VM完成一些重负载的远程处理任务。当然Sunshine/Moonlight自然也可以用于本地VM的推流,只要Host和VM在网络上互通即可。
Sunshine/Moonlight的安装相当简单,它不是一个为KVM工况特别开发的软件,因此无需修改VM配置文件,只需在Host和VM上安装好,开箱即用(第一次连接有个配对过程,请参考官方文档)。这套软件用起来的感觉跟远程桌面差不多,但是它完全开源,且局域网延迟比远程桌面要低得多(因为Moonlight实际上是一个专注局域网游戏推流的客户端,注重时延的优化;而Sunshine又是一个自托管的Host端应用,没有中间服务器的网络时延开销)。关于安装仅需记住一点:Sunshine是Host端,Moonlight是Client端,不要安装错了位置。
软件安装/使用比较简单,参考官方文档即可,后文只展示使用效果。Moonlight会自动搜索局域网中的可连接设备,点击某个设备后可看到如下界面:
这里是该设备可以启动的所有应用连接,DESKTOP是连接后进入桌面,STEAM则是连接后打开steam,后续可自行添加应用,以便连接后快速启动这些应用。点击右上角的设置图标还可以对该设备的连接属性进行设置,进入设置界面:
在这里可以设置推流时视频的分辨率、帧率、码率以及一些其它的界面、操作相关选项,设置完成后回到上一级应用选择,选择一个应用进行连接,效果如图所示:
可以看出这是另一台VM,不是前文使用vGPU的VM。实际上这是一台部署在局域网服务器上的VM,直接将物理GPU进行穿透,CPU、内存和GPU性能都比前文的VM更强大。仅为演示Moonlight效果,故直接用了现成的VM,就不在笔记本本地搭建Sunshine/Moonlight环境进行测试了(局域网能推流成功,本地更是不在话下)。
使用Sunshine/Moonlight搭建局域网高性能Windows VM是一个比较的理想选择:作为一个Linux用户,由于垄断等因素无法100%放弃Windows环境,但通过在局域网搭建高性能Windows VM,让自己的Linux笔记本作为轻客户端,而将繁重的图形任务(例如玩3A游戏、进行视频渲染)提交给VM处理,不失为一种优雅的方案。至于说为什么不把服务器直接安装成Windows操作系统,这样也可以使用Sunshine/Moonlight进行推流,只能说笔者的服务器不仅要用来跑Windows VM,还托管了很多服务,其中大多数在Linux环境下使用更加稳定,还有不少甚至直接不支持Windows,因此Linux服务器就是最优选择。