协议开放生命周期

本文档介绍了组件尝试连接到其命名空间中的协议时发生的步骤。

这些步骤适用于在组件管理器下运行的以下模型:

大致步骤如下:

构建组件的命名空间

命名空间是指在组件启动时向其提供的一组目录。每个目录都与一个文件系统路径相关联,该路径可供组件访问其他组件提供的文件和协议。

这些目录采用通道句柄形式,组件可以通过这些通道使用 fuchsia.io.Directory FIDL 协议

例如,所有组件都会在 /pkg 收到指向其创建来源软件包内容的句柄。这意味着,组件可以通过读取 /pkg/bin 的内容来查看其软件包中提供的二进制文件。

组件的清单中的 use 声明决定了如何填充命名空间。使用协议功能时...

use: [
    {
        protocol: "fuchsia.example.Foo",
    },
]

...组件管理器将向协议的父目录的组件命名空间添加条目。在此示例中,该协议的命名空间路径为 /svc/fuchsia.example.Foo(默认路径分配),这意味着组件管理器将向命名空间添加 /svc 的句柄。

/svc 目录由组件管理器本身提供,并且组件管理器将在组件的生命周期内响应对此目录的协议请求。

命名空间中显示内容的确切语义因 capability 类型而异。例如,如果使用目录功能而非协议功能...

use: [
    {
        directory: "example-data",
        rights: [ "r*" ],
        path: "/example/data",
    },
]

...命名空间中显示的是目录本身的句柄,而不是父目录的句柄。在此示例中,这意味着 /example/data 的句柄将显示在命名空间中,而如果此路径用于协议功能,则 /example 将显示在命名空间中。

组件打开协议

当组件想要打开协议时,它会创建一个新的通道对,并通过其命名空间中的通道发送该对的一端的 Open 请求。例如,如果组件想要打开与 /svc/fuchsia.example.Foo 的连接,则新通道对的一端将通过其命名空间中的 /svc 句柄发送。然后,该组件可以通过该通道调用 fuchsia.example.Foo 协议。

由于包含协议 (/svc) 的目录由组件管理器提供,因此组件管理器将通过组件发送的 Open 请求接收新通道的服务器端。然后,组件管理器必须确定通过此通道提供协议的组件。

Open 触发功能路由

如需确定通过通道提供协议的组件,组件管理器必须按照 offerexpose 声明遍历组件树,以查找 capability 的来源。此过程称为capability 路由

从触发 capability 路由的组件的父级开始,组件管理器会检查每个组件的清单,查找目的地与子组件匹配的 offer 声明。优惠将指定 parentself 或子项名称作为来源。如果优惠来自组件的领域,则会继续沿着树向上遍历;如果优惠来自组件的某个子级,则会沿着树向下遍历到该子级。

路由开始沿树向下遍历后,会查找 expose 声明,这些声明会指定 self 的来源或子项的名称。如果 capability 来自子级,则组件管理器会继续向下遍历树。

找到源为 selfofferexpose 声明后,组件管理器便可将通道交给该组件。

如果链条在任何步骤中无效,组件管理器将记录错误并关闭从 Open 调用收到的通道。这可能由多种情况导致,例如:

  • 组件 C 提供了 parent 中的 capability,但其父级 R 未向 C 提供该 capability。
  • 组件 C 通过其子组件 D 提供了 capability,但子组件 D 未向 C 公开该 capability。

例如,请考虑以下组件及其清单的树状结构(为简洁起见,省略了 program 块和运行器设置):

    C
   / \
  B   D
 /
A

A.cml:
{
    // ...
    capabilities: [
        {
            protocol: "fuchsia.example.Foo",
        },
    ],
    expose: [
        {
            protocol: "fuchsia.example.Foo",
            from: "self",
        },
    ],
}

B.cml:
{
    // ...
    expose: [
        {
            protocol: "fuchsia.example.Foo",
            from: "#A",
        },
    ],
    children: [
        {
            name: "A",
            url: "fuchsia-pkg://fuchsia.com/a#meta/a.cm",
        },
    ]
}

C.cml:
{
    // ...
    offer: [
        {
            protocol: "fuchsia.example.Foo",
            from: "#B",
            to: [ "#D" ],
        },
    ]
    children: [
        {
            name: "B",
            url: "fuchsia-pkg://fuchsia.com/b#meta/b.cm",
        },
        {
            name: "D",
            url: "fuchsia-pkg://fuchsia.com/d#meta/d.cm",
        },
    ]
}

D.cml:
{
    // ...
    use: [
        {
            protocol: "fuchsia.example.Foo",
        },
    ],
}

D 在其命名空间中对 /svc/fuchsia.example.Foo 调用 Open 时,组件管理器将遍历树,以查找应提供此协议的组件。它将从 D 的父级 C 开始,然后:

  • 查找 fuchsia.example.FooDoffer 声明,并确认它来自子级 B
  • B 中查找 fuchsia.example.Fooexpose 声明,并确认它来自 A
  • A 中查找 fuchsia.example.Fooexpose 声明,并确认它来自 self。这意味着,A 是提供 D 尝试使用的 capability 的组件。

现在,组件管理器找到了提供方组件,可以尝试通过 Open 请求传递它收到的通道。

绑定到组件并发送协议通道

找到提供方后,客户端组件现在会绑定到提供方。如果组件当前处于停止状态,这将导致其开始运行。

每个组件在启动后都会在其句柄表中收到指向出站目录的服务器句柄。 绑定组件后,组件管理器会将协议通道的服务器端转发到提供方组件的传出目录,位于提供方组件的 offerexpose 声明中的源路径下。

在上例中,组件管理器将通过组件 A 的出站目录句柄向 /svc/fuchsia.example.Foo 路径发送 Open 请求,并提供在组件管理器调用 Open 时从组件 D 收到的通道句柄。

然后,组件 A 会接收此请求,并开始通过其所提供的通道响应消息。

由于组件管理器会直接将协议通道的服务器端转发到提供方组件的传出目录,因此它不会参与消息代理,并且在完成 capability 路由后完全不在视野范围内。与其他组件建立连接后,它们会直接相互通信,中间没有仲裁者。

注意事项

运行时不可预测性

由于 capability 路由的运行时性质以及提供 capability 的组件的行为,在给定组件尝试访问其命名空间中的 capability 之前,无法确定该组件能否成功访问该 capability。即使该 capability 存在有效的提供/公开链,软件包更新也可能会在运行时破坏此链,并且声称在清单中提供 capability 的组件在运行时完全有可能无法提供该 capability。

提供的功能与 Ambient 功能

某些功能由组件框架本身提供,可直接由组件使用(或将隐式提供给组件),而无需其父级提供这些功能。目前,这些国家/地区包括:

  • /pkg:指向用于创建组件的软件包的句柄。
  • /svc/fuchsia.component.Realm:一种协议,组件可以使用该协议来管理自己的领域。