RFC-0002:Fuchsia 平台版本控制 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 使用版本控制来让平台不断发展,同时提供兼容性。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2020-04-30 |
审核日期(年-月-日) | 2020-05-23 |
摘要
本文档提出了针对 Fuchsia 平台的 API 级别和 ABI 修订版的概念。最终开发者针对目标 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 修订版的一小部分重要组件。该平台会维护一个旧版组件列表,以及运行这些特定组件所需的怪癖表。怪癖是一种兼容性 shim,可让旧版组件使用原本不受支持的接口。借助此机制,平台可以移除对旧版 ABI 修订版本的一般支持,同时仍能运行以该旧版 ABI 修订版本为目标的某些重要组件。
示例。假设平台不再支持包含
fuchsia.foo.Bar
的任何 ABI 修订版,但baz.cm
是一个尚未迁移到fuchsia.foo.Bar2
的重要组件。该项目可以将baz.cm
视为具有needs-fuchsia-foo-bar
怪癖的旧版组件。即使平台不支持baz.cm
的目标 ABI 修订版,也可以通过将其对fuchsia.foo.Bar
的请求路由到兼容性 shim(可能使用fuchsia.foo.Bar2
实现)来继续运行baz.cm
。兼容性 shim 无需支持fuchsia.foo.Bar
所暗示的完整语义。相反,兼容性 shim 只需正常运行,即可让baz.cm
(以及具有needs-fuchsia-foo-bar
怪癖的其他特定组件)正常运行。
平台无法运行既不以受支持的 ABI 修订版为目标平台,也不列为旧版组件的组件,因为平台不知道这些组件预期的语义。
生命周期
Fuchsia 系统接口的每个元素(例如系统调用或 FIDL 消息)都会经历以下生命周期:
- 该元素已引入到平台中。在 Fuchsia 发布包含该元素的新 API 级别的 SDK 之前,最终开发者无法使用该 API。如果可以在不破坏 ABI 的情况下引入元素(例如添加系统调用),则可以更新现有 ABI 修订版本的语义以包含新引入的元素。否则,必须将该元素隐藏起来,以免破坏以旧版 ABI 修订版为目标的组件。
- 如果可能,可以通过引入子元素来扩展该元素。例如,可以通过引入新字段来扩展 FIDL 表。 引入子元素会启动该子元素的另一个元素生命周期实例,包括需要新的 API 级别才能向最终开发者公开该元素的 API。只有在添加子元素不会破坏现有 API 或 ABI 的情况下,才能扩展元素。
- 该元素可能已废弃。以较低 ABI 修订版为目标平台的组件在较新平台版本上运行时仍可使用该元素。不过,以较新 API 级别为目标平台的最终开发者将无法再使用该元素。
- 当平台不再支持该元素的引入和废弃之间的任何 ABI 修订版本时,该元素就会成为旧版。此时,只要特定旧版组件通过某种方式实际使用了该元素,平台就只需支持该元素。
- 当所有旧版组件都不再使用该元素后,您就可以从平台中完全移除该元素。
动力学
这种方法通过将访问新 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 修订版本为目标的子模块提供该服务。
同样,平台可能希望将特定旧版组件的功能路由到提供兼容性 shim 的专用目的地。例如,我们可以定义路由怪癖,以便将其应用于怪癖表中具有该怪癖的特定旧版组件。
SDK
SDK 应以某种机器可读的格式(例如在其 JSON 元数据中)指定 SDK 支持的 API 级别以及这些 API 级别与其 ABI 修订之间的映射。应修改 SDK 集成,以允许最终开发者指定目标 API 级别,并将目标 API 级别作为命令行参数提供给需要该值的所有工具。
性能
此提案尝试通过在构建和发现期间干预主副本,最大限度地减少平台版本控制对性能的影响。用于运行旧版组件的兼容性 shim 可能会对性能产生重大影响,但在将组件添加到旧版组件列表时,项目可以根据具体情况评估这些性能影响。
安全注意事项
此提案应该会对安全性产生积极影响,因为该提案可以更轻松地将 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
注解来控制在构建应用时声明是否可用。系统库(也称为框架)还使用“链接在或之后”检查和显式 quirk 表来支持需要平台旧版语义的旧版应用。
Apple 已成功使用这些机制将适用于这些操作系统的应用从旧版 API 迁移到新版 API。