RFC-0002:Fuchsia 平台版本控制

RFC-0002:Fuchsia 平台版本控制
状态已接受
领域
  • 常规
说明

使用版本控制功能来让平台不断发展,同时提供兼容性。

Gerrit 更改
作者
审核人
提交日期(年-月-日)2020-04-30
审核日期(年-月-日)2020-05-23

摘要

本文档提出了 Fuchsia 平台的 API 级别ABI 修订版本的概念。最终开发者在构建时依据的是目标 API 级别,目标 API 级别决定了哪些声明对应用可见。目标 API 级别也会作为目标 ABI 修订版本嵌入已编译的应用中,表示应用期望平台提供的语义。给定的 Fuchsia 平台版本通常支持多个 ABI 修订版本,让平台能够运行旧版应用,同时仍然提供改进平台的路径。

设计初衷

目前,Fuchsia 平台通过一系列软过渡不断发展。为了更改 Fuchsia 系统接口的一部分,该平台首先引入了新界面。然后,应用会迁移到新界面。所有应用都迁移到新界面后,平台会移除旧界面。

使用此方法时,平台只能像运行最慢的应用一样快进。为了完成软转换,平台需要等待最后一个应用从旧接口迁移。随着应用数量的增加以及平台与应用之间的耦合程度的降低,软转换的执行时间越来越长。最终,我们将无法使用软转换来改进平台。

此 RFC 解决了以下问题声明:

Fuchsia 平台如何能够继续发展,同时能够在更长的一段时间内运行数量不断增长的旧应用?

为何此时推荐?

我们的一些客户要求提高平台的稳定性。如果我们现在提供这种稳定性,将会减慢平台的发展速度。 为了满足当前的这些客户需求,平台需要能够提供更长的兼容期,而不会让项目停滞不前。

此外,Windows 的经验是,在被要求长时间与这些应用提供二进制文件兼容性之前,在应用中嵌入目标 ABI 修订版本可让我们受益。Windows 错过了这一机会,现在会尝试使用启发法来猜测二进制文件的目标 ABI 修订版本,这会给开发者造成极大的困难。

术语

Fuchsia 的一个版本是面向用户群的 Fuchsia 操作系统和相关软件包的 build。一个版本有一个版本号,用于标识该版本中包含的一组软件工件。

向后兼容性是指较新版本的 Fuchsia 能够运行旨在在较低版本的 Fuchsia 上运行的二进制文件。

Fuchsia IDK 是开发环境集成商使用的一个工件,用于向开发者公开 Fuchsia 平台,以构建在 Fuchsia 上运行的应用。Fuchsia IDK 由 Fuchsia 项目发布,定义了 Fuchsia 平台与 Fuchsia 上运行的应用之间的合约。IDK 工具定义了 Fuchsia IDK 工具与开发环境集成商环境之间的合同。

软转换是一种技术,用于将不向后兼容的更改分解为一系列较小的平台更改和一组已知二进制文件,以便在每个步骤在本地保持兼容性。

设计

本文档中所述的设计旨在对 Fuchsia 系统接口进行版本控制,以便平台和应用能够就应用期望平台提供的语义达成一致。

具体而言,如果某个应用在给定的 Fuchsia 版本上运行,那么除非 Fuchsia 有意停止了对该应用的支持,否则该应用应继续在未来版本的 Fuchsia 上运行。此设计并未解决创建可在较低版本的 Fuchsia 上运行的新应用的反向问题。

版本控制

Fuchsia 平台使用两种版本标识符:API 级别ABI 修订版本。这两个版本均标识平台提供的接口,而不是该接口的实现。Fuchsia 的各个版本使用不同的版本控制方案,用于标识该版本中的具体实现。

给定的 API 级别涉及特定的 ABI 修订版本,但多个 API 级别可能涉及同一个 ABI 修订版本。

API 级别

Fuchsia API 级别表示在构建应用时可用的一组 API。Fuchsia IDK 的给定版本通常支持多个 API 级别。在受支持的给定 API 级别提供的 API 在各个 IDK 版本之间应保持一致。

示例:请考虑 pkg/fit,它是 SDK 中的 C++ 库。fit 库声明了多个函数,每个函数都是该库提供的 API。API 定义了这组函数,这意味着两个 IDK 版本应在同一 API 级别在 fit 库中公开同一组函数。

从语法上讲,Fuchsia API 级别是一个无符号的 64 位整数。 随着平台的发展(请参阅下文的演变部分),API 级别按递增顺序分配,并且旨在供人类(包括最终开发者)理解。

ABI 修订版本

Fuchsia ABI 修订版本表示应用希望平台提供的 Fuchsia 系统接口的语义。给定的 Fuchsia 版本通常支持多个 ABI 修订版本,但受支持的给定 ABI 修订版本的语义应在 Fuchsia 版本之间保持一致(请参阅下文的演变)。

示例:请考虑使用 zx_clock_get_monotonic,这是 vDSO 作为 Fuchsia 系统接口的一部分提供的函数。ABI 修订版本会指定此函数是否存在以及调用此函数后会发生什么情况,这意味着 zx_clock_get_monotonic 的语义应在同一 ABI 修订版本的不同 Fuchsia 版本之间保持一致。

从语法上讲,Fuchsia ABI 修订版本是一个无符号的 64 位整数。ABI 修订版本是没有内部结构的不透明标识符。如需为新的 ABI 修订版本创建标识符,请从之前从未用于识别 Fuchsia ABI 修订版本的值中随机选择一个无符号的 64 位整数。

ABI 修订版本的标识符是随机选择的,以防止最终开发者猜测未来的 ABI 修订版本标识符,并对未来版本的 Fuchsia 系统接口的语义形成预期。因此,ABI 修订版本应便于机器理解,而极少由人工解读。

进化

每当平台在 Fuchsia IDK 中添加或移除 API 或 ABI 修订版本发生更改时,平台都会提高 API 级别。在实践中,项目可能会通过按既定频率(例如,每天一次或每周一次)提高 API 级别来批量更改更改。

每当平台对 Fuchsia 系统接口的语义进行向后不兼容的更改时,平台都会更改 ABI 修订版本。在实践中,项目可能会通过按某种既定频率(例如每六周或每六个月)更改 ABI 修订版本来批量进行向后不兼容的更改。

在限制中,语义的每次更改都可能向后不兼容,但在实践中,操作系统确实会更改其语义,而不会破坏应用。例如,许多流行的操作系统会在不破坏其应用的情况下添加系统调用。

待办项。创建一份文档,详细说明平台认为对 Fuchsia 系统接口进行哪些更改是向后兼容的。随着项目积累了关于一般更改和不会导致应用的实际更改的实现经验,项目可能需要随着时间的推移不断优化该文档。

应用

最终开发者在构建组件时只会选择一个目标 API 级别。目标 API 级别控制在构建组件时 Fuchsia IDK 中的哪些声明可用。例如,在构建以 API 级别 6 为目标的组件时,API 级别 7 中引入的 FIDL 消息不可用,但在构建以 API 级别 7 或 8 为目标的组件时,该消息可用(假设该消息在 API 级别 8 中未被废弃)。

在构建组件时,SDK 中的工具会在组件清单中包含与目标 API 级别相关联的目标 ABI 修订版本。这样一来,每个组件都会声明开发者在构建其组件时希望平台提供的语义。给定的软件包可以包含许多组件,每个组件都可以选择其偏好的目标 ABI 修订版本。

平台

平台维护着一个受支持的 ABI 修订版本列表。平台会为面向受支持的 ABI 修订版本的组件提供二进制兼容性,这意味着平台将尝试为这些组件提供其目标 ABI 修订版本所指示的平台语义。

示例:考虑从 fuchsia.foo.Bar 协议转换到 fuchsia.foo.Bar2 协议。假设组件 baz.cm 有一个目标 ABI 修订版本,指明该组件希望平台提供 fuchsia.foo.Bar。运行 baz.cm 时,平台会将对 fuchsia.foo.Bar 的请求路由到相应的实现。不过,在转换为 fuchsia.foo.Bar2 后,如果运行具有目标 ABI 修订版本的组件,平台不会再将对 fuchsia.foo.Bar 的请求路由到实现,因为以该 ABI 修订版本为目标的组件应改用 fuchsia.foo.Bar2

在某些情况下,平台可能希望取消对给定 ABI 修订版本的支持。此类移除通常受到仍依赖于旧 ABI 修订版本的重要组件尾部限制。平台不会保留旧版 ABI 修订版本所隐含的完整语义,而是维护一个旧版组件列表,以及一个运行这些特定组件所需的怪异组件表。quirk 是一种兼容性填充码,可让旧版组件使用以其他方式不受支持的接口。利用这种机制,平台可以取消对旧版 ABI 修订版本的常规支持,同时仍能够运行以该旧版 ABI 修订版本为目标的某些重要组件。

示例:假设平台不再支持任何包含 fuchsia.foo.Bar 的 ABI 修订版本,但 baz.cm 是尚未迁移到 fuchsia.foo.Bar2 的重要组件。项目可以将 baz.cm 视为具有 needs-fuchsia-foo-bar 怪异行为的旧版组件。虽然平台不支持 baz.cm 的目标 ABI 修订版本,但平台可以继续运行 baz.cm,方法是将其对 fuchsia.foo.Bar 的请求路由到兼容性填充码(可能使用 fuchsia.foo.Bar2 实现)。兼容性 shim 不需要支持 fuchsia.foo.Bar 隐含的完整语义。相反,兼容性 shim 只需足够好才能使 baz.cm(以及具有 needs-fuchsia-foo-bar 怪异的其他特定组件)正常运行。

平台无法运行既不以受支持的 ABI 修订版本为目标,又未列为旧版组件的组件,因为平台不知道这些组件期望的语义。

生命周期

Fuchsia 系统接口的每个元素(例如系统调用或 FIDL 消息)都会经历以下生命周期:

  1. 该元素被引入到平台中。在 Fuchsia 发布包含该元素的新 API 级别的 SDK 之前,最终开发者将无法使用该 API。如果可在不破坏 ABI 的情况下引入元素(例如,添加系统调用),则可以更新现有 ABI 修订版本的语义,以包含新引入的元素。否则,必须对以旧版 ABI 修订版本为目标的组件隐藏该元素,以免破坏它们。
  2. 如果可能,可通过引入子元素来extended元素。例如,可以通过引入新字段对 FIDL 表进行扩展。引入子元素会启动该子元素的其他元素生命周期实例,包括需要新的 API 级别才能使该元素的 API 对最终开发者可见。只有在添加子元素不会破坏现有的 API 或 ABI 的情况下,才能扩展元素。
  3. 该元素可能已被弃用。在较新的平台版本上运行时,以较低 ABI 修订版本为目标的组件仍然可以使用该元素。不过,以更高 API 级别为目标平台的最终开发者将无法再使用该元素。
  4. 一旦平台不再支持从该元素引入弃用之间的任何 ABI 修订,则该元素属于旧版。此时,平台只需要支持该元素,因为特定旧版组件实际上是通过怪异组件使用该元素。
  5. 如果所有旧版组件都不再使用该元素,则可以从平台中完全移除该元素。

动力学

这种方法通过耦合对新 API 的访问权限来执行这些迁移,从而激励开发者从已弃用的接口进行迁移。具体而言,如需获得对新引入的 API 的访问权限,开发者必须更改其目标 API 级别,这需要他们从该 API 级别中废弃的所有接口迁出。

实现

实现此设计涉及 Fuchsia 系统的许多层。本文档提供了每个相关层所需更改的草图,但这些层的详细设计留待后续文档。

FIDL

FIDL 应提供一种方法来标注每个协议元素可用的 API 级别范围。FIDL 工具链应了解目标 API 级别,并生成适合该 API 级别的代码。

当某个协议元素(例如表格中的字段或协议中的消息)在某个给定的 API 级别被废弃时,我们希望这些面向该 API 级别的组件能够接收包含该协议元素的消息,但希望阻止这些组件发送包含该协议元素的消息。

系统头文件

系统头文件应允许最终开发者指定目标 API 级别,然后根据目标 API 级别调整使用这些头文件而可见的 API 集。此外,系统头文件应定义可用于将其他库中的声明的可见性限制为特定 API 级别的宏。

vDSO

系统应提供多个 vDSO,每个 vDSO 都支持一系列 ABI 修订版本。如果可能,系统应通过以向后兼容的方式扩展 vDSO 来改进,但如果无法实现,系统无法使用单独的受支持 ABI 修订版本列表创建新的 vDSO。

扩展 vDSO 会增加现有二进制文件的攻击面,因为这些现有二进制文件可以获得对 vDSO 扩展程序的访问权限。在决定是否扩展现有 vDSO 或创建新的 vDSO 时,项目应考虑安全隐患以及兼容性影响。

vDSO 可以提供一个函数来检查 vDSO 是否支持给定的 ABI 修订版本,但 vDSO 不应直接公开受支持的 ABI 修订版本列表,因为向应用公开该列表会使应用在扩展列表时中断。

流程框架

启动进程时,客户端应通知进程启动器,进程需要哪些 ABI 修订版本。进程启动器应使用该信息为新启动的进程选择适当的 vDSO 和进程引导消息。

待解决的问题。在创建没有组件清单的进程时,应使用什么 ABI 修订版本?一种可能的方法是,将 ABI 修订版本放在可执行文件的 ELF 数据中,而不是(除了 ?)放在组件清单中。另一种方法是将 ABI 修订版本添加到 fuchsia.ldsvc.Loader 协议,该协议通常会路由到可执行文件的来源。

组件框架

构建组件清单的工具应将目标 API 级别作为命令行参数,并将相应的 ABI 修订版本嵌入到它们创建的组件清单中。

虽然不是立即需要,但组件最终希望根据 ABI 修订版本来调整功能路由。例如,某个组件可能希望停止向其某个子组件提供某项服务。立即移除此服务可能会破坏与该子组件旧版本的兼容性。因此,父级可能只想为以旧版 ABI 修订版本为目标的子级提供该服务。

同样,平台可能希望将特定旧版组件的功能路由到提供兼容性填充码的专用目的地。例如,我们可以定义一个路由 quirk,应用于在 quirk 表中具有该怪异的特定旧版组件。

SDK

SDK 应以某种机器可读的格式(例如,在其 JSON 元数据中)指定 SDK 支持的 API 级别,以及这些 API 级别与其 ABI 修订版本之间的映射。您应修改 SDK 集成,让最终开发者能够指定目标 API 级别,并将目标 API 级别作为命令行参数提供给需要该值的所有工具。

性能

此方案尝试在构建和发现期间干预主实例,最大限度地降低平台版本控制对性能的影响。用于运行旧版组件的兼容性填充码可能会对性能产生重大影响,但在向旧版组件列表中添加组件时,项目可以针对具体情况评估这些性能影响。

安全注意事项

该方案应对安全性产生积极影响,因为该方案将使 Fuchsia 软件生态系统更轻松地迁移到新版 API,这些新版 API 可能具有比旧版 API 更好的安全属性。

此外,分配新的 ABI 修订版本的功能可以避免向现有应用公开新的 ABI,从而减少这些应用面临的攻击面。在决定是否扩展现有 ABI 或是否分配新的 ABI 修订版本时,项目应考虑分配新的 ABI 修订版本的安全优势。

该方案确实为恶意应用提供了一种机制,供其选择平台中其他可能较旧的代码路径,例如通过声明以较旧的 ABI 修订版本为目标。随着平台的发展,该项目需要像对待支持新版 ABI 修订版本的代码一样,对支持旧版 ABI 修订版本的代码进行处理。

隐私注意事项

该方案应对隐私保护产生积极影响,因为它将使 Fuchsia 软件生态系统更轻松地迁移到新版 API,这些新版 API 可能比旧版 API 具有更好的隐私权属性。

测试

此方案在一定程度上增加了测试矩阵,因为平台的行为因正在运行的组件的 ABI 修订版本而异。我们将在 Fuchsia 兼容性测试套件 (CTS) 的设计中考虑测试矩阵中的这种增加。例如,项目可能需要根据 ABI 修订版本对 CTS 进行版本更新,以确保平台在改进时不会降低对旧版 ABI 修订版本的支持。

文档

您应更新平台的文档,为每个 API 添加注释,说明其在生命周期中的当前阶段及其生命周期历史记录(例如,引入、废弃和/或移除该 API 的时间)。这些注解应派生自同一可信来源,以便控制应用在以特定 API 级别为目标平台时是否可以访问这些 API。例如,fidldoc 工具应了解 FIDL 源文件中的 API 级别注解,并在生成的文档中生成相应的注解。

每当平台创建新的 ABI 修订版本标识符时,项目都应更新文档,以说明新的 ABI 修订版本不能以哪些方式向后兼容以前的 ABI 修订版本,以及最终开发者在更新应用时应执行的操作(如果有)。

此外,该项目还应包含一些概念性文档,解释该平台为何设有 API 级别以及如何从一个 API 级别升级到另一种 API 级别。

缺点、替代方案和未知因素

实施该方案的费用是多少?

实施该方案的主要成本是在发展平台时增加运营复杂性。现在,添加新的 API 需要整个项目进行协调,以便在新的 API 级别发布该 API。同样,弃用 ABI 更复杂,因为弃用过程会分几个步骤完成。

系统本身也会变得更加复杂,因为系统的行为在一定程度上取决于每个组件的 ABI 修订版本。

还有哪些策略可以解决同样的问题?

一些其他平台使用的另一种策略是永不移除功能。例如,Web 平台几乎完全是以累加的方式在不断发展变化。从某些方面来看,这种方法更简单,因为系统不需要通过某种机制来弃用功能。

另一种方法是对系统的不同部分使用不同的版本标识符,而不是对整个系统使用单一的 API 级别。在某种程度上,Fuchsia 也采用了这种方法。例如,每个文件系统都有自己的版本标识符,该标识符用于磁盘上表示法与文件系统的内存中代码之间的协定。对整个系统使用同一个 API 级别意味着,平台与应用之间协定的演变在一定程度上需要协调一致。

现有技术和参考资料

关于这一主题,已有大量技术资料。本文档中的建议直接基于 Android、Windows 和 macOS/iOS 的体验。

Android

Android 有 API 级别的概念。Android 上的每个平台接口都用引入接口的 API 级别进行了标注。Android 应用还会使用 uses-sdk 元素在其清单中指定其目标 API 级别。原则上,Android 可以使用此 API 级别机制来弃用和移除较旧的接口。

Windows

Windows 大量使用类似于 ABI 修订版本的概念,后者在应用清单中显示为 SupportedOS 条目。Windows 使用 GUID 来标识应用所针对的 ABI 修订版本,这与本文档中关于使用不透明 64 位整数的建议类似。

在 Windows 中,SupportedOS GUID 与特定版本的 Windows 相关联。例如,e2011457-1546-43c5-a5fe-008deee3d3f0 标识 Windows Vista。但适用于更高版本的 Windows(例如Windows 7)识别 e2011457-1546-43c5-a5fe-008deee3d3f0 GUID,并提供与 Windows Vista ABI 的兼容性。本文档中的方案将 ABI 修订版本与平台版本分离,后者更为灵活。

macOS、iOS

macOS 和 iOS 都使用 API_AVAILABLE@available 注解来控制在构建应用时声明是否可用。系统库(也称为框架)还使用“关联或之后关联”检查和显式怪异表来支持需要来自平台的旧语义的旧应用。

Apple 已成功使用这些机制将这些操作系统的应用从旧版 API 迁移到新版 API。