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

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

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

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

总结

本文档提议向 fuchsia.sys2.Realm/CreateChild 添加一个新参数。此参数将包含一组带编号的句柄。当系统要求所创建的组件运行组件时,运行程序将收到这些句柄。提供的句柄将仅适用于在具有新类型耐用性的集合中运行的组件:single-run。集合中耐用性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);
}

然后,此协议会转换为一项 capability:

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

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

权益

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

缺点

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

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

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

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

此时,句柄提供程序无法得知哪个组件与哪个句柄请求相关联。这可以通过引入其他客户端识别机制来解决,但这比我们提出的设计要复杂得多。

与建议的设计相比,此解决方案的规模更大,也更复杂。 即便如此,我们提出的设计也不会阻止未来对已编号的句柄的显式路由发生冲突,也不会造成冲突。

StartChild

此替代方案建议将带编号的句柄作为参数添加到 RealmStartChild 调用中。这与所提议的设计类似,但其缺点是在组件绑定与组件提供的功能(将启动组件)的绑定与正在进行的 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 协议管理子项。