RFC-0205:Vulkan 加载器

RFC-0205:Vulkan 加载器
状态已接受
领域
  • 图形
说明

介绍应用如何加载 Vulkan ICD 和层

问题
  • 87685
Gerrit 更改
  • 739772
作者
审核人
提交日期(年-月-日)2023-01-12
审核日期(年-月-日)2023-01-12

总结

此 RFC 说明了 Fuchsia 上的软件如何加载 Vulkan ICD以执行硬件加速渲染。

本文档中的系统大部分已经实现,但本文档可能会包含我们计划在将来对架构进行的更改。

设计初衷

Vulkan API 具有 C 样式的接口,应用可使用该接口对 GPU 进行编程。Vulkan 应用使用 Vulkan 加载程序与 Vulkan 函数进行交互,如与 Vulkan 函数进行交互中所述。

在本文档中,“应用”用于表示使用 Vulkan API 的软件组件。

Vulkan 加载程序还负责加载可安装客户端驱动程序 (ICD) 和 Vulkan 层,以及委托对代表 ICD 执行 GPU 命令所需的 magma 或其他 API 的访问权限。

Vulkan ICD 是特定于供应商的共享库,这些库会加载到应用中以使应用能够使用 GPU 进行渲染。使用 Vulkan 的应用需要一种机制来为系统中的硬件识别和加载正确的 ICD。

Vulkan 层是共享库,可通过增强 Vulkan API 调用的调度链来修改或观察 Vulkan API 的行为。它们可用于增强 Vulkan 的功能,或代表 Vulkan 调试或性能分析功能插入 API。

利益相关方

教员

@

审核者

cstout@ costan@ jhowarth@ msandy@ rosasco@ palmer@ wittrock@

咨询人员

社交

设计已由 Magma 团队的成员审核。此文档的早期版本已分享给组件框架团队并已在安全团队咨询交流时间活动。

设计

在 Fuchsia 上,Vulkan 加载程序分为两部分:加载到应用中的 libvulkan.so 共享库,以及负责加载 ICD VMO 并将其传输到 libvulkan.so 的加载器服务 (vulkan_loader)。它们使用 fuchsia.vulkan.loader.Loader 协议进行通信。

libvulkan.so

Khronos 是 Vulkan API 的标准正文。它们提供了可在 Linux、Windows、macOS 和大多数其他平台上使用的加载器共享库实现。Google 编写了一个适用于 Android 的单独加载器。

Fuchsia 加载器基于 Khronos 的实现;代码位于 Fuchsia 代码库的 third_party/Vulkan-Loader 中,但最终将全部传送到上游。当应用调用 vkCreateInstance 或其他枚举函数时,该加载器会读取环境变量和 JSON 配置文件以确定要使用的 ICD 和层集。层是从组件的命名空间加载的,因此它们通常存储在软件包内。这些目录还可以从提供给组件的目录功能中加载,前提是加载程序配置已设为使用这些目录。

ICD 加载

Vulkan 加载程序启动流程

启动时,libvulkan.so 会连接到 fuchsia.vulkan.loader.Loader 协议。此频道必须在应用的整个生命周期内保持连接状态。如果退出,则将来的所有加载器调用都可能会失败。

这种长期有效的连接可防止组件框架在使用 Vulkan 的客户端运行时重新加载或更新加载器。这种设置是可取的,因为它可以防止在应用使用加载器 API 调用时发生意外更改 Vulkan ICD 和加载器接口。某些用于枚举扩展或其他实例属性的 Vulkan 入口点不接受任何类型的 context 参数;因此,实现将具有一些隐式全局状态。

Vulkan 加载程序流程

ICD 使用 fuchsia.vulkan.loader.Loader 协议加载。该加载器使用 fuchsia.vulkan.loader/Loader.ConnectToManifestFs 方法访问包含描述所有相关 ID 的清单 JSON 文件的文件系统;此文件系统与 Linux 上的 /usr/local/share/vulkan/icd.d 文件系统相同;如需详细了解该文件系统,请参阅文件系统服务

然后,加载器将使用 fuchsia.vulkan.loader/Loader.Get 方法获取与 ICD 对应的 VMO,它会通过 dlopen_vmo 将其加载到进程中并从中获取 ICD 入口点。Fuchsia 上的 Vulkan 入口点集与 Linux 上的相同,但包含特定于 Fuchsia 的扩展程序,如下所述。

客户端组件还可能打包了 SwiftShader 等软件 ICD 实现。对于 SwiftShader,VK_ICD_FILENAMES 环境变量可用于指定 ICD 的 manifest.json 的路径。系统将从 Vulkan 客户端组件的 /pkg/lib 加载 ICD 共享库。

由于大多数 ICD 并不存储在软件包中,并且版本与应用二进制文件是分开的,因此它们只能对链接到的应用的 ABI 做出有限的假设。Fuchsia 系统接口中列出了它们可以依赖的确切接口,但通常它们只能使用有限的符号列表,这些符号必须全部来自 libc.solibzircon.so。构建 ICD 时,会根据许可名单验证导入的符号,以确保可针对多个版本的客户端应用加载 ICD。将来,随着封闭替换的推出,此许可名单可能会缩小。

ICD 需要能够连接到外部协议;具体来说,ICD 必须连接到与硬件进行通信的底层设备驱动程序。他们可能还希望读取特定于供应商的配置文件以及日志错误。libc.so 会导出多个符号来执行 I/O,但实际上,底层操作(如 open)是在 libfdio 中实现的。此外,如果没有直接从 libfdio 导出的其他符号,则无法使用文件系统连接 Zircon 通道。

为了允许 ICD 执行有限的 I/O,我们在 Vulkan ICD API 中添加了以下定义:

VkResult(VKAPI_PTR* PFN_vkOpenInNamespaceAddr)(const char* pName, uint32_t handle);
VKAPI_ATTR void VKAPI_CALL vk_icdInitializeOpenInNamespaceCallback(PFN_vkOpenInNamespaceAddr
open_in_namespace_addr);

ICD 应公开 vk_icdInitializeOpenInNamespaceCallback。在调用任何其他驱动程序函数之前,系统将使用 open_in_namespace_addr 回调调用此函数。ICD 可以将文件名和 Zircon 通道客户端端传递给此回调,以通过名称连接到文件系统节点。

此函数可以访问进程的传入命名空间,因此 ICD 可以读取配置文件或连接到 fuchsia.logger.LogSinkfuchsia.tracing.provider.Registry 等服务。Vulkan ICD 可能包含全局状态,因此,如果进程是可以托管多个子组件的运行程序(可能通过使用虚拟机或其他非进程机制来隔离组件),该运行程序必须确保提供给 ICD 的服务可以安全地从任何子组件使用。例如,如果有多个不受信任的子组件同时位于进程中,则运行程序不应通过运行程序不信任的子组件路由 fuchsia.tracing.provider.Registry,因为该组件可能会窥探所有 ICD 图形活动。

open_in_namespace_addr 回调用于访问 /loader-gpu-devices 路径的特殊情况。对该路径的所有访问都会使用 fuchsia.vulkan.loader/Loader.ConnectToDeviceFs 方法路由到从 vulkan_loader 提供的文件系统;这样,ICD 就可以连接到所需的任何硬件专用设备驱动程序节点。ICD 可以使用 zxio 或原始 FIDL 遍历文件系统;如需详细了解该文件系统,请参阅文件系统传送

层通常通过 SDK 分发,并从与应用相同的软件包中加载,因此它们可以依赖于 SDK 中任何软件的相同 ABI 保证。就 ABI 而言,通过目录功能从外部软件包加载的层应被视为与 ICD 相同。

ICD 卸载/重新加载

目前无法卸载共享库,因此任何 ICD 都将在进程生命周期内保持加载状态。为避免在创建新的 Vulkan 实例时出现内存膨胀,该加载器会保留其看到过的所有 ICD(通过共享库文件名标识)的无到期缓存。只要 vulkan_loader 连接处于活动状态,此文件名就是唯一的。

加载器执行环境

Vulkan API 没有异步运行循环的概念,因此从应用的角度来看,函数调用必须同步完成。该加载器不会从应用收到 async_dispatcher_t*,并且无法使用 libasync-default.so 中的默认调度程序。它可以在内部创建自己的调度程序和线程。

组件的传出目录由应用代码托管,因此加载器无法在其中放入条目。这限制了它与其他组件交互的方式。虽然目前所有应用都使用 libvulkan.so 的副本,但该副本在加载时因其 Soname 而被去重,但不要求应用仅加载加载器的单个副本。

默认情况下,该加载器会在 /vulkan-loader-configuration 中搜索配置文件,回退到 /pkg/data。这些路径可以被环境变量或替换层替换,这与在 Linux 上相同。

vulkan_loader

vulkan_loader 是一项服务,负责确定可用的 ICD、加载这些 ICD 并将其提供给应用。该测试托管在 /core/vulkan_loader 上,其公开的 fuchsia.vulkan.loader.Loader 服务会路由到会话、测试框架和多个应用。它采用 C++ 编写,代码位于 //src/graphics/bin/vulkan_loader,相关文档位于 /src/graphics/bin/vulkan_loader/README.md

将来,此服务可能会以 Rust 重写,以降低安全风险并利用异步编程功能。

识别新设备

vulkan_loader 服务必须能够确定哪些 ICD 可用。这是由一组正在运行的设备驱动程序驱动的。如果设备驱动程序未针对硬件运行,则与其关联的 ICD 将无法使用。

vulkan_loader/dev/class/goldfish-pipe 和 /dev/class/gpu 上使用目录观察器来确定何时出现新的图形设备。

当出现新的图形设备时,该加载器必须确定与 ICD 相关联的组件。具体机制取决于设备的类型:

  • /dev/class/gpu - 在设备上调用 fuchsia.gpu.magma/Device.GetIcdList
  • /dev/class/goldfish-pipe:ICD 网址硬编码为 fuchsia-pkg://fuchsia.com/libvulkan_goldfish#meta/vulkan.cm

未来可能会支持更多类型的 GPU 硬件设备。软件 ICD 还可能在某些设备上通过加载器协议公开作为回退(由 vulkan_loader 配置选择)。软件 Vulkan ICD(例如 SwiftShader)通常具有 JIT,并且需要向可执行内存写入数据;正因如此,它们可能无法在出于安全考虑而必须严格控制这种功能的生产系统上使用。

文件系统服务

vulkan_loader 为客户端提供多个文件系统,包括清单 fs设备 fs。它会根据通过 devfs 接收的多个 ICD 软件包和服务的内容创建这些文件系统。因此,它们必须使用文件系统服务库进行构建,并且不会反映磁盘上的任何内容。

  • manifest fs:描述所有相关 ICD 的所有清单 JSON 文件;该文件系统与 Linux 上的 /usr/local/share/vulkan/icd.d 相同,因此只需对加载器进行极少的更改。
  • device fs:包含受支持的 Vulkan ICD 所需的所有 GPU 设备。对于 /dev/<path>/<node> 设备,文件系统将包含一个 <path>/<node> 条目。

ICD®︎ 加载器界面

ICD 以 CFv2 组件的形式提供给加载器。ICD 组件必须公开一个包含共享库的任意目录树的 contents 目录,以及包含单个 metadata.json 文件的 metadata 目录。

ICD 通常单独包含在单独的文件包中。在这种情况下,contents 目录将是软件包的根目录,metadata 目录将是软件包中的 meta/metadata/ 目录。不过,加载器不会强制执行此布局。

理想情况下,metadata.jsonmanifest.json 应存储在软件包中的 meta 目录下,因为该目录在存储小文件时是最高效的。

ICD 共享库

ICD 共享库应与 Vulkan ICD ABI 匹配。ICD 是可执行的共享库,可放置在软件包的大多数子目录(而非 /bin)中。

组件清单

Vulkan 加载程序提供一个 icd_runner 运行程序,用于简化通过软件包创建 ICD 组件的过程。ICD 软件包必须包含用于导出 contentsmetadata 目录功能的组件清单 .cml

icd_runner 会自动从 ICD 软件包的 /pkg-data/pkg-metadata 路径中导出 /pkg/data/pkg/meta/metadata 目录。CML 可以使用这些内容来导出这两种目录功能(使用 subdir 属性将子目录公开为完整功能)。

ICD 组件也可以使用 ELF 运行程序,但唯一可用的服务是 fuchsia.logger.LogSink

metadata.json

metadata.json 是单个 JSON 文件,用于向加载器描述 ICD。 示例:

{
    "file_path": "lib/libvulkan_example.so",
    "version": 1,
    "manifest_path": "meta/icd.d/libvulkan_example.json"
}
  • 对于此元数据版本,version 必须为 1。
  • file_path 是 ICD 共享库相对于公开的 contents 目录的位置。
  • manifest_pathKhronos ICD 清单 JSON 文件相对于公开的 contents 目录的位置。

其他客户端

可用的 Vulkan ICD 集合可能会随时间而变化;系统首次启动时,在硬件枚举之前,将没有可用的 ICD。此后,设备可能会被热插拔,并显示或消失。

这意味着,vkEnumeratePhysicalDevices 返回的设备列表可能随时发生更改。某些需要 Vulkan 的应用可能需要在可用设备集发生更改后重试。它们可以在从 fuchsia.vulkan.loader/Loader.ConnectToManifestFs 返回的文件系统上使用文件系统观察器,以确定何时重试。

实现

此设计代表了已在 Fuchsia 上实现的 Vulkan 加载程序的当前架构。

性能

Vulkan 加载程序在进程启动时处于最活跃状态。Vulkan ICD 加载完成后,它会执行 trampoline Vulkan 调用以进入 ICD,或者将 ICD 函数实现返回给应用,以供应用直接调用。因此,其性能仅在进程启动期间才至关重要。

没有特别考虑加载器的性能。它必须启动组件以连接到 ICD,并遍历多个文件系统路径以得出 ICD 和层配置。目前还不认为它对运行时性能有很大的影响。

向后兼容性

libvulkan.so 与 vulkan_loader 之间的通信使用文件系统、JSON 和 FIDL。文件系统和 JSON 已在 Linux 上使用几年,并且不存在向后兼容性问题。您可以自然地改进它们(分别添加路径和键)以保持向后兼容性。FIDL 接口很小,可以使用 FIDL 版本控制机制来改进。

安全注意事项

组件将加载由 Vulkan 加载程序提供的共享库。系统的常规验证执行强制执行将确保可执行共享库来自可信位置(例如文件系统)。任何父组件都可以插入 fuchsia.vulkan.loader.Loader 协议,因此无法保证加载器服务组件看到的是由系统提供的。

选择加载的 ICD 由 Magma 系统驱动程序 (MSD) 中的路径引用,并通过解析器加载。默认情况下,系统会使用完整的解析器,以便加载临时软件包。从临时软件包加载 ICD 对 ICD 的开发者来说很有用,但对大多数用户来说应该不是必要的。您可以通过停用完整的解析器(设置 auto_update_packages=false gn 参数)来停用加载临时软件包的功能。我们还可以为 Vulkan 加载器创建多个核心分片,产品所有者可以从中选择;eng build 可以选择使用完整解析器的分片,而用户 build 可以将该分片与基本解析器搭配使用。

如果需要特定产品,可以创建多个 vulkan_loader 服务实例,每个实例都可以访问不同的解析器。根据客户端的安全要求,其 fuchsia.vulkan.loader.Loader 实现可以路由到客户端组件。目前,没有任何产品有此要求。

配置加载器可能会导致应用出现意外行为,具体原因如下:加载新层、阻止加载其他层或在这些层上设置选项。组件必须选择从其软件包外部获取其配置(通过从软件包外部路由目录 capability),但能够完全控制加载器配置。

ICD 共享库在客户端进程中执行,并且可以在该进程中执行任意代码。构建流程和一致性测试将确保它们仅导入列入许可名单的符号,但这不是安全保证,并且很容易被绕过,例如查看调用堆栈来查找地址和解析内存中的可执行文件以找到有用的小工具。应用不会验证 Vulkan 返回的大多数值,并且可能会通过对这些值的谨慎操作来操纵应用,以执行任意内存访问。

如果运行程序将它不信任的多个组件加载到单个进程中(或许通过使用虚拟机或其他非进程机制来隔离组件),这些组件就不能直接进行 Vulkan 调用,因为没有已知的方式可以验证 Vulkan API 调用来保证应用不会在 Vulkan ICD 中执行未定义的行为;即使是 Vulkan 验证层也仅提供有限的保护。运行程序代码可能会自行调用 Vulkan,例如,使用 Skia 或 ANGLE 代表客户端执行经过验证的渲染命令。提供给 ID 的服务和设备通道必须来自运行程序信任的某个来源,以防止子组件相互窥探。

隐私注意事项

Vulkan 加载器对隐私的影响极小。通过 FIDL 公开的唯一信息是应用是否尝试使用 Vulkan,以及应用尝试使用哪些设备。

测试

vulkan_loader 和 libvulkan.so 具有单元测试和集成测试。这些测试是封闭的,不依赖于设备驱动程序或系统上安装的实际 ICD。

此外,我们还会进行 CTF 测试,以确保 fuchsia.vulkan.loader.Loader 协议的实现正确无误,且该协议提供的 ICD 与旧版加载器兼容。

紫红色树中的 Vulkan CTS 和其他 Vulkan 测试是端到端测试,用于检查 vulkan_loader 是否与 libvulkan.so 兼容。这些测试只能在包含 Vulkan 硬件和设备驱动程序的系统上运行。

文档

我们的 vulkan_loader 文档位于 /src/graphics/bin/vulkan_loader/README.md。有关如何使用 Vulkan 加载程序,提供了一些用户文档

上游 Vulkan 加载器具有相关文档。我们应尝试向该文档添加特定于 Fuchsia 的信息并向上游添加。

早期技术和参考资料

Linux/Windows/MacOS 加载程序