本文介绍 WDDM 中的 GPU 半虚拟化。 此功能从 Windows 10 版本 1803(WDDM 2.4)开始提供。
关于 GPU 虚拟化
GPU 虚拟化是 Windows 客户端和 Windows Server 的重要功能。 有许多方案需要有效使用虚拟机中的 GPU 资源。
服务器方案(其中主机 OS 不运行用户应用程序)包括:
- 桌面虚拟化
- 计算(AI、ML 等)
客户端方案(其中主机 OS 在 VM 和用户应用程序之间共享 GPU)包括:
- 开发和测试图形应用程序(其中测试在 VM 中运行)
- 出于安全目的,在 VM 中运行应用程序
- 在具有 GPU 加速的 VM 中运行 Linux
WDDM 中的 GPU 半虚拟化
半虚拟化(PV)提供与其基础硬件类似的虚拟机(VM)的接口。 在 PV 中,在安装虚拟机之前要明确移植客户操作系统,因为非定制的客户操作系统无法在虚拟机监控器 (VMM) 上运行。
优势:
- 多个 VM 共享硬件资源。
- 驱动程序代码中需要进行少量更改。
下图描述了 WDDM 半虚拟化设计中涉及的各种组件。
来宾 VM 中的 D3D 运行时不会更改。 与用户模式运行时和 KMT 内核 Thunks 的接口保持不变。
驱动程序组件不需要进行许多更改:
来宾 VM 中的 UMD 需要:
- 请注意,与主机内核模式驱动程序(KMD)的通信在 VM 边界之间发生。
- 使用添加的 Dxgkrnl 服务访问注册表设置。
来宾中没有 KMD,只有 UMD。 虚拟渲染设备 (VRD) KMD 取代了 KMD。 VRD 的作用是方便 Dxgkrnl 的加载。
来宾中没有视频内存管理器 (VidMm) 或调度程序 (VidSch)。
VM 中的 Dxgkrnl 通过 VM 总线通道获取 thunk 调用并将其封送到主机分区。 来宾中的 Dxgkrnl 还在分配、进程、设备及其他资源方面创建本地对象,从而减少与主机的通信量。
虚拟渲染设备 (VRD)
VM 中不存在半虚拟化 GPU 时,VM 的设备管理器会显示“Microsoft Hyper-V 视频”适配器。 默认情况下,此显示专用适配器与 BasicRender 适配器配对,用于呈现。
将半虚拟化 GPU 添加到 VM 时,VM 的设备管理器会显示两个适配器:
- Microsoft Hyper-V 视频适配器或Microsoft远程显示适配器
- Microsoft虚拟呈现驱动程序(实际名称是主机上的 GPU 适配器的名称)
默认情况下,VRD 与 Hyper-V 视频适配器配对,因此所有 UI 呈现都与 VRD 适配器一起发生。
如果遇到呈现问题,可以使用 GpuVirtualizationFlags 注册表设置禁用此配对。 在这种情况下,仅呈现适配器 (VRD) 会在应用程序特别选择它时使用。 例如,某些 DirectX 示例允许更改呈现设备。 当应用程序决定使用它时,Direct3D 运行时会将逻辑显示输出添加到 VRD。
将多个虚拟 GPU 添加到 VM 时,客户机中可以有多个 VRD 适配器。 但是,只能将其中一个与 Hyper-V 视频适配器配对。 无法指定哪一个;OS 选择。
容器和虚拟机
VM 和容器支持 GPU 虚拟化。 容器是轻型 VM,其中主机 OS 二进制文件映射到容器 VM。
有关容器的详细信息,请参阅 Windows 和容器。
保护 VM
安全 VM 存在以下限制:
- 除了使用 DriverKnownEscape 标记的已知转义外,不允许调用驱动程序转义。
- 已启用 IoMmu 隔离。 如果驱动程序不支持 IoMmu 隔离,VM 创建会失败。
启用安全模式时:
- DxgkDdiSetVirtualMachineData 设置了 SecureVirtualMachine 标志。
- DxgkDdiQueryAdapterInfo 已设置 SecureVirtualMachine 标志。
有注册表设置可以强制安全模式或在开发期间禁用 IoMmu 隔离。 有关详细信息,请参阅 注册表设置。
VM/容器桌面的远程控制
可以使用两种方法将虚拟机(VM)中的桌面内容远程到主机:
- Hyper-V 显示适配器
- 终端会话远程控制
使用 RDP(远程桌面)连接到 VM 时,将使用终端会话远程处理。
Hyper-V 管理器使用 VMConnect 应用程序来显示 VM 桌面。 VMConnect 在两种模式下工作:
- 增强模式,它使用终端会话远程处理。
- 使用 Hyper-V 显示适配器的基本模式。
VM 工作进程和 VM 内存
启动 VM 或容器时,作系统会在主机上创建以下进程:
- VM 工作进程 (vmwp.exe)
- VM 内存进程(vmmem.exe)
Vmwp 包含各种虚拟设备驱动程序,包括 vrdumed.dll、用于半虚拟化图形适配器的驱动程序。
vmmem 进程虚拟地址空间是来宾中 vGPU IO 空间的后备空间。 当访客访问 IO 空间时,生成的物理地址是进入二级转换的入口,该转换使用 vmmem 进程的页表。
在虚拟化环境中,当虚拟机运行时,通常在主机上用户进程的上下文中运行的各种 KMD DDI 调用将在 vmmem 进程的上下文中执行。
Dxgkrnl 为这些进程创建单个 DXGPROCESS(以及相应的 KMD 进程对象),本文中称为 VM 工作进程。 与 DXG VM 工作进程相关的 EPROCESS 为 vmmem。
VM 进程
在来宾 VM 中创建 DXGPROCESS 时,Dxgkrnl 在主机上创建相应的 DXGPROCESS 对象。 本文中此过程称为 VM 进程。 与 DXGPROCESS 关联的 EPROCESS 是 vmmem。
来自 VM 或 VM 分配创建的所有渲染操作都是在 VM 的 DXGPROCESS 上下文中完成的。
出于调试目的,Dxgkrnl 通知 KMD 哪个进程是 vm 工作进程或 DxgkDdiCreateProcess中的 VM 进程。 使用此信息,驱动程序可以将 VM 进程链接到 VM 工作进程。 此信息有助于在多个 VM 运行时进行调试。
驱动程序要求
支持 GPU 半虚拟化的 KMD 需要设置 DXGK_VIDMMCAPS::ParavirtualizationSupported 功能。
用户模式驱动程序(UMD)不应在专用驱动程序数据(指针、句柄等)中使用任何与进程上下文相关的数据。 相反,KMD 在不同的进程上下文中获取主机中的专用数据。
来宾中的 UMD 无法与主机中的 KMD 共享内存。 它必须使用 UMD 中 注册表访问中所述的函数来访问注册表。
当前的半虚拟化实现使用 VM 总线在来宾与主机之间通信。 最大消息大小为 128KB。 目前,Dxgkrnl 不会将信息分块发送。 因此,驱动程序需要限制通过创建对象传递的专用数据的大小。 例如,当 Pfnd3dddiAllocatecb 用于创建多个分配时,总消息大小包括标头、全局专用数据,以及每个分配专用数据的大小乘以分配数。 所有这些信息都需要放入单个消息中。
在全屏模拟模式下运行应用程序
应启用间接显示适配器进行远程控制(默认已启用)。 若要禁用它,请执行以下步骤。
- 开始编辑组策略
- 导航到计算机配置 ->管理模板 ->Windows 组件 ->远程桌面服务 ->远程桌面会话主机 ->远程会话环境
- 打开“使用 WDDM 图形显示驱动程序进行远程桌面连接”项
- 选择“禁用”并选择“确定”
- 重新启动
默认情况下启用对 VM 中全屏应用程序的 DXGI 支持。 若要禁用它,请使用 StagingTool.exe /disable 19316777
。
全屏应用程序必须在模拟全屏模式下运行。
为所有 DXGI 应用程序启用 eFSE,并为交换效果转换为 WDDM 2.0 设置最低 WDDM 版本:
D3DEnableFeature.exe /enable DXGI_eFSE_Enablement_Policy
D3DEnableFeature.exe /setvariant DXGI_eFSE_Enablement_Policy 7
默认情况下,eFSE 为 D3D9 应用程序启用。
VM 中的 DriverStore
主机上的驱动程序二进制文件位于驱动程序存储 %windir%\system32\drivers\DriverStore\FileRepository<DriverDirectory>。
对于半虚拟化,VM 中的 UMD 二进制文件应位于 %windir%\system32\drivers\HostDriverStore\FileRepository<DriverDirectory>。
主机 KMD 会报告 UMD DLL 的名称,其中包含驱动程序存储空间的完整路径。 例如,c:\windows\system32\DriverStore\FileRepository\DriverSpecificDirectory\d3dumd.dll。
当 VM 请求 UMD 名称时,该名称将转换为 <VmSystemDrive>:\windows\system32\HostDriverStore\FileRepository\DriverSpecificDirectory\d3dumd.dll。
用于容器的主机 DriverStore
对于容器,Hyper-V 将主机中的完整主机驱动程序存储目录映射到容器中的 <%windir%\HostDriverStore。
用于完整 VM 的主机 DriverStore
当虚拟 GPU 适配器在 VM 中启动时,驱动程序存储文件将复制到 VM。 在作系统的已发布版本中禁用此功能。
以下注册表项和可用值控制复制操作。 默认情况下,密钥不存在。
DWORD RTL_REGISTRY_CONTROL\GraphicsDrivers\DriverStoreCopyMode
价值 | 描述 |
---|---|
0 | 禁用复制驱动程序存储 |
1 | 正常运行(启用复制驱动存储文件,不覆盖现有文件)。 |
2 | 启用复制驱动程序存储并覆盖现有文件。 |
从 UMD 访问注册表
KMD 注册表项存在于主机上,不会反映到 VM。 因此,UMD 无法直接读取此类驱动程序注册表项。 pfnQueryAdapterInfoCb2 回调将添加到 D3D 运行时的 D3DDDI_ADAPTERCALLBACKS 结构体中。 UMD 可以调用 pfnQueryAdapterInfoCb2 并设置 D3DDDICB_QUERYADAPTERINFO2 以读取某些注册表项:
- D3DDDICB_QUERYADAPTERINFO2::QueryType 设置为 D3DDDI_QUERYADAPTERTYPE_QUERYREGISTRY。
- pPrivateDriverData 指向具有返回注册表信息的 D3DDDI_QUERYREGISTRY_INFO 结构的缓冲区。 UMD 填补了以下成员的空缺:
- D3DDDI_QUERYREGISTRY_INFO::QueryType 指定注册表访问的类型;例如,服务密钥、适配器密钥或驱动程序存储路径。
- D3DDDI_QUERYREGISTRY_FLAGS::QueryFlags 指定查询的标志。
- ValueName 标识要读取的值的名称。
- ValueType 指定要读取的值的类型。
- PrivateDriverDataSize 是
sizeof(D3DDDI_QUERYREGISTRY_INFO)
加上动态大小输出值的缓冲区大小。
UMD 还可以直接调用 D3DKMTQueryAdapterInfo。 此调用对来宾中的 UMD 非常有用,因为它是向主机调用的,并提供了一种将某些名称转换为来宾名称空间的方法。
D3DKMTQueryAdapterInfo 被调用,D3DKMT_QUERYADAPTERINFO 被设置为如下,以读取某些注册表键:
- Type 设置为 KMTQAITYPE_QUERYREGISTRY
- pPrivateDriverData 指向 D3DKMT_ADAPTERREGISTRYINFO 结构体
- PrivateDriverDataSize 是
sizeof(D3DKMT_ADAPTERREGISTRYINFO)
加上动态大小输出值的缓冲区大小。
示例 1:从服务密钥读取值
WCHAR ValueName = L"EnableDebug";
D3DDDI_QUERYREGISTRY_INFO Args = {};
Args.QueryType = D3DDDI_QUERYREGISTRY_SERVICEKEY;
Args.QueryFlags.TranslatePath = FALSE or TRUE;
Args.ValueType = Supported registry value type;
wcscpy_s(Args.ValueName, ARRAYSIZE(Args.ValueName), ValueName);
D3DKMT_QUERYADAPTERINFO Args1 = {};
Args1.hAdapter = hAdapter;
Args1.Type = KMTQAITYPE_QUERYREGISTRY;
Args1.pPrivateDriverData = &Args;
Args1.PrivateDriverDataSize = sizeof(Args);
NTSTATUS Status = D3DKMTQueryAdapterInfo(&Args1);
if (NT_SUCCESS(Status) &&
Args.Status == D3DDDI_QUERYREGISTRY_STATUS_SUCCESS)
{
if (ValueType == REG_SZ || ValueType == REG_EXPAND_SZ) {
wprintf(L"Value: \"%s\"\n", Args.OutputString);
} else
if (ValueType == REG_MULTI_SZ) {
wprintf(L"Value: ");
for (UINT i = 0; i < Args.OutputValueSize; i++) {
if (Args.OutputString[i] == 0) {
wprintf(L" ");
} else {
wprintf(L"%c", Args.OutputString[i]);
}
}
wprintf(L"\n");
} else
if (ValueType == REG_DWORD) {
wprintf(L"Value: %d\n", Args.OutputDword);
} else
if (ValueType == REG_QWORD) {
wprintf(L"Value: 0x%I64x\n", Args.OutputQword);
} else
if (ValueType == REG_BINARY) {
wprintf(L"Num bytes: %d\n", Args.OutputValueSize);
for (UINT i = 0; i < Args.OutputValueSize; i++) {
wprintf(L"%d ", Args.OutputBinary[i]);
}
wprintf(L"\n");
}
}
示例 2:读取驱动程序存储路径
D3DDDI_QUERYREGISTRY_INFO Args = {};
Args.QueryType = D3DDDI_QUERYREGISTRY_DRIVERSTOREPATH;
D3DKMT_QUERYADAPTERINFO Args1 = {};
Args1.hAdapter = hAdapter;
Args1.Type = KMTQAITYPE_QUERYREGISTRY;
Args1.pPrivateDriverData = &Args;
Args1.PrivateDriverDataSize = sizeof(Args);
NTSTATUS Status = D3DKMTQueryAdapterInfo(&Args1);
if (NT_SUCCESS(Status) &&
Args.Status == D3DDDI_QUERYREGISTRY_STATUS_SUCCESS)
{
Args.OutputString holds the output NULL terminated string.
Args.OutputValueSize holds the number of characters in the string
}
将文件复制到 VM 中的 %windir%\system32 和 %windir%\syswow64
在某些情况下,驱动程序用户模式 DLL 需要存在于 %windir%\system32 和 %windir%\syswow64 目录中。
OS 提供了一种方法,让驱动程序指定应从主机中的驱动程序存储复制到来宾中 %windir%\system32 or %windir%\syswow64 的文件。
在安装 INF 中,驱动程序可以在图形适配器注册表项的以下子项中定义多个值:
- CopyToVmOverwrite
- CopyToVmWhenNewer
- CopyToVmOverwriteWow64
- CopyToVmWhenNewerWow64
CopyToVmOverwrite 和 CopyToVmWhenNewer 子项用于将文件复制到 %windir%\system32 目录。
CopyToVmOverwriteWow64 和 CopyToVmWhenNewerWow64 子项用于将文件复制到 %windir%\syswow64 目录。
CopyToVmOverwrite 和 CopyToVmOverwriteWow64 下的文件始终会覆盖目标中的文件。
CopyToVmWhenNewer 和 CopyToVmWhenNewerWow64 下的文件只有在文件更改日期较新的情况下才会覆盖目标中的文件。 “较新”条件比较了两条信息:
- FileVersion
- 最后写入时间
当目标文件以 .dll 或 .exe 后缀结尾时,FileVersion 将用作最重要的比较值,其中最大版本被视为“较新”。 如果目标文件不以 .dll 或 .exe 后缀结尾或两个 FileVersion 相等,则 LastWriteTime 将用作最不重要的比较值,其中较晚的日期/时间被视为“较新”。
子项下的每个数值类型都必须是 REG_MULTI_SZ 或 REG_SZ。 如果值类型REG_MULTI_SZ,则值中最多应有两个字符串。 此要求意味着每个值定义单个字符串或一对字符串,其中第二个字符串可以为空。
配对中的首个名称是驱动程序存储区中文件的路径。 路径相对于驱动程序存储的根目录,可以包含子目录。
对中的第二个名称是文件的名称,因为它应出现在 %windir%\system32
或 %windir%\syswow64
目录中。 第二个名称应只是文件名,不包括路径。
如果第二个名称为空,则文件名与驱动程序存储(不包括子目录)中的文件名相同。
这种方法允许驱动程序在主机驱动程序存储区和来宾中就会使用不同的名称。
示例 1
以下示例演示了如何让 OS 将 <DriverStorePath>\CopyToVm\softgpu1.dll
复制到 %windir%\system32\softgpu2.dll
。
INF [DDInstall] section
HKR,"softgpukmd\CopyToVmOverwrite",SoftGpuFiles,%REG_MULTI_SZ%,"CopyToVm\softgpu1.dll”, “softgpu2.dll”
The directive creates the registry key in the software (adapter) key:
"HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\<number>\CopyToVmOverwrite”, SoftGpuFiles = REG_MULTI_SZ, “CopyToVm\softgpu1.dll”, “softgpu2.dll"
示例 2
以下示例演示如何让 OS 将 <DriverStorePath>\softgpu1.dll
复制到 %windir%\system32\softgpu.dll
,并将 <DriverStorePath>\softgpu2.dll
复制到 %windir%\system32\softgpu2.dll
。
INF [DDInstall] section:
HKR,"CopyToVmOverwrite",SoftGpuFiles1,%REG_MULTI_SZ%,"softgpu1.dll”,”softgpu.dll"
HKR,"CopyToVmOverwrite",SoftGpuFiles2,%REG_SZ%, “softgpu2.dll"
The directive creates the registry key in the software (adapter) key:
“HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\<number>\CopyToVmOverwrite”, SoftGpuFiles1 = REG_MULTI_SZ, “softgpu1.dll”, “softgpu.dll"
“HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\<number>\CopyToVmOverwrite”, SoftGpuFiles2 = REG_SZ, “softgpu2.dll””
示例 3
以下示例演示如何让 OS 将 <DriverStorePath>\Subdir1\Subdir2\softgpu2wow64.dll
复制到 %windir%\syswow64\softgpu.dll
,以及将 <DriverStorePath>\softgpu.dll
复制到 %windir%\syswow64\softgpu2wow64.dll
。
INF [DDInstall] section:
HKR,"CopyToVmOverwriteWow64",SoftGpuFiles,%REG_MULTI_SZ%,“Subdir1\Subdir2\softgpu2wow64.dll”,”softgpu.dll”.
The directive creates the registry key in the software (adapter) key:
“HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\<number>\CopyToVmOverwriteWow64”, SoftGpuFiles = REG_MULTI_SZ, “Subdir1\Subdir2\softgpu2wow64.dll”,”softgpu.dll
对 DxgkDdiCreateProcess 的更改
需要更新 KMD DxgkDdiCreateProcess 函数以支持 VM 工作进程和 VM 进程。 以下字段已被添加到 DXGKARG_CREATEPROCESS 结构中:
- hKmdVmWorkerProcess
- ProcessNameLength
- pProcessName
在 DXGK_CREATEPROCESSFLAGS 中添加了以下标志,以支持 VM 工作进程和 VM 进程:
- VirtualMachineProcess
- VirtualMachineWorkerProcess
DxgkDdiSetVirtualMachineData
DxgkDdiSetVirtualMachineData 被添加到 Dxgkrnl,以便将有关虚拟机的信息传递给 KMD。
向主机发送异步 VM 总线消息
从来宾 OS 中的 Dxgkrnl 发送到主机的某些信息是异步的。 这种方法提高了来宾中高频 Dxgkrnl API 调用的性能。 向主机发送每条同步 VM 总线消息的开销可能会很高。
异步消息包括:
- D3DKMTSubmitCommand
- D3DKMTSubmitCommandToHardwareQueue
- D3DKMTSignalSynchronizationObjectFromGpu
- D3DKMTWaitForSynchronizationObjectFromGpu
GPU-PV 支持 LDA
GPU-PV 支持联动显示适配器(LDA)。 为确保一致的实现并支持将来可能将 LDA 支持反向移植到较旧的 Windows 版本,KMD 需要通过调用 DxgkCbIsFeatureEnabled(DXGK_FEATURE_LDA_GPUPV) 来检查 GPU-PV 中的 LDA 支持。 如果函数成功并返回“已启用”,则启用支持。 如果 KMD 不调用此回调,Dxgkrnl 假定 KMD 不支持 GPU-PV 中的 LDA。
如果 OS 支持该功能,则由驱动程序在用户模式下启用 LDA。 如果驱动程序在用户模式下启用 LDA,则应按如下所示执行此作。
运行时 | LDA 状态 |
---|---|
D3D12 之前的运行时 | 如果支持 DXGK_FEATURE_LDA_GPUPV,且来宾 OS 为 Windows 11 版本 22H2 (WDDM 3.1) 或更高版本,则启用。 |
非 DirectX 运行时 (Windows) | 如果支持 DXGK_FEATURE_LDA_GPUPV,且来宾 OS 为 Windows 11 版本 22H2 (WDDM 3.1) 或更高版本,则启用。 UMD 可以调用 D3DKMTQueryAdapterInfo(KMTQAITYPE_PHYSICALADAPTERCOUNT)并在返回大于 1 的物理适配器数时启用 LDA,而不是检查 OS 版本。 |
D3D12 运行时 (Windows) | 启用。 请参阅 D3D12 运行时的 LDA 状态设置。 |
Linux(d3d12 和非 DX 运行时) | 如果支持 DXGK_FEATURE_LDA_GPUPV,则启用。 |
使用小于 DXGKDDI_INTERFACE_VERSION_WDDM3_0 的接口版本编译的驱动程序不会检查 DXGK_FEATURE_LDA_GPUPV。 这些驱动程序仍可为 Linux 运行时启用 LDA。
为 D3D12 运行时设置 LDA 状态
为 D3D12 运行时启用或禁用 LDA 时,UMD 需要将正确的层和节点映射信息返回到运行时。 代码流如下所示:
D3D12 从 UMD 获取 D3D12_CROSS_NODE_SHARING_TIER 上限。
D3D12 通过调用 D3DKMTQueryAdapterInfo(KMTQAITYPE_PHYSICALADAPTERCOUNT)从 Dxgkrnl 获取物理适配器计数。
D3D12 调用 pfnQueryNodeMap(PhysicalAdapterCount, &map) 获取逻辑节点索引到物理节点的映射。 在这种情况下,节点表示物理适配器。 UMD 需要设置映射中的实际物理适配器索引或 D3D12DDI_NODE_MAP_HIDE_NODE 来禁用节点。
根据 pfnQueryNodeMap 结果,D3D12 通过不计算隐藏节点来计算有效的物理适配器计数。
如果层的状态与有效物理适配器数量不匹配,则 D3D12 将无法创建设备。 出现不匹配的情况是:
- 层级为 D3D12DDI_CROSS_NODE_SHARING_TIER_NOT_SUPPORTED 并且适配器数量大于 1。
- 该层不是 D3D12DDI_CROSS_NODE_SHARING_TIER_NOT_SUPPORTED 并且适配器数量为 1。
要禁用 LDA,UMD 需要返回 D3D12DDI_CROSS_NODE_SHARING_TIER_NOT_SUPPORTED 层,并在节点映射中只启用一个物理适配器。
D3DKMTQueryAdapterInfo(KMTQAITYPE_PHYSICALADAPTERCOUNT)
对物理适配器计数的 KMTQAITYPE_PHYSICALADAPTERCOUNT 查询始终会向来宾返回正确的物理适配器数量:
- 在 Windows 11 版本 22H2 之前的来宾上,它会返回 1。 此值已在访客程序代码中硬编码。 如果 LDA 支持移植到较旧的 OS 版本,将来可能会更改。
- 在 Windows 11 版本 22H2 及更高版本系统上,它将返回:
- 启用 DXGK_FEATURE_LDA_GPUPV 时的实际物理适配器数。
- 否则为 1。
半虚拟化启动
在 BIOS 中启用虚拟化支持(VT-d 或类似)。 对于 VMMS 虚拟机和容器,GPU-PV 设置不同。
在 PowerShell 中(以管理员身份运行),在服务器上启用脚本执行:
set-executionpolicy unrestricted
VMMS 虚拟机设置
设置主机和 VM
VM 中的 OS 内部版本可能比主机中的 OS 内部版本更早或更新。
在服务器角色启用 Hyper-V 功能或在客户端启用 Hyper-V 功能。 在服务器上启用此功能时,选择将网络适配器用作外部交换机的选项。
(可选)启用测试签名 (bcdedit -set TESTSIGNING ON)
重新启动。
安装支持准虚拟化的 GPU 驱动程序。
(可选)某些驱动程序未设置 ParavirtualizationSupported 上限。 在这种情况下,请在安装驱动程序之前添加以下注册表,或在设置标志后禁用/启用设备。
DWORD HKLM\System\CurrentControlSet\Control\GraphicsDrivers\GpuVirtualizationFlags = 1
若要检查 OS 是否识别到半虚拟化 GPU,请执行以下 PowerShell 命令:
Get-VMPartitionableGpu # Example output from running the command Name : \\?\PCI#VEN_10DE&DEV_1C02&SUBSYS_11C210DE&REV_A1#4&275d7527&0&0010#{064092b3-625e-43bf-9eb5-d c845897dd59}\GPUPARAV ValidPartitionCounts : {32} PartitionCount : 32 TotalVRAM : 1,000,000,000 AvailableVRAM : 1,000,000,000 MinPartitionVRAM : 0 MaxPartitionVRAM : 1,000,000,000 OptimalPartitionVRAM : 1,000,000,000 TotalEncode : 18,446,744,073,709,551,615 AvailableEncode : 18,446,744,073,709,551,615 MinPartitionEncode : 0 MaxPartitionEncode : 18,446,744,073,709,551,615 OptimalPartitionEncode : 18446744073709551615 TotalDecode : 1000000000 AvailableDecode : 1000000000 MinPartitionDecode : 0 MaxPartitionDecode : 1000000000 OptimalPartitionDecode : 1000000000 TotalCompute : 1000000000 AvailableCompute : 1000000000 MinPartitionCompute : 0 MaxPartitionCompute : 1000000000 OptimalPartitionCompute : 1000000000 CimSession : CimSession: . ComputerName : MYCOMPUTER-TEST2 IsDeleted : False
在 PowerShell 中运行以下命令,使用 GPU 创建 VM。 创建名为 TEST 的 VM。
$vm = “TEST“ New-VM -VMName $vm -Generation 2 Set-VM -GuestControlledCacheTypes $true -VMName $vm
为 VM 设置 IO 空间。 GPU-PV 使用 IO 空间来处理 CPU 可见的分配。 至少需要 8GB 的 IO 空间。
Set-VM -LowMemoryMappedIoSpace 1GB -VMName $vm Set-VM -HighMemoryMappedIoSpace 16GB -VMName $vm
[可选]默认情况下,高内存 IO 空间的基址设置为 (64GB - 512MB)。 在具有 36 位物理内存寻址的 Haswell 芯片集上,IO 空间区域的结束地址需要低于 64GB,因此需要相应地设置起始地址。 使用以下参数运行时,以下脚本名为 SetHighMmioBase.ps1,将起始地址设置为 47GB:
SetHightMmioBase.ps1 “TEST” 48128 # SetHighMmioBase.ps1 param( [string]$VmName, $BaseInMB) function Get-WMIVM { [CmdletBinding()] param( [parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VmName = "" ) gwmi -namespace root\virtualization\v2 -query "select * from Msvm_ComputerSystem where ElementName = '$VmName'" } function Get-WMIVmSettingData { [CmdletBinding()] param( [parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$VmName = "" ) $vm = Get-WMIVM $VmName return $vm.GetRelated ("Msvm_VirtualSystemSettingData","Msvm_SettingsDefineState",$null,$null, "SettingData", "ManagedElement", $false, $null) } Write-Host "Setting HighMmioGapBase to $BaseInMB for VmName $VmName" $vssd = Get-WMIVmSettingData $VmName $vmms = Get-WmiObject -Namespace "root\virtualization\v2" -Class Msvm_VirtualSystemManagementService $vssd.HighMmioGapBase = $BaseInMB $settingsText = $vssd.PSBase.GetText("CimDtd20") $ret=$vmms.ModifySystemSettings($settingsText).ReturnValue if ($ret -eq 0) { Write-Host "Successfully set" $vssd.HighMmioGapBase } else { Write-Host "Error $ret" }
将虚拟 GPU 添加到 VM 并禁用检查点。
Add-VMGpuPartitionAdapter -VMName $vm Set-VM -CheckpointType Disabled -VMName $vm
若要检查 VM 是否具有半虚拟化 GPU,请执行以下命令:
Get-VMGpuPartitionAdapter -VMName $vm in PowerShell. The output should show the adapter. # Example output from running the command MinPartitionVRAM : MaxPartitionVRAM : OptimalPartitionVRAM : MinPartitionEncode : MaxPartitionEncode : OptimalPartitionEncode : MinPartitionDecode : MaxPartitionDecode : OptimalPartitionDecode : MinPartitionCompute : MaxPartitionCompute : OptimalPartitionCompute : Name : GPU Partition Settings Id : Microsoft:9ABB95E2-D12D-43C3-B840-6F4A9CFB217B\929890BC-BB33-4687-BC1A-F72A4F1B3B3F VMId : 9abb95e2-d12d-43c3-b840-6f4a9cfb217b VMName : TEST VMSnapshotId : 00000000-0000-0000-0000-000000000000 VMSnapshotName : CimSession : CimSession: . ComputerName : MYCOMPUTER-TEST2 IsDeleted : False VMCheckpointId : 00000000-0000-0000-0000-000000000000 VMCheckpointName :
将 VM 中使用的同一客户端版本的 VHDX 复制到主机目录。 例如,
d:\VM\os.vhdx
。打开 Hyper-V 管理器并修改 VM 参数(选择 VM 并选择“设置”):
- 安全性 - 取消选中 启用安全启动。
- 内存 - 选中“启用动态内存”。 将内存量设置为 1,024 MB 或更多。
- 处理器 - 将 虚拟处理器数 设置为 2 或 4。
- 网络适配器 - 从下拉列表中选择要用于 VM 的网络适配器。 如果启用了网络调试,请确保选择Microsoft调试NET适配器。
- SCSI 控制器 - 硬盘驱动器 - 添加 - 虚拟硬盘 - 浏览 - 选择
d:\VM\os.vhdx
当在来宾中初始化适配器时,OS 会将文件从主机驱动程序存储复制到来宾中的 HostDriverStore 目录。
- 挂载 VM 的 VHDX。 例如,挂载到磁盘 f:。
- 在装载的 VM 中,创建名为 f:\%windir%\system32\HostDriverStore\FileRepository的目录。
- 将主机中的 %windir%\system32\DriverStore 驱动程序文件复制到 VM。 VM 中应有 f:\%windir%\system32\HostDriverStore\FileRepository\YourDriverDirectory\* 。
如果驱动程序需要从
%windir%\system32
或%windir%\syswow64
访问文件,请手动将文件复制到 VM。如果驱动程序没有经过 Microsoft 签名,请在 VM 中启用测试签名。 在 CMD 管理窗口中运行以下命令:
bcdedit /store <VM drive>:\EFI\Microsoft\Boot\BCD -set {bootmgr} testsigning on
卸载 VM 的 VHDX。
启动 VM。
使用“Hyper-V 管理器连接”选项连接到 VM。
在 VM 中
检查 VM 的设备管理器中是否存在虚拟呈现设备。 VM 内的所有呈现都通过虚拟 GPU。
用于设置 VM 的 PowerShell 脚本
以下 PowerShell 脚本是如何从头开始设置 VM 的示例。 修改它以满足你的需求。
Param(
[string]$VMName,
[string]$VHDPath,
[string]$SwitchName,
[switch]$CreateVm,
[switch]$InitDebug,
[switch]$CopyRegistry,
[switch]$CopyDriverStore,
[switch]$CreateSwitch,
[switch]$AddGpu,
[switch]$All
)
if($All)
{
$CreateVm = $True
$CreateInitDebug = $True
$CopyRegistry = $True
$CopyDriverStore = $True
$CreateSwitch = $True
$AddGpu = $True
$InitDebug = $True
}
$vm = $VMName
#
# Validate parameters
#
if ($CreateSwitch -or $CreateVM)
{
if ($SwitchName -eq "")
{
write "SwitchName is not set"
exit
}
}
if ($AddGpu -or $CreateVM)
{
if ($VMName -eq "")
{
write "VMName is not set"
exit
}
}
if ($InitDebug -or $CreateVM -or $CopyDriverStore -or $CopyRegistry)
{
if ($VHDPath -eq "")
{
write "VHDPath is not set"
exit
}
}
enable-windowsoptionalfeature -FeatureName Microsoft-Hyper-V-All -online
#
# Create a network switch for the VM
#
if ($CreateSwitch)
{
New-VMSwitch $SwitchName -NetAdapterName "Ethernet (Kernel Debugger)"
}
#
# Create a VM and assign VHD to it
#
if ($CreateVm)
{
New-VM -VMName $vm -Generation 2
Set-VM -GuestControlledCacheTypes $true -VMName $vm
Set-VM -LowMemoryMappedIoSpace 1Gb -VMName $vm
Set-VM -HighMemoryMappedIoSpace 32GB -VMName $vm
Set-VMProcessor -VMname $vm -count 4
Set-VMMemory -VMName $vm -DynamicMemoryEnabled $true -MinimumBytes 1024MB -MaximumBytes 4096MB -StartupBytes 1024MB -Buffer 20
Add-VMHardDiskDrive -VMName $vm -Path $VHDPath
Connect-VMNetworkAdapter -VMName $vm -Name "Network Adapter" -SwitchName $SwitchName
Set-VMFirmware -VMName $vm -EnableSecureBoot off
Set-VMFirmware -VMName $vm -FirstBootDevice (Get-VMHardDiskDrive -VMName $vm)
}
#
# Enable debugger and testsiging
#
if ($InitDebug)
```powershell
{
Mount-vhd $VHDPath
Add-PartitionAccessPath -DiskNumber (Get-DiskImage -ImagePath $VHDPath | Get-Disk).Number -PartitionNumber 1 -AssignDriveLetter
$efidrive = (Get-DiskImage -ImagePath $VHDPath | Get-Disk | Get-Partition -PartitionNumber 1).DriveLetter
bcdedit /store ${efidrive}:\EFI\Microsoft\Boot\BCD -set '{bootmgr}' testsigning on
bcdedit /store ${efidrive}:\EFI\Microsoft\Boot\BCD -set '{default}' debug on
bcdedit /store ${efidrive}:\EFI\Microsoft\Boot\BCD /dbgsettings net port:50052 key:a.b.c.d hostip:10.131.18.133
Dismount-VHD $VHDPath
}
#
# Now boot the VM without vGPU to verify that it's initialized correctly
# If everything is OK, turn off the VM
#
if ($CreateVm)
{
Write-Output "Boot the VM and turn it OFF after it's initialized"
pause
}
#
# Add virtual GPU
#
if($AddGpu)
{
Add-VMGpuPartitionAdapter -VMName $vm
Get-VMGpuPartitionAdapter -VMName $vm
}
#
# Copy the driver store to the VM
#
if ($CopyDriverStore)
{
Write "Copying driver store"
Mount-vhd $VHDPath
$drive = (Get-DiskImage -ImagePath $VHDPath | Get-Disk | Get-Partition -PartitionNumber 3).DriveLetter
xcopy /s $Env:windir\system32\driverstore\* ${drive}:\windows\system32\hostdriverstore\
Dismount-VHD $VHDPath
}
#
# Export driver registry settings
#
if ($CopyRegistry)
{
Write "Copying registry"
Mount-vhd $VHDPath
$drive = (Get-DiskImage -ImagePath $VHDPath | Get-Disk | Get-Partition -PartitionNumber 3).DriveLetter
reg load HKLM\VMSettings ${drive}:\Windows\System32\config\SYSTEM
reg copy "HKLM\System\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000" "HKLM\VmSettings\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000" /s /f
reg unload "HKLM\VmSettings"
Dismount-VHD $VHDPath
}
调试 VM
在常规客户端计算机上配置 VM 调试器的方式与网络调试相同。
如果 VM 未启动或看到黑屏:
使用以下命令关闭 VM 并从中删除虚拟 GPU:
$vm = “TEST“ remove-VMGpuPartitionAdapter -VMName $vm -AdapterId “<Id from Get-VMGpuPartitionAdapter>”
例如:
remove-VMGpuPartitionAdapter -VMName $vm -AdapterId “Microsoft:9ABB95E2-D12D-43C3-B840-6F4A9CFB217B\929890BC-BB33-4687-BC1A-F72A4F1B3B3F”
启动 VM。 如果成功启动,请确保驱动程序文件正确复制到 VM 中的 HostDriverStore。
使用
Add-VMGpuPartitionAdapter
命令将 vGPU 添加到 VM。再次启动 VM。
有关其他信息,请参阅 故障排除。
容器设置
容器(也称为 主机计算系统(HCS)VM 与完整 VM 之间的区别在于 OS 二进制文件和驱动程序存储文件映射到容器。 因此,无需将驱动程序文件复制到容器,除非在 windows\system32
目录中需要这些文件。
对于安全容器:
- 已禁用驱动程序转义。
- 驱动程序必须支持在安全容器内启用 IOMMU 隔离。
更新主机上的驱动程序并启动或停止主机 GPU 时,更改将反映在容器中。
Windows 沙盒
此容器类型用于尝试有风险的应用程序。 完整的桌面映像被远程传输到主机。 间接显示驱动程序用于远程控制。 不使用图形 VAIL,因此将桌面图像引入主机速度较慢。
默认情况下,虚拟 GPU 在 Windows 沙盒中处于禁用状态。 若要启用它,请创建 WSB 配置文件(例如,config.wsb),并设置虚拟 GPU 选项。 通过单击配置文件启动沙盒。
配置文件示例:
<Configuration>
<VGpu>Enable</VGpu>
</Configuration>
默认情况下,容器中的 vGPU 禁用了驱动程序转义。 有一个配置选项可以启用驱动程序转义。 下面的 WSB 文件示例同时启用了沙盒中的 vGPU 和驱动程序转义:
<Configuration>
<VGpu>EnableVendorExtensions</VGpu>
</Configuration>
Windows 沙盒支持 GPU 适配器“热插即用”。
本地集成的虚拟应用程序 (VAIL) 容器
使用此容器类型可在基于 WCOS(Windows Core作系统)的主机内运行 Win32 应用程序。 容器中每个应用程序的映像都远程到主机。 启用图形 VAIL 可远程控制每个应用程序交换链。 已启用驱动程序转义。
常见容器要求
计算机要求如下:
- Vtx 和 Vtd 都必须在 BIOS 中启用(或其等效项:AMD-V、AMD-IOMMU)。
- 至少 8 GB RAM。
- 超过 5 GB 的系统磁盘空间。
为 Windows 沙盒设置内核调试器
使用 CMDIAG
一个容器管理器服务(cmservice)负责管理 Hyper-V 个独立隔离的容器。 CMDIAG.EXE 是安装 Hyper-V 和容器功能时可用的应用程序。 它为容器启用内核模式调试、启用测试签名等。
容器管理器支持串行和 NET 调试。
运行 cmdiag.exe Debug
以查看选项。
CMDIAG 修改容器基础映像中的调试器设置。 启用内核调试器时,只应有一个容器实例运行。
更改调试器设置之前,请停止 HVSICS 服务。
# Example 1:
C:\Windows\system32>sc stop hvsics
SERVICE_NAME: HVSICS
TYPE : 30 WIN32
STATE : 3 STOP_PENDING
(STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x1
WAIT_HINT : 0xbb8
C:\Windows\system32>cmdiag debug -on -Serial -Force
Debugging successfully enabled. Connection string: -k com:pipe,port=\\.\pipe\debugpipe,reconnect -v
# Example 2:
C:\Windows\system32>cmdiag debug -on -net -port 51000 -key a.b.c.d -hostip 10.131.18.34
在不同的计算机上运行调试器
使用串行调试器时,可能需要在不同的计算机上运行它。 使用 kdsrv.exe
在不同的计算机上运行调试器。 如需更多信息,请参阅 KD 连接服务器。
要在内核调试期间禁用超时,请设置以下注册表项:
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\UtilityVm" /v BridgeTransactionTimeout /t REG_DWORD /d 0xffffffff /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\UtilityVm" /v BridgeServerConnectTimeout /t REG_DWORD /d 0xffffffff /f
reg add "HKLM\SOFTWARE\Microsoft\HVSI" /f /v DisableResetContainer /t REG_DWORD /d 1
reg add "HKLM\SOFTWARE\Microsoft\HVSI" /f /v AppLaunchTimeoutInSeconds /t REG_DWORD /d 0x7fffffff
reg add "HKLM\Software\Microsoft\Terminal Server Client" /f /v ConnectionHealthMonitoringSupported /t REG_DWORD /d 0
reg add "HKLM\Software\Microsoft\Terminal Server Client" /f /v DisableUDPTransport /t REG_DWORD /d 1
reg add "HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client" /f /v ConnectionHealthMonitoringSupported /t REG_DWORD /d 0
reg add "HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client" /f /v DisableUDPTransport /t REG_DWORD /d 1
为 VAIL 容器设置内核调试器
- 使用 telnet 连接到主机。 可以从主机 OS 中的网络设置获取主机的 IP 地址。
- 使用
cmdiag.exe
配置调试器。
设置管理程序调试器
bcdedit /hypervisorsettings NET port:50000 key:a.b.c.d hostip:1.1.1.1
bcdedit /set {hypervisorsettings} hypervisorbusparams 0.0.0 (if needed)
bcdedit /set hypervisordebug on
reboot host
故障 排除
本部分介绍如何排查 GPU-PV 问题。
Get-VMHostPartitionableGpu
调用 Get-VMHostPartitionableGpu 以查看是否存在虚拟化 GPU。 如果输出为空,则会出现错误(驱动程序未设置虚拟化上限、未启用虚拟化等)。
Get-VMHostPartitionableGpu
# Example output from running the command
Name : \\?\PCI#VEN_10DE&DEV_1188&SUBSYS_095B10DE&REV_A1#6&cfd27c8&0&00400008#{064092b3-625e-43bf-9eb5-dc845897dd59}\PARAV
ValidPartitionCounts : {32, 4}
PartitionCount : 32
TotalVRAM : 2,000,000
AvailableVRAM : 1,800,000
MinPartitionVRAM : 100,000
MaxPartitionVRAM : 1,000,000
OptimalPartitionVRAM : 1,000,000
TotalEncode : 20
AvailableEncode : 20
MinPartitionEncode : 1
MaxPartitionEncode : 5
OptimalPartitionEncode : 4
TotalDecode : 40
AvailableDecode : 30
MinPartitionDecode : 2
MaxPartitionDecode : 20
OptimalPartitionDecode : 15
TotalCompute : 100
AvailableCompute : 100
MinPartitionCompute : 1
MaxPartitionCompute : 50
OptimalPartitionCompute : 30
CimSession : CimSession: .
ComputerName : WIN-T3H0LVHJJ59
IsDeleted : False
使用 ETW 事件
Dxgkrnl 具有用于 ETW 事件的管理和操作通道。 事件显示在 Windows 事件查看器中:应用程序和服务日志 - Microsoft - Windows - Dxgkrnl。
事件查看器包含来自其他组件的事件,这些组件通过 GPU-PV(Hyper-V-Compute、Hyper-V-Worker、Hyper-V-VID 等)参与 VM 的创建。
使用 Add-VMGpuPartitionAdapter
在使用 Add-VMGpuPartitionAdapter 时,如果不需要某项功能(例如解码),请不要指定该功能。 不要对此功能使用 0。
使用 Remove-VMGpuPartitionAdapter
如果 VM 无法启动或出现渲染问题,请尝试使用 Remove-VMGpuPartitionAdapter删除其虚拟 GPU。
remove-VMGpuPartitionAdapter -VMName $vm -AdapterId "Microsoft:9ABB95E2-D12D-43C3-B840-6F4A9CFB217B\929890BC-BB33-4687-BC1A-F72A4F1B3B3F"
防止 VM 在启动期间启动
set-vm -AutomaticStartAction Nothing -VmName TEST
事件查看器事件
将事件添加到事件查看器通道,以帮助识别 vGPU 启动问题。 可以在“应用程序和服务日志\Microsoft\Windows\Dxgkrnl”中找到事件。 事件通道是管理通道和运行通道。
在下列情况下会发出事件:
- vGPU 已创建
- vGPU 已销毁
- 来宾打开虚拟适配器
事件文件位于:
- c:\Windows\System32\winevt\Logs\Microsoft-Windows-DxgKrnl-Admin.evtx
- c:\Windows\System32\winevt\Logs\Microsoft-Windows-DxgKrnl-Operational.evtx
请检查 vGPU 是否已创建,以及是否有任何错误。
注册表设置
GPU虚拟化标志
GpuVirtualizationFlags 注册表项用于设置半虚拟化 GPU 的行为。 密钥位于:
DWORD HKLM\System\CurrentControlSet\Control\GraphicsDrivers\GpuVirtualizationFlags
定义了以下位:
位 | 描述 |
---|---|
0x1 | 为所有硬件适配器强制设置 ParavirtualizationSupported 上限。 在主机中使用该位。 |
0x2 | 强制为 BasicRender 设置 ParavirtualizationSupported 上限。 在主机中使用该位。 |
0x4 | 强制安全虚拟机模式,其中所有虚拟机都将被视为安全。 在此模式下,用户模式驱动程序存在限制。 例如,驱动程序无法使用 Escape 调用,因此它们会失败。 在主机中使用该位。 |
0x8 | 启用准虚拟化适配器与仅显示适配器配对。 在客用虚拟机中使用此比特。 默认情况下启用配对。 |
GuestIoSpaceSizeInMb
GuestIoSpaceSizeInMb 注册表项用于设置虚拟 GPU 的来宾 IO 空间的大小(以兆字节为单位)。 默认值为 1,000MB (1GB)。 密钥位于:
DWORD HKLM\System\CurrentControlSet\Control\GraphicsDrivers\Paravirtualization\GuestIoSpaceSizeInMb
来宾 IO 空间目前实现了 CPU 可见分配。 主机中 CPU 可见的分配后备存储被固定在内存中,并映射到来宾 IO 空间。 在来宾中,分配的用户模式虚拟地址被映射到 IO 空间区域。 在某些 Haswell 系统上,CPU 具有 36 位物理地址。 此类系统上的 Hyper-V 具有有限的 IO 空间大小。
为安全虚拟机禁用 IOMMU 隔离
如果驱动程序不支持 IoMmu 隔离,请在开发过程中使用以下注册表设置来禁用 IoMmu 隔离。
`DWORD HKLM\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\IoMmuFlags = 8`
限制虚拟函数的数量
默认情况下,支持 GPU 半虚拟化的适配器公开的虚拟函数数为 32。 此数字表示可将适配器添加到 32 个虚拟机,假设每个 VM 都有一个适配器。
可以使用以下注册表设置来限制公开的虚拟函数数。
DWORD HKLM\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\NumVirtualFunctions
例如,如果将 NumVirtualFunctions
设置为 1,则适配器一次只能添加到一个 GPU。 如果计算机有多个支持 GPU-PV 的 GPU 适配器,并且想要将每个适配器分配给 VM,则此设置非常有用。 Add-VMGpuPartitionAdapter
不允许指定要添加的适配器。 因此,如果将两个适配器添加到 VM,两个适配器都可以从主机获取相同的 GPU-PV 适配器。
WDDM 2.4 DDI 更新
以下 DDI 更新用于支持 WDDM 2.4 中的 GPU 半虚拟化。
已添加 DXGK_VIDMMCAPS 上限
ParavirtualizationSupported 功能已被添加到 DXGK_VIDMMCAPS 结构体中。 如果主机 KMD 实现本部分中所述的所有 DDI,则设置此上限。
通过 DDI 传递的驱动程序专用数据
UMD 使用各种 DDI 与相应的 KMD 交换专用信息。 当 UMD 在来宾 VM 中运行时,相应的 KMD DDI 调用在主机分区中发生。 因此,UMD:
- 无法在私有数据中传递任何指针。
- 无法在私有数据中传递任何句柄。
- 不应指示 KMD 对 GPU 状态进行全局更改,因为此更改可能会影响其他正在运行的 VM。
为 DxgkDdiCreateProcess 添加了 VirtualMachineProcess 标志
OS 为每个正在运行的 VM 创建 VM 工作进程。 Dxgkrnl 创建相应的 DXGPROCESS,并在设置 VirtualMachineWorkerProcess 标志后调用 DxgkDdiCreateProcess。 在此进程上下文中不会创建渲染或驱动程序资源。 因此,驱动程序可能会跳过分配某些资源。
OS 在主机中为使用 GPU 的来宾 VM 中的每个进程创建 DXGPROCESS。 Dxgkrnl 调用 DxgkDdiCreateProcess 并设置了 VirtualMachineProcess 标志。 每个 VM DXG 进程都属于与 VM 工作进程相同的 EPROCESS。
DxgkDdiQueryAdapterInfo 更新
DXGKARG_QUERYADAPTERINFO 结构已更新,以包括以下字段以支持半虚拟化:
添加了 Flags 成员,允许 Dxgkrnl 指示以下内容:
- 它将 VirtualMachineData 设置为指示调用来自 VM。
- 它将 SecureVirtualMachine 设置为表示 VM 在安全模式下运行。
添加了 hKmdProcessHandle,这允许驱动程序在处理源自来宾 VM 的查询时识别和使用主机端的正确进程上下文。
DxgkDdiEscape 更新
hKmdProcessHandle 成员被添加到 DXGKARG_ESCAPE 结构中,以允许驱动程序在处理来自来宾虚拟机的转义时,识别并使用主机端的正确进程上下文。
VirtualMachineData 标志被添加到 D3DDDI_ESCAPEFLAGS 结构中,以指示 DxgkDdiEscape 是从虚拟机调用的。
物理访问 GPU 分配
目前,驱动程序不会实现对分配的物理访问。 驱动程序必须支持 GpuMmu。
WDDM 2.5 DDI 更新
对于 WDDM 2.5,为了支持半虚拟化,还需要进行以下 DDI 更改。
由 KMD 主机发出来宾事件信号
当 KMD 需要对 UMD 创建的事件发出信号时,会出现一些没有虚拟化的情况。 若要在使用半虚拟化时处理此类情况,主机上的 KMD 需要向客户机中创建的事件发送信号。 为此添加了 DxgkCbSignalEvent 回调。 KMD 还可以使用此回调来发出主机进程的事件信号。
支持虚拟机中 UMD 提供的句柄
某些驱动程序回调接受 UMD 传递的 Dxgkrnl 分配或资源句柄,例如:
主机上的调用必须在调用 DxgkDdiXxx 函数的同一线程上下文中进行。
例如,假设在没有虚拟化的情况下,KMD 在调用 D3DKMTEscape 的用户模式线程的上下文中调用 DxgkCbAcquireHandleData,该线程调用 DxgkDdiEscape。
UMD 在虚拟机中运行时,它只知道来宾分配句柄,并且无法将此类句柄传递给 KMD,因为 KMD 在主机中运行。 来宾中的 UMD 会调用 D3DKMTEscape,而主机中的 KMD 会接收相应的 DxgkDdiEscape 调用。 KMD 需要在此线程的上下文中调用 DxgkCbAcquireHandleData。
为了能将来宾分配/资源句柄转换为相应的主机句柄,添加了 D3DDDI_ESCAPEFLAGS::DriverKnownEscape 驱动程序转义标志。
在调用 D3DKMTEscape 并设置 DriverKnownEscape 标记时:
将 D3DKMT_ESCAPE::Type 设置为 D3DKMT_ESCAPE_DRIVERPRIVATE。
将 D3DKMT_ESCAPE::pPrivateDriverData 设置为指向已知的驱动程序转义结构,该结构在下一节中定义。 每个结构都以 D3DDDI_DRIVERESCAPETYPE 值开头。
如果未使用虚拟化,则转换后的句柄与输入句柄相同。
定义了以下已知的驱动程序转义字符。
以下代码片段演示如何使用 DriverKnownEscape 标志。
D3DDDI_DRIVERESCAPE_TRANSLATEALLOCATIONEHANDLE Command = {};
Command.EscapeType = D3DDDI_DRIVERESCAPETYPE_TRANSLATEALLOCATIONHANDLE;
Command.hAllocation = hAlloc;
D3DKMT_ESCAPE Args = {};
Args.hAdapter = hAdapter;
Args.Flags.DriverKnownEscape = TRUE;
Args.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
Args.pPrivateDriverData = &Command;
Args.PrivateDriverDataSize = sizeof(Command);
Status = D3DKMTEscape(&Args);
WDDM 2.6 DDI 更新
从 WDDM 2.6(Windows 10 版本 1903 开始),为半虚拟化支持进行了以下更新:
驱动程序可以在虚拟机中使用 DXGK_ALLOCATIONINFOFLAGS::ACCESSEDPHYSICALLY 标志。 在 WDDM 2.6 之前,驱动程序无法在虚拟机中使用此标志,并且此标志的分配创建失败。
UMD 可以在虚拟机中使用 Pfnd3dkmtUpdateallocationproperty。 在 WDDM 2.6 之前,此调用将失败。