RFC-0107:动态优惠 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 允许在 CFv2 中运行时创建优惠。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-05-24 |
审核日期(年-月-日) | 2021 年 6 月 24 日 |
摘要
在组件框架 v2 (CFv2) 中,父级组件可以使用集合在运行时向拓扑图添加动态子组件。不过,无法在运行时配置为此类动态子组件提供的一组功能。商品是在父级组件的清单中声明的,该清单在父级组件开始运行时将不可变。
父级组件的清单会统一处理给定集合中的所有动态子项;它甚至无法谈论集合中的个别组件。这意味着存在一些限制:
- 无法向单个动态子项提供 capability。向集合提供 capability 会将该 capability 提供给该集合中的所有动态子项。
- 无法将单个动态子项提供的功能提供给其他组件。从集合提供 capability 会创建一个汇总 FIDL 服务,该服务允许连接到该集合中的任何动态子项。
本文档提出了动态产品,可在运行时添加到组件拓扑中。
利益相关方
- 在运行时构建动态拓扑的 CFv2 用户。这些团队将由驱动程序框架团队代表。
- 组件框架领域所有者,他们可以验证设计是否合理且不会与任何未来计划冲突。
- 安全团队,他们可以确保此功能不会破坏系统的安全属性。
设计初衷
大多数 CFv2 使用场景涉及由组件组成的静态拓扑,这些组件协同工作以完成某项任务。所有这些组件的标识和它们之间的关系都是事先已知的,并且可以硬编码到组件清单中。
在某些其他用例中,开发者不知道将要运行的确切组件集,但知道这些动态组件之间的相互关系。开发者可以将所有动态组件分组到集合中,其中给定集合中的每个组件都提供一组相同的功能。
不过,有些 CFv2 用户希望在运行时构建复杂的拓扑,为每个动态子项提供一组独特的功能,并在动态子项之间路由功能。
用例:驱动程序作为组件
在驱动程序框架的“驱动程序即组件”项目中,动态实例化的驱动程序组件依赖于其他动态实例化的驱动程序组件提供的功能。我们将从一个驱动程序组件提供给另一个驱动程序组件的功能称为驱动程序间功能。
CFv2 本身不支持驱动程序间功能。驱动程序框架团队通过在 driver_manager
本身中实现类似功能来解决此问题。此权宜解决方法存在一些缺点,并会暴露驱动程序间功能与“正常”功能之间的令人困惑的差异:
- 组件通过在其传入命名空间中打开路径来连接到“正常”功能,但通过特定于驱动程序框架的
exposed_dir
字段连接到驱动程序间功能。 - 提供
exposed_dir
后,接收组件可以访问其依赖项实现的所有协议。driver_manager
不会限制接收器仅访问所需的协议。驱动程序框架团队可以解决此问题,但这样做会进一步复制组件框架机制。 - 驱动程序组件需要在清单的
use
部分声明其依赖的“常规”功能,但不应以相同的方式列出驱动程序间功能。如果这样做,Component Manager 将无法找到满足use
的offer
。- 目前,Component Manager 大多会忽略这些多余的
use
,但这可能会在短期内造成混淆,并且多余的use
可能会在较长一段时间内变成错误。
- 目前,Component Manager 大多会忽略这些多余的
驱动程序作者不应将“正常”功能视为与驱动程序间功能不同,因此应尽快取消此区分。等待时间越长,在模型发生变化时,就需要迁移的驱动程序就越多。
设计
此提案扩展了 fuchsia.sys2.Realm/CreateChild
API,以接受 OfferDecl
列表,用于描述将向新创建的子项提供的额外功能(除了通常提供的功能之外)。我们将以这种方式指定的 OfferDecl
称为动态商品。以常规方式(即在组件清单中列出)指定的 OfferDecl
将称为静态商品。
任何类型的优惠(例如协议、目录等)都可以用作动态优惠。
动态商品始终定位到新创建的子组件。因此,定位到组件的一组动态商品在组件创建时已知,之后便不可变。(是否必须实际指定 OfferDecl
的 target
字段将在 API 审核中确定。)
适用于静态商品的任何来源也适用于动态商品。此外,与静态商品不同,动态商品可以使用“同胞兄弟”动态子组件(即与目标具有相同父级的组件)作为来源。如果动态商品的来源是动态子商品,则目标商品不必与来源位于同一集合中。
创建子组件后,动态优惠的行为应与静态优惠完全相同,包括组件关闭顺序等注意事项。
销毁
当其来源或目标之一被销毁时,动态商品也会被销毁。优惠被销毁后,系统的行为将完全像该优惠从未存在一样。
动态商品的用户应尽可能先销毁目标,再销毁来源。如果要销毁的目标是某个优惠(如果优惠掉落在森林中,而周围没有任何组件可以听到它...),则从拓扑中移除该优惠的过程非常简单。不过,有时无法避免,源代码会在目标代码之前被销毁。值得注意的是,使用单次运行集合(在 RFC-0101 中引入)时,动态组件会在终止后立即销毁,而终止可能随时发生。
即使是销毁来源,流程也非常简单:
- 组件管理器会关闭源组件。这会阻止目标打开与源的任何新连接,并指示运行程序停止源组件(如果它正在运行)。
- 当源组件停止时,源和目标之间的所有通道都会关闭。
- 源组件和动态优惠将同时从拓扑中移除。
从目标的角度来看,与 capability 对应的通道会关闭,并且任何重新获取 capability 的尝试都会永久失败,就像 capability 从一开始就未路由一样。
此 RFC 未提供用于重新创建此类优惠的机制。
防止依赖项循环
CFv2 要求组件及其之间的强大优惠形成有向无环图,以便能够按依赖项顺序顺利关闭组件。对于静态商品,我们可以通过查看组件清单来确保没有依赖项循环。
对于动态商品,以下准则足以确保无环依赖项图:
- 动态组件创建顺序是严格的总顺序。换句话说,不会有两个动态组件同时添加到拓扑中。
- 动态商品始终与其目标组件同时创建。
- 所有优惠(静态和动态优惠)都是不可变的。
- 所有商品(静态和动态商品)都必须始终具有有效的来源和有效的目标。
CreateChild
会在动态优惠创建时检查此属性,并且每当来源或目标销毁时,优惠也会随之销毁。
总而言之,这意味着动态商品的来源始终比目标更早。如果来源较新,则表示其在目标创建时不存在,因此商品在创建时具有无效的来源。由于“创建顺序”是严格的总顺序,因此不可能存在依赖项循环。
处理无效路线
在撰写本文时,Component Manager 不会主动拒绝路由配置无效的组件清单。也就是说,如果某个功能由不公开该功能的来源提供,或者某个组件未收到其所使用的功能的提供,则在组件创建时不会引发错误。只有在组件尝试打开路由无效的 capability 时,才会引发错误。
按照“动态商品应像静态商品一样运作”的原则,我们将在此处实现相同的行为。组件可以为来源未公开的功能创建动态优惠。同样,父级组件可以创建动态子级组件,而无需为其使用的每项 capability 提供 offer。
今后,如果组件管理器在这些情况下更改为提前返回错误,我们也会更改 CreateChild
的合约以返回错误。
实现
这项变更面向用户的界面包括:
- 对
CreateChild
API 进行了向后兼容的更改,以接受动态商品列表作为参数。 - 一种按合集选择启用的设置,可让动态商品定位到该合集中的组件。(请参阅安全注意事项中的讨论。)
- 可选择启用的父级组件的许可名单。(注意:请参阅更新:移除许可名单)
(1) 的实现将完全在组件管理器本地进行。实现 (2) 和 (3) 可能涉及对解析 CML 文件的工具进行更改。无论采用哪种方式,都无需跨团队协调。
性能
除了对 CreateChild
和 DestroyChild
的调用(这两项操作很少发生且对性能没有重要影响)之外,这项更改应该不会对系统性能产生任何影响。即使是这些操作,预计也不会对性能产生不利影响。
工效学设计
商品销毁行为是新行为,可能会给依赖项突然消失的组件作者带来人体工学方面的挑战。
从目标组件的角度来看,确定在 offer 被销毁后发生了什么并不完全简单:它只会看到通道关闭。目标组件无法立即判断源组件是被销毁、停止还是有意关闭了通道。为了确定来源是否确实已销毁,目标必须尝试重新连接,并等待来自组件管理器的响应,以指明路由永久失败。
安全注意事项
这种设计本身就破坏了 CFv2 的一项重要安全目标:应声明所有 capability 路径(或潜在 capability 路径),以便对其进行审核。在测试之外使用动态商品时,需要确保除了组件清单之外,还可以通过其他机制审核潜在的 capability 路由。
使用此功能时,驱动程序框架会承担确保可审核潜在 capability 路径的责任。目前,这些规则会在多个位置进行编码:分散绑定规则、板级驱动程序的源代码、ACPI 等。这使得审核变得非常困难。这种差距并非新问题,需要由驱动程序框架来解决,但如何解决不在本 RFC 的讨论范围之内。
系统会向 CollectionDecl
添加一个可选设置,如果该设置存在,则父级组件可以将动态商品传递给在该集合中创建的组件。这将向 CML 审核员明确表明,动态商品正在使用,并且需要参阅组件清单之外的来源,以了解可能的 capability 路线的范围。
此外,组件框架团队将列入选择启用此功能的许可名单,以确保所有使用情况都适当。(注意:请参阅更新:移除许可名单)
后续工作
如果我们发现动态商品有更多用例,则可能需要向组件清单架构中引入商品上限。您可以使用优惠上限来限制可在运行时创建的动态优惠类型。
清单将包含以下声明:
- “协议
fuchsia.example.Foo
可从组件A
动态提供给集合B
中的任何组件”,或 - “可以动态地在集合
C
中的任意两个组件之间提供任何目录功能”。
对于初始的驱动程序框架用例,我们认为更粗略的收集级选择接受即可。在列入任何其他客户名单之前,必须重新评估此立场。
隐私注意事项
此提案不会向系统的任何部分公开其尚无权访问的数据,因此对隐私应该没有影响。
测试
我们将添加单元测试和集成测试覆盖率,与任何其他组件框架功能类似。驱动程序框架的集成测试将间接提供额外的覆盖率。
此功能不应对组件框架 API 的可测试性产生任何帮助或影响。使用集合和 Realm
协议已经需要在集成测试环境中使用真实的 Component Manager 实例,这种情况将继续存在。
文档
由于动态优惠将被列入许可名单,并且最初仅由单个客户端使用,因此不应更新高层级组件框架概念文档,以提及此功能的存在。关于 CreateChild
、DestroyChild
和 CollectionDecl
更改的文档应该足以满足要求。
在列入任何其他客户端的许可名单之前,必须重新评估此立场。
缺点、替代方案和未知情况
这种设计的主要缺点是,它会使审核 capability 路由变得更加复杂,因为并非所有潜在 capability 路由都显示在组件清单中。不过,这是驱动程序框架固有的:由于设备可以动态地添加和移除到计算机,因此驱动程序组件的拓扑本身就是动态的。静态优惠显然不够,因此故事必须变得更复杂。动态商品会尽可能反映静态商品的行为,以最大限度地减少这种额外的复杂性。
替代方案:capability 令牌
之前曾提出使用capability 令牌来解决一项相关问题。该提案可以被视为此 RFC 的泛化,其中动态优惠可以在不同领域之间跨越。来源组件的父级会“铸造”capability 令牌,并将其传递给另一个父级组件(可能会通过一组中间组件)。接收组件可以将此令牌传递到 CreateChild
,后者充当授权机制,用于证明双方家长都同意该优惠。
创建后,此动态商品应像静态商品一样运作,但主要区别在于这些动态商品可以在多个领域之间切换。静态优惠(以及主要方案中的动态优惠)始终是单个父级组件的本地优惠。
跨领域优惠的影响尚不完全明确,我们认为它们没有必要,因此已拒绝此选项。
替代方案:提供“直播功能提供商”
在主要提案中,动态商品是静态数据。产品代表有关组件管理器应如何路由功能请求的说明,但不会自行提供对功能的访问权限。
我们可以改为传递capability provider,即可用于直接获取 capability 的渠道。然后,CreateChild
会接受命名功能提供程序的矢量。当动态子组件尝试打开动态提供的 capability 时,该 Open
请求将被转发给 capability 提供程序。
父级组件可以通过以下方式获取此类 capability 提供程序:
- 自行实现 capability 提供程序,
- 调用
Realm
API 以获取其父级或子级的某个公开 capability 的 capability 提供程序,或者 - 通过通道从其他组件接收 capability 提供程序。
这将解锁与 capability 令牌相同的用例,但具有额外的好处(或可辩驳的缺点),即父级可以直接向其子级提供 capability 提供程序:只需在内存中启动服务器并将 capability 提供程序传递给 CreateChild
。
不过,capability 提供程序与 CFv2 路由框架的许多有价值的属性不兼容:capability 路由的可审核性、重启之间的持久性等。
替代方案:销毁
销毁作为优惠来源的动态组件时,除了销毁优惠之外,还有许多合理的行为。本部分将讨论这些替代方案。
禁止销毁来源
此 RFC 的早期草稿建议仅禁止销毁动态优惠的来源组件。组件会以这种方式“固定”下来,直到这些优惠的目标被销毁为止。
从一开始,这种方法就显得脆弱,并且存在潜在的人体工学问题(例如,“无法销毁组件”错误可能非常难以处理)。不过,随着 RFC-0101 的接受,这种做法变得完全不可行,因为该 RFC 引入了自动销毁组件的场景。
销毁传播
另一个提案是让销毁沿着商品传播。也就是说,如果动态商品的来源被销毁,目标也会被销毁,以此类推,以递归方式销毁。
此选项看起来很可怕,与其他 CFv2 行为不一致。在许多情况下,功能不可路由或不可用,但这些情况都不会导致依赖于这些功能的组件被强制终止或销毁,即使依赖项很强也是如此。组件可以观察其依赖项不可用的情况,并在需要时退出。
更新:移除许可名单
此功能已成功在动态组件中强制执行最小权限。
随着 web_instance
组件迁移到现代组件框架,此功能在 Chromium 代码库中可能很有用。目前,此组件使用旧版组件框架的功能启动动态子项。这些子项的功能集可以在运行时变化,并且在启用某些功能时,在旧版组件创建期间,系统会通过 additional_services
字段为子项提供额外的协议功能。动态优惠可让 web_instance
在运行时决定哪些动态子项将获得额外的功能,从而为将此行为迁移到现代组件框架提供清晰且简单的迁移路径。
因此,我们将移除许可名单,客户无需获得组件框架团队的批准即可使用该功能。
移除许可名单后,将会发生以下变化:
- 编写有关如何使用动态优惠的文档。
- 从 CMC 中移除
dynamic_offers
受限功能键。 - 删除
//tools/cmc/build/restricted_features
中的dynamic_offers
许可名单。