协议开放生命周期

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

这些步骤

大体上讲,请按以下步骤操作:

构建组件的命名空间

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

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

例如,所有组件都将收到在 /pkg 中创建它们时所用的软件包内容的句柄。这意味着,组件可以通过读取 /pkg/bin 的内容来查看其软件包中可用的二进制文件。

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

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

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

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

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

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 的源代码。此过程称为“功能路由”

从触发功能路由的组件的父级开始,组件管理器将检查每个组件的清单,查找其目的地与子级匹配的 offer 声明。优惠将指定来源:“parent”“self”或“孩子姓名”。如果报价来自组件的领域,则继续向上遍历规则树;如果报价来自组件的某个子级,则会沿着方案树向下遍历到相应子级。

一旦路由开始沿着树向下遍历,它将查找 expose 声明,这些声明将指定来源 self 或子项的名称。如果功能来自子级,组件管理器将继续向下遍历树。

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

如果在链中的任意步骤无效,组件管理器就会记录错误并关闭从 Open 调用收到的渠道。这可能是由各种情况导致的,例如:

  • 组件 C 提供了 parent 中的功能,但其父级 R 没有向 C 提供该功能。
  • 组件 C 提供了其子 D 中的功能,但子 D 没有向 C 公开该功能。

例如,请考虑以下组件树及其清单(为简洁起见,省略了 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
  • 查找来自 Bfuchsia.example.Fooexpose 声明,您会看到它来自 A
  • 查找来自 Afuchsia.example.Fooexpose 声明,您会看到它来自 self。这意味着,A 是提供 D 尝试使用的功能的组件。

找到提供程序组件后,组件管理器便可尝试移交通过 Open 请求收到的频道。

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

找到提供程序后,客户端组件现在会绑定到提供程序。如果当前已停止,这会导致组件开始运行。

绑定组件后,组件管理器会将协议通道的服务器端转发到提供组件的传出目录(位于提供组件的 offerexpose 声明中的源路径下)。

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

然后,由组件 A 接收该请求并开始通过其给定的通道响应消息。

由于组件管理器直接将协议通道的服务器端转发到提供程序组件的传出目录,因此它不参与消息代理,在功能路由完成后就完全不位于画面中。与另一个组件建立连接后,它们会直接相互通信,中间不会进行任何仲裁。

注意事项

运行时不可预测性

由于 capability 路由的运行时性质和提供 capability 的组件的行为,在给定组件尝试访问其命名空间中的 capability 之前,我们无法知道该组件能否成功访问其命名空间中的 capability。即使存在针对 capability 的有效产品/公开链,软件包更新也可能会在运行时破坏此链,并且某个声明在其清单中提供 capability 的组件完全有可能在运行时无法做到。

提供的功能与 Ambient 权能

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

  • /pkg:创建组件时所依据的软件包的句柄。
  • /svc/fuchsia.component.Realm:组件可用于管理自己的领域的一种协议。