RFC-0101:具有编号句柄的动态组件

RFC-0101:具有编号句柄的动态组件
状态已接受
区域
  • 组件框架
说明

提供了一种将编号句柄传递给动态组件的方法。

Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-05-18
审核日期(年-月-日)2021-06-02

摘要

本文档建议向 fuchsia.sys2.Realm/CreateChild 添加一个新参数。此参数将包含一组编号句柄。创建的组件的运行程序在被要求运行组件时将收到这些句柄 。提供的句柄仅适用于在具有新类型持久性的集合中运行的组件:single-run。持久性为 durability 的集合中的组件在创建时启动,在停止时销毁。single-run这会将提供的句柄的作用域限定为组件的单次运行。

设计初衷

Starnix 是一个组件框架 v2 运行程序,可在 Fuchsia 上运行 Linux 二进制文件 。Starnix 实现 Linux 系统接口,以便在不进行修改的情况下运行这些二进制文件。

Starnix 提供了一个 ffx 插件,允许用户运行 Linux 组件 从主机命令行。Starnix 希望将 Linux 组件的 stdin/stdout/stderr 连接到一组套接字句柄。 此组件不会通过公开 FIDL 协议与系统交互。 相反,它会通过提供的套接字与用户交互。

设计

背景

组件框架中存在两个生命周期转换维度。 第一个维度与存在性(CreatedDestroyed)有关。第二个维度与组件的执行(StartedStopped)有关。在其他只有进程而没有组件的操作系统中,这些维度是等效的:创建进程与启动进程相同,进程在停止运行时会被销毁。

在为组件提供实参时,CreatedStarted 之间的区别非常重要。通常,单个组件实例可以多次启动和停止,因此组件管理器必须存储参数,以便在每次运行组件时提供这些参数。如果实参是句柄,这可能会有问题,因为并非所有句柄都可以重复。因此,任何不可重复的句柄都会被组件的单次运行“消耗”。

协议更新

背景中所述,创建组件与 启动组件不同。因此,提供给 CreateChild 的句柄必须是:

  1. 每次启动组件时都可用。
  2. 作用域限定为组件的单次运行。

由于并非所有句柄都可以重复(存储在组件管理器中以供后续运行),因此只有在每次运行时都从其来源提取新句柄的情况下,(1) 才是可行的。如需了解为何未选择在启动时提取句柄的方法,请参阅路由 句柄。幸运的是,(2) 是激励用例的可行解决方案。

系统会创建一个新表 ChildArgs,并将其作为参数添加到 Realm/CreateChild

protocol Realm {
  /// If args contains numbered_handles, the collection must have a durability
  /// of type `single-run`.
  CreateChild(CollectionRef collection, ChildDecl decl, ChildArgs args)
      -> () error fuchsia.component.Error;
}

resource table ChildArgs {
  /// The numbered handles for the component instance.
  ///
  /// Only PA_FD and PA_USER* handles are valid arguments, and inclusion of any other
  /// handles will result in an error.
  1: vector<fuchsia.process.HandleInfo>:N numbered_handles;
}

此外,fuchsia.component.runner.ComponentStartInfo 表将更新为包含编号句柄:

resource table ComponentStartInfo {
  6: vector<fuchsia.process.HandleInfo>:N numbered_handles;
}

运行程序会将这些句柄提供给组件,或者在运行程序不支持向组件提供编号句柄的情况下关闭这些句柄并返回错误。

集合持久性

通过 Realm 协议创建的组件位于集合中。 集合具有 durability 注解,用于指明集合中组件的生命周期语义。

系统将添加一个新的集合 durabilitysingle-run,以指明集合中的组件在创建时立即启动,在停止时销毁。ChildArgs.numbered_handles 只能与标记为 single-run 的集合搭配使用。这会将 ChildArgs 中的实参的作用域限定为组件的单次运行。

collections: [
    {
         name: "playground",
         durability: "single-run",
    }
],

实现

组件管理器将更新为存储 ChildArgs,直到它们传递给运行程序,并处理 single-run 集合语义。

向后兼容性

此更改在运行程序方面具有向后兼容性:运行程序不需要使用提供的编号句柄。如果运行程序不支持编号句柄,则应关闭这些句柄。

此更改不向后兼容 Realm 客户端。

此更改在 CML 方面具有向后兼容性:唯一的变化是添加了持久性枚举。

性能

由于句柄直接提供给组件管理器,因此预计不会对性能产生影响。

安全注意事项

此更改引入了一种让父项将任意编号句柄传递给子项的方法。这些句柄的交换由组件管理器和运行程序共同协调。只有在标记为 single-run 的集合中运行的组件才能以这种方式接收句柄。

测试

系统将扩展 CreateChild 的现有测试,以涵盖新实参。

缺点、替代方案和未知事项

缺点

与下面列出的替代方案相比,建议的解决方案存在以下几个缺点:

  • 这是一种启动组件的新方法:组件的生命周期不受功能绑定影响。
  • 提供的句柄对于组件框架的静态 CML 分析是不透明的。
  • 由于接收句柄的组件在停止时会被销毁,因此其持久性存储也会被清除。因此,使用持久性存储的组件不适合此功能。

将编号句柄作为功能进行路由

组件框架可以显式路由编号句柄。

有几种不同的方法可以实现此目的,但它们都具有以下“形状”。

引入一个由编号句柄的来源实现的协议:

protocol HandleProvider {
    Get(string handle_name) -> (fuchsia.process.HandleInfo handle);
}

然后,此协议将转换为功能:

capabilities: [
    {
        handle_provider: "stdin",
        path: "/svc/fuchsia.component.HandleProvider",
    },
],
expose: [
    {
        handle_provider: "stdin",
        from: "self",
    },
],

然后,可以通过 CML 将其路由到目标,就像其他功能一样。

福利

  • 可以发展为支持有关句柄的更多类型信息。
  • 使句柄的路由对组件框架显式可见。

缺点

组件在提取句柄之前无法启动。 即使可以通过在组件管理器中缓存句柄来分摊性能影响,激励用例也不会从中受益,因为组件的每次运行的句柄都不同。在建议的设计中,主机代码可以“触发并忘记”启动组件的请求,并继续执行,就好像调用成功一样。在这种替代方案中,主机代码需要等待关联的句柄请求返回。

句柄提供程序并非始终可以通过静态路由访问。在激励用例中,句柄提供程序位于宿主机上,通过 Overnet 连接到 Fuchsia 设备。可以通过“尽可能远”地路由,然后让“边缘”组件通过临时机制提取句柄,然后再将其返回给组件框架来解决此问题。这会给想要使用该功能的开发者带来额外的负担。

句柄提供程序无法区分来自给定功能路由的句柄请求。具体而言,请考虑激励用例:

  1. 用户从主机在不同的终端中启动两个 Linux 组件。
  2. 组件在集合中实例化。
  3. 句柄提供程序收到两个针对同一句柄的请求。

此时,句柄提供程序无法知道哪个组件与哪个句柄请求相关联。可以通过引入额外的客户端标识机制来解决此问题,但这比建议的设计复杂得多。

此解决方案比建议的设计更复杂,需要投入更多精力。 也就是说,建议的设计不会阻止或冲突未来对编号句柄的显式路由。

StartChild

此替代方案建议将编号句柄作为实参添加到 Realm 上的 StartChild 调用。这与建议的设计类似,但缺点是会在组件绑定到组件提供的功能(这将启动组件)与 StartChild 调用之间引入竞争。具体而言,组件只有在 StartChild 调用赢得竞争的情况下才会收到编号句柄,因为如果组件已启动,则不清楚如何传递句柄。

启动器协议

可以通过 Starter 协议传递编号句柄。启动器协议可用于启动组件,由组件管理器代表组件实现,并且可以像任何其他协议一样进行路由(即,组件可以由不是其父项的组件启动)。

此协议可以像任何其他协议一样进行路由,因此客户端可以使用它来启动位于组件层次结构中任意位置的组件。

启动器协议包含一个接受编号句柄作为实参的方法:

[Discoverable]
protocol StarterWithArgs {
    /// Start the component that is bound to this protocol.
    /// If the component is already running, the call returns an error.
    Start(StartArgs start_args) -> () error fuchsia.component.Error;
};

此提案与 StartChild 非常相似。以这种方式公开的协议的优势在于更易于审核和列入许可名单,但也会在调用 Start 与绑定到组件公开的任何其他功能之间引入竞争。此外,由于客户端并不总是父项,因此不一定能够重启组件以提供实参,这会加剧排序问题。可以通过向协议添加停止方法来解决此问题。

客户端还需要在启动器协议和 Realm 协议之间协调子项管理,而不是仅通过 Realm 协议管理子项。