RFC-0108:组件 binder 协议 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 框架提供的协议,允许组件启动其他组件。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-05-21 |
审核日期(年-月-日) | 2021-06-30 |
总结
此 RFC 引入了由框架提供的新协议 fuchsia.component.Binder
,该协议可让组件启动提供该协议的其他组件。
设计初衷
fuchsia.sys2.Realm 协议是框架提供的 API,允许组件在运行时操纵其领域。借助此协议,组件可以创建子组件并绑定到其公开的功能,由运行时决策而不仅仅是静态声明驱动。组件可以通过调用 BindChild
方法来绑定到子级的功能。此方法在成功执行后会启动提供的子组件(如果尚未启动),并打开与子组件的公开目录支持的 fuchsia.io.Directory
协议实例的连接。子级的公开目录是一个目录,其中包含子级在其清单中公开的所有功能。
这种方法有两个缺点。首先,它目前过载,因为它满足两个用例。它允许组件绑定到其子组件的 capability,并且还允许组件启动子组件。其次,它与组件框架的功能模型不一致。大多数组件都是为了满足功能请求而启动。换句话说,组件绑定到功能,而不是提供这些功能的组件。这是封装的一项重要功能,因为如果组件与功能而不是直接交互,则可以替换实现组件,而无需对客户端进行任何更改。
因此,BindChild
将被废弃。替换方法 OpenExposedDir
将添加到 fuchsia.sys2.Realm
协议中,该方法可让父级绑定到其子级的功能。此方法与 BindChild
之间的显著区别在于,在新方法中,当且仅当父项绑定到其 capability 之一时,才会启动子级。这种语义更改更符合组件框架的设计原则。此迁移工作已经开始,可通过 fxr/531142 进行跟踪。
不过,仅用 OpenExposedDir
替换 BindChild
是不够的。就数量和对平台的重要性而言,有大量用例依赖于 BindChild
的自动启动行为。在这些情况下,父组件不会绑定到任何子项的公开功能,或者父组件启动一个不提供任何功能的组件。这种模式可以在某些集成测试、驱动程序和会话元素中观察到。对于此用例,组件框架团队必须提供一个解决方案来供客户启动组件。
设计
组件框架将引入框架提供的新协议 fuchsia.component.Binder
。此功能允许作者将组件声明为直接可绑定。想要启动其他组件的组件会像处理任何其他功能一样使用该协议。组件管理器将位于此协议的服务器端,建立连接后,它会启动提供此功能的组件。通过观察连接客户端的 ZX_CHANNEL_PEER_CLOSED
,可以捕获目标组件的终止情况。
library fuchsia.component;
/// A framework-provided protocol that allows components that use it to start the
/// component that exposes it.
///
/// Note: The component doesn't need to serve this protocol, it is implemented
/// by the framework.
[Discoverable]
protocol Binder {};
组件作者只需公开以下协议:
{
expose: [
{
protocol: "fuchsia.component.Binder",
from: "framework", // Note that this is implemented by the framework on the component's behalf.
},
],
}
将启动此类组件的组件将通过绑定到公开的 capability 来实现:
{
use: [
{
protocol: "fuchsia.component.Binder",
from: "parent",
},
],
}
该方案的主要好处是,直接启动成为组件 API 的一部分。您可以在清单文件中审核可直接启动的组件,尤其是未公开功能的组件。此外,启动另一个组件并不仅限于启动直接子组件的父组件。不过,fuchsia.component.Binder
协议可以像其他功能一样路由到任何位置。
实现
实现这种设计不需要进行很多更改。引入此协议应该进行一两次 Gerrit 更改。
此功能发布后,BindChild
的用户将根据其用例改为使用 OpenExposedDir
或建议的 fuchsia.component.Binder
协议。
在 BindChild
的所有用例完成迁移后,该方法将从 fuchsia.sys2.Realm
协议中移除。
性能
此协议会造成性能下降。目前,父组件可以调用 BindChild
来启动子组件。在此方案之后,父级组件必须调用 OpenExposedDir
,然后打开 fuchsia.component.Bind
才能达到相同的效果。不过,这种回归问题应该没有太大意义,因为无论如何,这些事件都很少见。
安全注意事项
该协议不应引起安全问题。此协议是可路由的,因此您可以通过检查清单文件来启动哪些组件可审核哪些其他组件。
隐私注意事项
此协议不应引发隐私问题,因为这只是允许一种机制启动其他组件。
测试
系统将通过单元测试和集成测试对此功能进行测试。
此外,借助此功能,组件开发者还可以更轻松地测试其他组件的附带效应。例如,在“诊断”中,一些集成测试会断言检查 VMO 的状态。在这种情况下,驱动程序组件会启动一个用于操控 VMO 的木偶组件,然后驱动程序组件会对 VMO 的内容做出断言。提议的功能将允许驱动程序组件启动木偶组件,而无需无关的 FIDL 协议。
文档
组件框架领域文档中会记录 FIDL API,并更广泛地介绍此功能。
缺点、替代方案和未知情况
单运行组件
最近已接受一个相关的 RFC,该 RFC 还可允许组件启动子组件。该方案将能够满足由 BindChild
填充的部分用例,但并非全部。值得注意的是,通过所提议的机制启动的组件必须位于集合中,而 fuchsia.component.Binder
将适用于所有组件。
fuchsia.sys2.Realm/StartChild
另一种方法是通过添加启动子项的方法来扩展 fuchsia.sys2.Realm
协议。此方法 StartChild
将接受一个参数 ChildRef
,该参数将是对静态声明的子项或动态创建的子项(集合)的引用。这也是 BindChild
和 OpenExposedDir
将接受的参数。此方法会返回一个 Zircon Event 对象,该对象会在子组件停止运行后收到信号。子组件的生命周期将与父组件相关联。如果父组件已停止,则子组件也会停止。
library fuchsia.sys2;
[Discoverable]
protocol Realm {
/// Start child component instance, if it isn't already. Returns a Zircon
/// Event object that clients can use to observe when the child component stops.
/// When the child component stops, Component Manager will set this object's
/// ZX_EVENT_SIGNALED bit.
StartChild(ChildRef child) -> (zx.handle:EVENT event) error fuchsia.component.Error;
};
归根结底,这种替代方法是不可取的,因为它允许任何组件直接启动。仅当组件声明时,才允许直接启动,与功能绑定的附带效应相反。由于 StartChild
将采用 ChildRef
,因此将允许启动任何组件。对于集合,启动了哪些组件只能在运行时观察。
绑定方法
最后,可以使用一个方法来触发启动,而不是引入上面建议的空 Binder 协议。
library fuchsia.component;
[Discoverable]
protocol Binder {
/// Start the associated component instance.
/// This method is reentrant and safe for concurrency.
/// Calling `Bind` on an already-binded component is a no-op.
/// When the child component stops, the `ZX_EVENTPAIR_PEER_CLOSED` signal
/// will be asserted on the `event` object.
Bind(zx.handle:EVENTPAIR event) -> () error fuchsia.component.Error;
};
虽然此选项使启动触发器更加明确(即,作为对方法调用的响应,而不是连接到协议),但它不如该方案可行,因为它会为客户端增加额外的障碍。必须在调用该方法之前设置事件对对象,而不会获得任何附加功能。
Binder 功能
组件框架还可以引入由建议的 fuchsia.component.Binder
协议提供支持的新功能。
// a.cml
{
capabilities: [
{
binder: "a",
},
],
expose: [
{
binder: "a",
from: "self",
},
],
}
新功能具有许多优势,例如:
- 不同的类型有助于更好地区分启动功能。例如,我们可以为与协议不同的 binder 功能提供命名惯例。
- 在转换为绑定的内容方面,与其他功能保持一致(连接 == 绑定)
- 不会忘记
framework
关键字(尽管我们可以为此添加 cmc 检查)。
不过,尽管引入新功能会带来好处,但会增加概念性开销,也就是说,开发者需要学习和了解并支持 CF。框架提供的协议更直接,也更容易概念。