RFC-0108:组件 binder 协议

RFC-0108:组件 binder 协议
状态已接受
领域
  • 组件框架
说明

框架提供的协议,允许组件启动其他组件。

Gerrit 更改
  • 533701
作者
审核人
提交日期(年-月-日)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,该参数将是对静态声明的子项或动态创建的子项(集合)的引用。这也是 BindChildOpenExposedDir 将接受的参数。此方法会返回一个 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",
        },
    ],
}

新功能具有许多优势,例如:

  1. 不同的类型有助于更好地区分启动功能。例如,我们可以为与协议不同的 binder 功能提供命名惯例。
  2. 在转换为绑定的内容方面,与其他功能保持一致(连接 == 绑定)
  3. 不会忘记 framework 关键字(尽管我们可以为此添加 cmc 检查)。

不过,尽管引入新功能会带来好处,但会增加概念性开销,也就是说,开发者需要学习和了解并支持 CF。框架提供的协议更直接,也更容易概念。