| RFC-0108:组件 binder 协议 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 框架提供的协议,允许组件启动其他组件。 |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2021-05-21 |
| 审核日期(年-月-日) | 2021-06-30 |
摘要
此 RFC 引入了一个新的框架提供的协议 fuchsia.component.Binder,该协议将允许组件启动公开它的其他组件。
设计初衷
fuchsia.sys2.Realm 协议 是一个由框架提供的 API,允许组件在运行时操纵其领域。借助此协议,组件可以创建子组件并绑定到其公开的功能,而这些功能由运行时决策驱动,而不仅仅是静态声明。组件可以通过调用 BindChild 方法绑定到子功能。此方法在成功执行后,会启动所提供的子组件(如果尚未启动),并打开与 fuchsia.io.Directory 协议实例的连接,该实例由子组件的公开目录提供支持。子进程的公开目录是一个目录,其中包含子进程在其清单中公开的所有功能。
此方法有两大缺点。首先,它目前处于过载状态,因为其满足了两个使用情形。它允许组件绑定到其子组件的功能,还允许组件启动子组件。其次,它与组件框架的功能模型不一致。大多数组件都会启动,以满足对功能的需求。换句话说,组件绑定到功能,而不是提供这些功能的组件。这是封装的一项重要功能,因为如果组件与功能(而非组件本身)直接互动,则可以替换实现组件,而无需对客户端进行任何更改。
因此,BindChild 将被弃用。将向 fuchsia.sys2.Realm 协议添加替代方法 OpenExposedDir,以允许父级绑定到其子级的功能。此方法与 BindChild 之间的显著区别在于,在新方法中,只有当父级绑定到子级的某项功能时,才会启动子级。这种语义上的变化更符合组件框架的设计原则。此迁移的相关工作已开始,您可以在 fxr/531142 中跟踪进度。
不过,仅仅将 BindChild 替换为 OpenExposedDir 是不够的。有大量(就数量和对平台的重要性而言)用例依赖于 BindChild 的自动启动行为。在这些情况下,父组件不会绑定到任何子组件的公开功能,或者父组件启动的组件未公开任何功能。在某些集成测试、驱动程序和会话元素中可以观察到这种模式。对于此使用情形,组件框架团队必须为客户提供一种启动组件的解决方案。
设计
组件框架将引入新的框架提供的协议 fuchsia.component.Binder。借助此功能,作者可以将组件声明为可直接绑定的组件。希望启动其他组件的组件随后会像使用任何其他功能一样使用该协议。Component Manager 将位于此协议的服务器端,一旦建立连接,它就会启动公开此功能的组件。通过观察连接的客户端上的 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.
},
],
}
将启动此类组件的组件将通过绑定到公开的功能来执行此操作:
{
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 将有文档记录,并且此功能将在组件框架 Realm 文档中进行更广泛的记录。
缺点、替代方案和未知因素
单次运行组件
最近,一项相关的 RFC 已获接受,该 RFC 也将允许组件启动子组件。该提案将能够满足 BindChild 所涵盖的部分用例,但并非全部。值得注意的是,通过所提议的机制启动的组件必须位于集合中,而 fuchsia.component.Binder 适用于所有组件。
fuchsia.sys2.Realm/StartChild
另一种方法是通过添加启动子进程的方法来扩展 fuchsia.sys2.Realm 协议。此方法 StartChild 将接受一个参数 ChildRef,该参数将是对静态声明的子级或动态创建的子级(集合)的引用。此参数与 BindChild 接收的参数相同,并且 OpenExposedDir 将采用此参数。此方法将返回一个 Zircon 事件对象,该对象将在子组件停止时发出信号。子级组件的生命周期将与父级组件相关联。如果父组件停止,子组件也会停止。
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;
};
虽然此选项使启动触发器更加明确(即作为对方法调用的响应,而不是连接到协议),但它不如提案可行,因为这会给客户端增加额外的障碍。在调用该方法之前,必须设置一个事件对对象,但不会获得任何新增功能。
装订机功能
组件框架还可以引入由提议的 fuchsia.component.Binder 协议支持的新功能。
// a.cml
{
capabilities: [
{
binder: "a",
},
],
expose: [
{
binder: "a",
from: "self",
},
],
}
这项新功能可带来诸多好处,例如:
- 不同的类型有助于更好地区分启动功能。例如,我们可以推广与协议不同的 binder 功能命名惯例。
- 在转换为绑定(连接 == 绑定)方面与其他功能保持一致
- 不会忘记
framework关键字(不过我们可以为此添加 cmc 检查)。
不过,尽管有诸多好处,但引入新功能会增加概念开销,也就是说,开发者需要学习和理解新事物,CF 需要支持新事物。框架提供的协议更直接,也更易于概念化。