RFC-0205:Vulkan 加载程序 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 介绍应用如何加载 Vulkan ICD 和层 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-01-12 |
审核日期(年-月-日) | 2023-01-12 |
摘要
本文档介绍了 Fuchsia 上的软件如何加载 Vulkan ICD 和层以执行硬件加速渲染。
本文档中介绍的系统已基本实现,但本文档可能包含我们未来打算对架构进行的更改。
设计初衷
Vulkan API 采用 C 语言风格的接口,应用可使用该接口对 GPU 进行编程。Vulkan 应用可以通过多种方式使用 Vulkan 加载程序与 Vulkan 函数交互,如以下文档所述:与 Vulkan 函数交互。
在本文档中,“应用”用于表示使用 Vulkan API 的软件组件。
Vulkan 加载程序还负责加载可安装的客户端驱动程序 (ICD) 和 Vulkan 层,以及委托对 magma 或代表 ICD 执行 GPU 命令所需的其他 API 的访问权限。
Vulkan ICD 是特定于供应商的共享库,会加载到应用中,以便应用能够使用 GPU 进行渲染。使用 Vulkan 的应用需要一种机制来识别和加载系统中硬件的正确 ICD。
Vulkan 层是共享库,可通过增强 Vulkan API 调用的调度链来修改或观察 Vulkan API 的行为。它们可用于增强 Vulkan 的功能,或代表 Vulkan 调试或性能分析功能插入 API。
利益相关方
教员:
rlb@
Reviewers:
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 加载
启动时,libvulkan.so
会连接到 fuchsia.vulkan.loader.Loader 协议。此通道在应用生命周期内必须保持连接状态。如果它退出,所有后续的加载器调用都可能会失败。
此长期连接可防止组件框架在使用 Vulkan 的客户端运行时重新加载或更新加载器。这是可取的,因为它可以防止在应用使用加载器 API 调用时 Vulkan ICD 和加载器接口的版本发生意外更改。用于枚举扩展程序或其他实例属性的一些 Vulkan 入口点不接受任何类型的上下文参数;因此,实现将具有一些隐式全局状态。
ICD 使用 fuchsia.vulkan.loader.Loader
协议加载。加载器使用 fuchsia.vulkan.loader/Loader.ConnectToManifestFs
方法访问包含描述所有相关 ICD 的清单 JSON 文件的文件系统;此文件系统与 Linux 上的 /usr/local/share/vulkan/icd.d
文件系统相同;如需了解该文件系统的详细信息,请参阅文件系统传送。
然后,加载器将使用 fuchsia.vulkan.loader/Loader.Get
方法检索与 ICD 对应的 VMO,并将其 dlopen_vmo
加载到进程中,以便从中获取 ICD 入口点。Fuchsia 上的一组 Vulkan 入口点与 Linux 上的入口点相同,但 Fuchsia 专用扩展除外(如下所述)。
客户端组件还可以与软件 ICD 实现(例如 SwiftShader)打包在一起。对于 SwiftShader,VK_ICD_FILENAMES
环境变量可用于指定 ICD 的 manifest.json
路径。ICD 共享库将从 Vulkan 客户端组件的 /pkg/lib
加载。
由于大多数 ICD 都不会存储在软件包中,并且版本与应用二进制文件分开,因此它们只能对所关联应用的 ABI 做出有限的假设。它们可以依赖的确切接口列在 Fuchsia 系统接口中,但通常它们只能使用有限的符号列表,这些符号必须全部来自 libc.so
或 libzircon.so
。构建 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.LogSink
或 fuchsia.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
会向客户端提供多个文件系统,包括清单文件系统和设备文件系统。它会根据通过 devfs 接收的多个 ICD 软件包和服务的内容创建这些文件系统。因此,必须使用文件系统服务库构建它们,并且不会反映磁盘上的任何内容。
- 清单文件系统:描述所有相关 ICD 的所有清单 JSON 文件;此文件系统与 Linux 上的 /usr/local/share/vulkan/icd.d 相同,因此只需对加载器进行最少的更改。
- device fs:包含受支持的 Vulkan ICD 所需的所有 GPU 设备。对于
/dev/<path>/<node>
设备,文件系统将包含<path>/<node>
条目。
ICD↔︎loader 接口
ICD 会作为 CFv2 组件提供给加载器。ICD 组件必须公开一个 contents
目录,其中包含包含共享库的任意目录树,以及一个包含单个 metadata.json
文件的 metadata
目录。
ICD 通常单独包含在一个单独的软件包中。在这种情况下,contents
目录将是软件包的根目录,metadata
目录将是软件包中的 meta/metadata/
目录。不过,加载器不会强制执行此布局。
metadata.json
和 manifest.json
最好存储在软件包中的 meta
目录下,因为该目录在存储小文件方面效率最高。
ICD 共享库
ICD 共享库应与 Vulkan ICD ABI 一致。ICD 是可执行的共享库,可放置在软件包的大多数子目录(而非 /bin
)中。
组件清单
Vulkan 加载程序提供 icd_runner
runner,以简化从软件包创建 ICD 组件的过程。ICD 软件包必须包含用于导出 contents
和 metadata
目录功能的组件清单 .cml
。
icd_runner
会自动将 ICD 软件包中的 /pkg/data
和 /pkg/meta/metadata
目录导出到 /pkg-data
和 /pkg-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_path
是相对于公开的contents
目录的 Khronos ICD 清单 JSON 文件的位置。
其他客户端
可用的 Vulkan ICD 集可能会随时间而变化;在系统首次启动时,在硬件枚举之前,将不会有任何 ICD 可用。之后,设备可能会热插拔,并出现或消失。
这意味着,vkEnumeratePhysicalDevices
返回的设备列表随时都可能发生变化。某些需要 Vulkan 的应用可能希望在可用设备集合发生变化后重试。它们可以对从 fuchsia.vulkan.loader/Loader.ConnectToManifestFs
返回的文件系统使用文件系统监视器,以确定何时重试。
实现
此设计代表已在 Fuchsia 上实现的 Vulkan 加载器的当前架构。
性能
Vulkan 加载器在进程启动时最活跃。Vulkan ICD 加载后,它会跳转 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
实现可以根据客户端的安全要求路由到客户端组件。目前,没有任何商品有此要求。
加载器的配置可能会导致应用中出现意外行为,例如加载新图层、阻止加载其他图层或在这些图层上设置选项。组件必须选择从其软件包之外获取配置(通过从软件包之外路由目录功能),但在其他方面可以完全控制加载器配置。
ICD 共享库在客户端进程中执行,并且可以在该进程中执行任意代码。构建流程和一致性测试将确保它们仅导入许可名单中的符号,但这并不能保证安全,并且可能会很容易被绕过,例如查看调用堆栈以查找地址,以及解析内存中的可执行文件以查找实用的 gadget。应用不会验证 Vulkan 返回的大多数值,并且可能会通过精心操纵这些值而被操纵执行任意内存访问。
如果运行程序将多个不受信任的组件加载到单个进程中(可能通过使用虚拟机或其他非进程机制来隔离组件),则这些组件不得能够进行直接 Vulkan 调用,因为没有已知的方法可以验证 Vulkan API 调用,以保证应用不会在 Vulkan ICD 中执行未定义的行为;即使 Vulkan 验证层也只能提供有限的保护。Runner 代码可能会调用自身的 Vulkan,例如使用 Skia 或 ANGLE 代表客户端执行经过验证的渲染命令。向 ICD 提供的服务和设备通道必须来自运行程序信任的某个来源,以防止子组件相互窥探。
隐私注意事项
Vulkan 加载器对隐私的影响微乎其微。通过 FIDL 公开的唯一信息是应用是否尝试使用 Vulkan,以及它尝试使用哪些设备。
测试
vulkan_loader
和 libvulkan.so 具有单元测试和集成测试。这些测试是密封的,不依赖于系统上安装的设备驱动程序或真实 ICD。
此外,还有 CTF 测试,用于确保 fuchsia.vulkan.loader.Loader
协议的实现正确无误,以及它提供的 ICD 与旧版加载器兼容。
Vulkan CTS 和 Fuchsia 树中的其他 Vulkan 测试可用作端到端测试,用于检查 vulkan_loader
是否与 libvulkan.so
兼容。这些应用只能在具有 Vulkan 硬件和设备驱动程序的系统上运行。
文档
我们在 /src/graphics/bin/vulkan_loader/README.md 中提供了 vulkan_loader
文档。我们还提供了一些用户文档,介绍了如何使用 Vulkan 加载程序。
上游 Vulkan 加载器提供了文档。我们应尝试向该文档添加 Fuchsia 专用信息并将其上游。