vgpu_unlock:解除消费级英伟达GPU的vGPU功能限制

Unlock vGPU functionality for consumer grade GPUs.

分支1Tags0
文件最后提交记录最后更新时间
5 年前
5 年前
5 年前
5 年前
5 年前
5 年前
5 年前
5 年前
5 年前

解锁 vGPU 功能

针对消费级英伟达显卡开启 vGPU 功能。

注意事项!

此工具并不保证在所有情况下都能立即正常工作, 请自行承担风险使用。

描述

该工具使得 GeForce 和 Quadro 显卡能够利用 NVIDIA vGPU 图形虚拟化技术。通常,NVIDIA vGPU 设计上仅支持少数数据中心特斯拉和专业 Quadro 显卡,而不支持消费级图形卡(通过软件限制)。这个 vgpu_unlock 工具旨在消除基于 Linux 系统上的这种限制,从而使得大多数 Maxwell、Pascal、Volta(未测试)以及 Turing 架构的显卡可以使用 vGPU 技术。Ampere架构的支持尚在开发中。

由 Krutav Shah 维护的社区 Wiki 提供了更多信息,可在此处查看。

依赖项:

  • 此工具需要 Python3 及 Python3-pip;建议安装最新版本。
  • 需要 Python 包“frida”,通过运行 pip3 install frida 安装。
  • 必须安装 NVIDIA GRID vGPU 驱动程序。
  • 需要“dkms”,因为它简化了重新构建驱动程序的过程。根据操作系统中的包管理器来安装 DKMS。

安装步骤:

以下指令中的 <path_to_vgpu_unlock> 应替换为目标系统上此仓库的路径,而 <version> 则需替换为 NVIDIA GRID vGPU 驱动程序的版本号。

首先,以 dkms 模块的形式安装 NVIDIA GRID vGPU 驱动:

./nvidia-installer --dkms

修改 /lib/systemd/system/nvidia-vgpud.service 和 /lib/systemd/system/nvidia-vgpu-mgr.service 文件中以 ExecStart= 开始的行,将其可执行文件更改为 vgpu_unlock 并将原始可执行文件作为第一个参数。示例:

ExecStart=<path_to_vgpu_unlock>/vgpu_unlock /usr/bin/nvidia-vgpud

随后,重载 systemd 守护进程:

systemctl daemon-reload

修改 /usr/src/nvidia-<version>/nvidia/os-interface.c 文件,在文件开头的 #include 行之后添加以下代码:

#include "<path_to_vgpu_unlock>/vgpu_unlock_hooks.c"

接着,修改 /usr/src/nvidia-<version>/nvidia/nvidia.Kbuild 文件,并在其底部添加以下行:

ldflags-y += -T <path_to_vgpu_unlock>/kern.ld

使用 dkms 卸载 nvidia 内核模块:

dkms remove -m nvidia -v <version> --all

然后,使用 dkms 重建并重新安装 nvidia 内核模块:

dkms install -m nvidia -v <version>

重启计算机。


提示

此脚本仅适用于与其专业版 Tesla 对应的同代显卡。因此,只支持 Maxwell 及其以后的英伟达 GPU。它不设计用于低端显卡模型,因此并非所有卡片都能与 vGPU 流畅配合。为了获得最佳体验,推荐使用与 Tesla 卡片具有相同芯片模型的显卡。操作系统的适用性也是如此,某些最新的 Linux 发行版可能不完全兼容 vGPU 软件。


工作原理

vGPU 支持检查?

为了判断特定 GPU 是否支持 vGPU 功能,驱动会检查 PCI 设备 ID。这一标识符连同 PCI 厂商 ID 对于每种 PCI 设备都是唯一的。为了让 vGPU 支持生效,我们需要告诉驱动,已安装 GPU 的 PCI 设备 ID 是 vGPU 兼容 GPU 使用的设备 ID 之一。

用户空间脚本:vgpu_unlock

用户空间服务 nvidia-vgpud 和 nvidia-vgpu-mgr 通过 ioctl 系统调用与内核模块通信。它们会读取 PCI 设备 ID 以确定安装的 GPU 是否为 vGPU 兼容。

python 脚本 vgpu_unlock 会拦截指定为第一个参数的可执行文件与内核之间的所有 ioctl 系统调用。然后修改内核响应,表明 PCI 设备 ID 支持 vGPU 并具备 vGPU 功能的 GPU。

内核模块钩子:vgpu_unlock_hooks.c

为了与 GPU 交换数据,内核模块使用 ioremap* 内核函数将 PCI 总线的物理地址空间映射到自己的虚拟地址空间。然后使用 memcpy 内核函数在该映射地址空间中读写数据。

通过在 os-interface.c 文件中包含 vgpu_unlock_hooks.c 文件,我们可以使用 C 预处理器宏来替换和拦截对 ioremap 和 memcpy 函数的调用。这使我们能跟踪何处进行了映射以及访问了哪些数据。

内核模块链接脚本:kern.ld

这是 gcc 提供的默认链接脚本的修改版。脚本被修改为将 nv-kernel.o 中的 .rodata 段放入 .data 而不是 .rodata,使其可写。同时提供符号 vgpu_unlock_nv_kern_rodata_beg 和 vgpu_unlock_nv_kern_rodata_end 来指示该段的开始和结束位置。

如何协同工作

开机后,nvidia-vgpud 服务查询内核以获取所有已安装的 GPU 并检查 vGPU 功能。此调用由 vgpu_unlock python 脚本拦截,从而使 GPU 成为 vGPU 兼容。若找到 vGPU 兼容 GPU,则 nvidia-vgpu 创建一个 MDEV 设备,系统创建 /sys/class/mdev_bus 目录。

现在可以通过在 mdev 总线表示形式的 create 文件中回显 UUID 来创建 vGPU 设备。这将在 MDEV 总线上创建新的 vGPU 设备结构,这些设备随后可以分配给虚拟机。当虚拟机启动时,会打开 MDEV 设备。这就触发 nvidia-vgpu-mgr 使用 ioctl 与内核通信。这些调用再次由 vgpu_unlock python 脚本拦截,当 nvidia-vgpu-mgr 查询 GPU 是否支持 vGPU 时,回答会被修改为“是”。之后,尝试初始化 vGPU 设备实例。

vGPU 设备的初始化由内核模块处理,它会执行自己的 vGPU 功能检查,稍微复杂一些。

内核模块会映射物理 PCI 地址范围 0xf0000000-0xf1000000 到其虚拟地址空间,然后进行一些未知具体作用的操作。我们知道,在这些操作之后,它会在物理地址 0xf0029624 处访问一个 128 位值,称为魔术值,还会在物理地址 0xf0029634 处访问另一个 128 位值,称为密钥值。

内核模块有两张魔术值查找表,一张是 vGPU 兼容 GPU 的,另一张是非兼容 GPU 的。所以,内核模块会在这两张表中寻找魔术值,如果找到,表条目中还包含一组 AES-128 加密数据块和一个 HMAC-SHA256 签名。

然后,使用前面提到的密钥值计算加密数据块的 HMAC-SHA256 签名,如果签名正确,则使用 AES-128 和同一密钥解密块。

解密后的数据中又会出现一次 PCI 设备 ID。

为了使内核模块接受 GPU 为 vGPU 兼容,魔术值必须存在于 vGPU 兼容魔术值的表中,密钥生成的有效 HMAC-SHA256 签名,以及 AES-128 解密的数据块中包含一个 vGPU 兼容的 PCI 设备 ID。如果有任何检查失败,就会返回错误码 0x56,“调用不受支持”。

vgpu_unlock_hooks.c 中的钩子会查找映射包含魔术值和密钥值的物理地址范围,重新计算这些值在内核模块虚拟地址空间中的地址,监视在这些地址上执行的 memcpy 操作,如果发生此类操作,则保存这些值,直到两个都已知,定位 nv-kernel.o 中的 .rodata 段,找到签名和数据块,验证签名,解密块,编辑解密数据中的 PCI 设备 ID,重新加密块,重新生成签名,并将魔术值、块和签名插入 vGPU 兼容魔术值的表中。这就是它们的工作方式。

项目介绍

为消费级显卡解锁虚拟GPU功能。【此简介由AI生成】

定制我的领域
1125.31 K472访问 GitHub