RFC-0167:早期用户空间引导中的软件包

RFC-0167:早期用户空间引导中的软件包
状态已接受
领域
  • 组件框架
说明

引入了 BootFS 和启动解析软件包。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-05-10
审核日期(年-月-日)2022-06-13

摘要

此 RFC 会提议在 bootfs 中引入软件包。这样一来, 具有软件包隔离和命名空间的优势,这些优势让后期用户模式一直都受益于早期用户模式 启动,并消除第三方驱动程序开发障碍。

设计初衷

在 组件框架拥有坚实的打包架构,但迄今为止我们还没有 致力于将现在可供后期用户空间使用的工具引入到早期启动阶段。 因此,用户空间引导发现自己面临着几个问题, 引入包装来解决这个问题例如进程沙盒、可验证 内容和变量库版本

这些前期启动问题表现为意想不到的复杂性和低效 与 bootfs 映像的交互时, 这在很大程度上是通过在前期启动过程中采用打包技术来消除的。打包 为改善系统运行状况、 不仅仅是解决现有问题。针对以下各项实现内容标识符标准化: 可执行文件和库,这使得我们可以重复使用 之前不相交的存储空间(如 bootfs 和 blobfs)中的等效数据。

组件框架有一个“package”这个概念就是 fuchsia-pkg;而组件框架和打包系统 严格来说,它们是相互独立的,它们紧密相关,足以使 世界的组件化和打包是合作的目标。

近期工作(例如,组件管理器作为首次用户启动后执行) 都会导致越来越多的“组件一直向下”架构。 甚至包括文件系统和设备驱动程序等提前启动可执行文件, Fuchsia 组件,或者正向这一模型迁移。时机已到 将系统的这种组件与 "packages-all-the-way-down"系统组装,并将所有值 促使打包产品进行用户空间引导。

更具体地说,bootfs 中缺少软件包名称 是树外驱动程序开发的障碍,因为它无法 生成有效的 fuchsia 映像,其中在 bootfs 中编码的可执行文件 共享库依赖项中存在版本偏差。此外,目前的 bootfs 映像出现泄露 ABI。驱动程序可以在启动时根据是否存在或 而不需要其他驱动程序,例如选择使用较新的库, 可以做到这一点,而无需明确定义对另一个驱动程序的依赖。 同样,由于驱动程序最终都在 bootfs 内的同一文件夹中, 驱动程序的名称本身会创建一个 ABI。这些类型的 ABI 的可用时间越长,弃用起来就越难。 正是此类行为(Windows 驱动程序和视频游戏)的先例 DRM 驱动程序会公开这种类型的意外 ABI)。

利益相关方

Facilitator:hjfreyer@google.com

审核人:geb@google.com、mcgrathr@google.com、surajmalhotra@google.com、 aaronwood@google.com、galbanum@google.com、wittrock@google.com、 jfsulliv@google.com

已咨询

社交化:通过设计文档与利益相关方一起探索了主题, 之后在 RFC 之前向 tq-eng 开放了一般讨论。

设计

此更改涉及到 bootfs 和 /boot 目录、 BootResolver、BootUrl 和产品组件。

bootfs 变更

图片更改

fuchsia bootfs 软件包将由 bootfs 中的 Metal.far 文件表示, bootfs 目录条目将命名为 blob/<merkle root of meta.far>。每个 blob 会在 bootfs、blob/<merkle root of the dependency>

<ph type="x-smartling-placeholder">
    </ph>
  1. 我们已经在运行时计算 Merkles 以验证内容,并且速度很快 所以在创建 zbi 等流程中 压缩图片,这无关紧要。
  2. 此提案要求的新图片构建流程已经是 在实际的图像构建步骤之前确定内容标识, 填充 meta.far 文件这意味着,基于内容的重复信息删除 大多发生在生成清单的过程中 推进图像构建步骤。它应该非常简单 只需确保我们已在这一阶段执行了所有可能的重复信息删除即可。

在讨论向 /blob 下的 bootfs 添加新条目时,我们只是 添加引用相同命令的新 bootfs 目录条目 底层文件(概念上是硬链接)。

bootfs 中一个名为“pkg_map”的新文件将保留从 复制到对 软件包。

bootfs 大小将根据新添加的 meta.fars 和新的 pkg_map 文件增大。 其他的一切都只是 已存在。保守地说,经过压缩的 bootfs 将增加约 70KiB 介绍 x64 架构。

/boot 更改

/boot 中会引入一个名为 /blob 的新子目录。以下文件中的所有文件: 名称以 /blob 作为前缀的 bootfs 映像将放置在其中。时间 bootfs 中的所有组件向软件包的迁移已完成, 级别目录将仅包含内核 vmos、Shell 脚本和 /boot 成为“命名空间”所需的组件管理器。

组件管理器命名空间最初是 /boot 的子目录 “布局”中的与所有依赖项预期的一样最终,我们的目标是 将组件管理器合并为一个 meta.far 编码,这种编码依赖于 分辨率的 SWD 堆栈。

BootResolver 变更

软件包名称空间 bootfs 组件的核心工作与以下项高度重叠: package-resolver 完成的工作。必须映射直观易懂的软件包名称 添加到经过内容 ID 处理的 meta.far 中,必须对 meta.far 进行解码,且其内容文件 才能构造命名空间。因此,该设计旨在重复利用 现有的软件包解析器逻辑。

重复使用 package-resolver 逻辑的最简单入口点是 package-directory::serve ,了解所有最新动态。此入口点接受 BlobFS 和 content-id 识别 meta.far,打开 meta.far,解析其 meta/contents 文件,以及 构造和提供文件中编码的命名空间。我们可以重复利用 但前提是我们能提供由 bootfs 提供支持的目录, 将 blob 作为 BlobFS 客户端发送到 API。

    let (proxy, server) =
        fidl::endpoints::create_proxy().map_err(ResolverError::CreateEndpoints)?;
    let () = package_directory::serve(
        package_directory::ExecutionScope::new(),
        <some blobfs::Client-like view on top of the bootfs blobs>,
        <meta.far hash>,
        fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE,
        server,
    )
    .await
    .map_err(ResolverError::ServePackageDirectory)?;

blobfs::Client 只是封装了 fio::DirectoryProxy,这就是 bootfs 的 通过组件管理器公开。我们可以使用 Blobfs 客户端,并将其传递给 package_directory::Serve 调用。有点小 但需要进行更改以确保某些 blobfs::Client API 失败 妥善地安装在 bootfs(即需要可变性的 bootfs)上时 open_blob_for_write、delete_blob),但在加载过程中不需要可变性。 package_directory::serve 执行。

BootUrl 更改

BootUrl 目前不使用其网址的主机或路径部分,因为 无需编码代码库和软件包这意味着,您无需引入 新的网址架构和解析器,我们可以改为引入新组件加载 。存在 或缺少软件包路径,都会指示新的 “BootResolver 更改”中所述的解决路径。

示例:

fuchsia-boot:///#my_component.cm:bootresolver 会将此网址解读为 命名空间已在 /boot 目录即可。

fuchsia-boot:///my_package#my_component.cm:bootresolver 将 将此网址视为打包组件,而其中“my_package”添加到 存在 my_package 的 Metal.far 的 Merkle 根,并且应该用于 构造一个特定于“my_package”命名空间的命名空间。

产品/图片装配变更

Bootfs 映像构建通过两阶段的工作执行来完成 由构建系统和映像汇编系统组成

首先,我们将引入一个名为 bootfs_packages 的新 gn 变量。 此列表将在 product.gni 中声明,并分配给 名为 bootfs_package_labels 的调用方变量,传递给任何对象 分配 build/input:bootfsassemble_system 调用 bootfs_labels 变量。

当我们将 bootfs 组件从其无软件包编码迁移到 bootfs 软件包时,我们将将其从包含 将其添加到 bootfs_labels 依赖项集中,并将其添加到 bootfs_packages

接下来,在生成映像组装配置时,我们将使用现有的 list_package_manifests 模板,用于从软件包中收集软件包清单 (在调用方的 bootfs_package_labels 变量中定义)。

接下来,映像汇编从构建遍历中获取清单,并将其用于 调用 zbi 等工具,将 build dir 中的文件打包到映像中。 软件包清单格式包含“blob”列表这些对象在图像上 创建的内容,用于将 blob/<merkle_root> 命名文件添加到 bootfs 映像

在迭代这些 blob 对象时,我们会检查 是包 meta.far 的标识符,将添加 将软件包名称复制到 meta.far 的 Merkle 根目录中。bootfs 映像结束时 该地图将以 json 格式写入“pkg_map”描述的文件 (请参阅上面的 bootfs 更改部分)。

我们选择在图像汇编级别实现这一转换, 原因:

  • ProductAssembly 只是合并了其各种 软件包。

  • ImageAssemblyConfig 验证仍然很简单。

  • 在“packages”的产品组合中验证继续 对“软件包”执行操作

实现

功能实现

映像汇编更改、bootfs 更改、BootResolver 更改 和 BootUrl 更改都可以同时完成。

为了确保我们不会看到 在实现过程中使用 bootfs_package_labels, 我们先从“Image Assembly”更改开始我们将 在 bootfs 软件包清单之前 实现此功能 聚合,我们将在构建时检查该集是否为空。

BootUrl 的语义将通过 3 个 CL 发生变化。首先,迁移之前 首个 bootfs 组件添加命名空间时,我们会将 BootUrl 更改为 则视为无效(确保 我们保留软件包路径的可用性作为 解决策略)。第二个是迁移到了第一个 bootfs 组件, fuchsia_package 中,我们将允许在 fuchsia-boot 网址中含有软件包路径。第三, 当最后一个 bootfs 组件迁移到 fuchsia_package 后,我们将禁止 不包含软件包的 fuchsia-boot 网址。

Migration

这些功能完全实现后,我们将迁移 bootfs 组件 增量。给定组件的迁移如下:

  1. 我们将找到组件的未打包依赖项的位置 (.cml 文件、二进制文件等)会添加到 bootfs_labels 依赖项中。
  2. 我们会将该未打包依赖项集合转换为 fuchsia_package gn 目标。
  3. 我们将从现有的 bootfs_labels 组中移除该软件包,并添加 添加到 bootfs_package_labels 组中。
  4. 我们会将 bootstrap.cml 文件中组件的网址更新为 添加软件包名称

性能

系统大小

保守地说,bootfs 映像将增加约 70KiB(压缩后) 介绍 x64 架构。这是因为在将所有组件迁移到 软件包之后,bootfs 大小将根据新添加的 meta.fars 和 新的 pkg_map 文件。其他的一切都只是 打包前的 bootfs 文件的目录条目。

运行时影响

如今,整个 bootfs 被急于在 component_manager 解析到某个目录 启动。迁移后,我们最终会将等效的解析工作 在 /boot 内设置组件的命名空间,直到该组件启动。

除了以前只是解析 bootfs 头文件;我们必须解析 meta.far

ZBI 已签字,因此我们目前不对 zbi 内的 bootfs 文件,只验证 zbi 本身。如果我们 不在引导加载程序中对 blob 进行运行时验证,我们将严格按照 与现在一样的安全地位

引导加载程序可能今天会发现一个潜在的错误状态, 程序集可能会错误地将源文件放在 错误。bootfs 中存在 blob 意味着 通过对启动磁盘进行运行时验证来避免这种情况 Blob。我们一开始不打算这样做。

构建时的影响

zbi 构建时的构建时性能保持不变;迁移之后 而无需遍历包含的每个依赖项的目的地条目清单 在 bootfs 中,我们改为遍历软件包清单来对 blob 进行编码。在 我们要遍历相同数量的工件。

向后兼容性

此方案涉及的许多更改都在 bootfs 中是独立的。 在 而不是整个 bootfs。因此,不存在系统造成的风险 提供了不完整的 bootfs 打包实现;提供的已打包的 bootfs 组件。

向后兼容性问题的一个潜在根源是,如果图片汇编程序 不知如何发现自己包含了一个带有新组件的旧版 bootfs 映像 在同一个 zbi 内的经理账号。如果发生这种情况,在迁移完成后 和非封装组件在 bootfs 中被禁止,这可能会导致 错误。不过,考虑到产品组件构建 bootfs 的方式, 这种状态今天根本不可能出现。

同样,对 BootUrl 的语义进行更改功能 实现不会偏离功能 因为它也包含在 bootfs 中。

安全注意事项

软件包名称/进程沙盒可严格提高运行时安全性 减少进程有权访问的工件

bootfs 映像是经过签名的只读映像 与格式设置无关, 图片。如果有,请在命令行中提供可执行文件的 Merkle 根身份(如果有的话) 如果我们做出决定, bootfs 映像应可增量更新

隐私注意事项

无。

测试

在组件解析器和产品组装方面进行测试的做法不错 记录,并将扩展以涵盖新功能。

我们将在语义更改的 3 个阶段分别添加 BootUrl 解析器测试 以表示正在强制执行网址的预期行为。

文档

有关用户空间引导的 API 文档,以及有关 bootfs 映像汇编时,需要更新。

缺点、替代方案和未知问题

重复使用 bootfs_labels 与引入新标签。

今天,依赖项列表中意外包含软件包 是空操作。进行这项更改后, 软件包的存在在语义上具有重要意义。因此, 我们需要“清理”无意中包含来自 bootfs_labels 命名空间。

此处的唯一复杂性涉及 fuchsia_driver_packages。 fuchsia_driver_package 是唯一的构建模板;如何组装产品 与 fuchsia_driver_package 的交互方式取决于驱动程序是否能够 被放入 bootfs 或 blobfs 中。放入 bootfs_labels 时,产品 会遍历驱动程序软件包,以便将驱动程序视为 依赖项,当放入 blobfs 中时,驱动程序通过 meta.far。这样做是为了让单个驱动程序目标可以在 bootfs 之间切换 和 blobfs,具体取决于产品。过去它能正常运行 但产品组合并没有为 其依赖项图表。不过,对于 bootfs 软件包名称 解读是否存在 fuchsia_package (及其关联的 package_manifest)作为声明,以放置 将软件包命名空间编码为 bootfs。

如果我们今天发布 bootfs 命名空间功能,大约有 20 个驱动程序会结束 同时作为未打包依赖项放置在 bootfs 中,并由 关联的 meta.far 和关联的 Blob;驱动程序的 meta.far 编码 在对 Drive_manager 的运行程序做出更改之前,未使用这些命令。而非膨胀 我们希望清除所有 fuchsia_packages 然后逐步将目标迁移到适当的打包环境。周三 选择清除 fuchsia_driver_packages 的 package_manifests 引入新的 gn 元数据屏障,防止 list_package_manifests 走到紫红色的车手。这样,您无需拆分每个驱动程序 将 fuchsia_driver_package 嵌入到 bootfs 目标和 blobfs 目标中, 因为它能显著降低驱动程序产品组装的复杂性 逻辑。此外,build 中可以提供服务的单个软件包定义 这可避免由于拆分每个目的而产生用户流失 然后移除所有“仅旧 bootfs”伪软件包。

遗憾的是,这具有不同的复杂性,例如引入 真正的 fuchsia_package 目标,对用户隐藏其名称。这就引出了 无法兼容其他现有和正在进行的工作,例如黄金测试 按名称验证设备上存在的所有软件包清单是否符合预期, 或产品组装工作,要求生成包装索引的标签 仅直接依赖于软件包目标本身。

因此,干扰性更小的方法是直接添加新标签, 将前期启动中的组件迁移到软件包时,请将其从旧版 复制到新标签中最终,我们需要合并这些列表 如果尚未将驱动程序迁移到适当的组件, 需要提高 fuchsia-driver-package 模板的复杂性。

在产品或映像组装操作中实现

  • 是否应在 产品组合还是图片组合?换句话说,我们是否应该遵循 解析清单,直到我们准备调用 使用 zbi 工具构建 bootfs?
    • ProductAssembly(ffx 组装产品)
    • 优点: <ph type="x-smartling-placeholder">
        </ph>
      • ImageAssembly 非常专注于生成图片文件 (zbi、blobfs 等)
      • ImageAssemblyConfig 包含包含所有 bootfs 文件的较为简单的列表。
    • 缺点: <ph type="x-smartling-placeholder">
        </ph>
      • 需要进行更复杂的验证 ImageAssemblyConfig,用于生成旧版汇编输入 bundle 与 ProductAssembly 创建的 ImageAssemblyConfig 匹配 (或者需要舍弃/弱化验证)。
      • 在产品组装结束时进行验证需要知道 查找 bootfs 中组件的“packages”(例如 value-file 已针对结构化配置完成存在性验证)。
    • ImageAssembly (ffx assembly create-system)
    • 优点: <ph type="x-smartling-placeholder">
        </ph>
      • ProductAssembly 只是合并了其各种 套装
      • ImageAssemblyConfig 验证仍然很简单
      • 在产品组合中“packages”的验证继续 对“软件包”执行操作
    • 缺点: <ph type="x-smartling-placeholder">
        </ph>
      • ImageAssemblyConfig 不再包含 bootfs 的完整内容 文件。
      • Image Assembly 需要执行软件包 ->到条目映射 然后再创建 zbi。

在 bootfs 中对 Metal.fars 进行编码,还是在构建 bootfs 映像时设置命名空间?

一种替代方案是简单地对“命名空间”启动映像 构建映像,使 /boot 下的每个组件都有自己的 组件管理器用作该组件的命名空间根目录的子目录。

在启动 bootfs 中对 meta.fars 进行编码时,我们通过 在 boot-resolver 中引入对 SWD 软件包解析库的依赖项, 并负责解读 meta.far 格式 面对 meta.far 格式的变化,继续采取这种做法。

在构建映像期间执行命名空间设置时, 如何解读产品装配的复杂程度 package-manifest/meta.far 格式,以及编写代码来翻译 meta.far 转换为 bootfs 映像中的软件包命名空间。这意味着需要新的代码, 第二个团队就成为解读 Meta.fars 和 负责确保产品与其保持同步。SWD 团队 表示担心新团队会接管 meta.far 的依赖项 而且可以通过汇合单个软件包来完全避免这种情况 编码和重复使用由 SWD 维护的工具, 格式。

两种策略的资源影响几乎相同( KiB)。

最后,软件包不仅仅是它们编码的命名空间。元数据中的 运行时功能(例如平台)需要 meta.fars(如软件包版本) 版本控制如果未使用 meta.fars,则需要引入一些新方法 对这种“额外信息”在基于 bootfs 的命名空间中,并教 组件管理器等额外编码服务, 元数据。