| 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 级别 7 中引入的 FIDL 消息在构建以 API 级别 6 为目标的组件时不可用,但在构建以 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 是一种兼容性 shim,可让旧版组件使用原本不受支持的接口。借助此机制,平台可以移除对旧版 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的请求路由到兼容性 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,但如果无法做到,系统可以创建新的 vDSO,其中包含单独的支持的 ABI 修订版本列表。
扩展 vDSO 会增加现有二进制文件的攻击面,因为这些现有二进制文件可以访问 vDSO 扩展。在决定是扩展现有 vDSO 还是创建新的 vDSO 时,项目应考虑安全性影响以及兼容性影响。
vDSO 可以提供一个用于检查 vDSO 是否支持给定 ABI 修订版本的函数,但 vDSO 不应直接公开受支持的 ABI 修订版本列表,因为如果将该列表公开给应用,当列表扩展时,应用就会中断。
流程框架
启动进程时,客户端应告知进程启动器相应进程所需的 ABI 修订版本。进程启动器应使用该信息为新启动的进程选择合适的 vDSO 和进程引导消息。
开放性问题。创建没有组件清单的进程时,应使用哪个 ABI 修订版本?一种可能性是将 ABI 修订版本放在可执行文件的 ELF 数据中,而不是(或除了)放在组件清单中。另一种可能性是将 ABI 修订版本添加到
fuchsia.ldsvc.Loader协议,该协议通常会路由到可执行文件的来源。
组件框架
构建组件清单的工具应将目标 API 级别作为命令行参数,并将相应的 ABI 修订版本嵌入到它们创建的组件清单中。
虽然不需要立即执行此操作,但组件最终会希望根据 ABI 修订版本来调整功能路由。例如,某个组件可能希望停止向其某个子组件提供特定服务。立即移除该服务可能会破坏与相应子组件旧版本的兼容性。相反,家长可能希望仅向以较新 ABI 修订版为目标受众群体的儿童提供服务。
同样,平台可能希望将特定旧版组件的功能路由到提供兼容性 shim 的专用目的地。例如,我们可以定义一个路由 quirk,该 quirk 会应用于 quirk 表中具有该 quirk 的特定旧版组件。
SDK
SDK 应以某种机器可读格式(例如,在其 JSON 元数据中)指定 SDK 支持的 API 级别以及这些 API 级别与其 ABI 修订版本之间的映射关系。应修改 SDK 集成,以允许最终开发者指定目标 API 级别,并以命令行实参的形式向需要该值的所有工具提供目标 API 级别。
性能
此提案试图通过在 build 和发现期间介入主要内容来最大限度地减少平台版本控制对性能的影响。用于运行旧版组件的兼容性 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 10)中,Windows 7)了解 e2011457-1546-43c5-a5fe-008deee3d3f0 GUID 并提供与 Windows Vista ABI 的兼容性。本文档中的提案将 ABI 修订版本与平台版本分离,从而提高了灵活性。
macOS、iOS
macOS 和 iOS 都使用 API_AVAILABLE 和 @available 注释来控制在构建应用时声明是否可用。系统库(也称为框架)还使用“链接于或晚于”检查和显式怪异表来支持需要平台提供旧语义的旧版应用。
Apple 已成功使用这些机制将这些操作系统上的应用从旧版 API 迁移到新版 API。