本文档介绍了组件尝试连接到其命名空间中的协议时发生的步骤。
大致步骤如下:
- 组件管理器会根据其清单中的
use
声明构建组件的命名空间。 - 运行后,组件将尝试在其命名空间中打开协议。
- 组件管理器会收到此
Open
请求,并执行查找提供该协议的组件所需的功能路由。 - 组件管理器绑定到提供协议的组件,并将
Open
请求连接到该组件
构建组件的命名空间
命名空间是指在组件启动时向其提供的一组目录。每个目录都与一个文件系统路径相关联,该路径可供组件访问其他组件提供的文件和协议。
这些目录采用通道的句柄形式,组件可以通过这些通道使用 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
触发功能路由
如需确定通过通道提供协议的组件,组件管理器必须按照 offer
和 expose
声明遍历组件树,以查找 capability 的来源。此过程称为capability 路由。
从触发 capability 路由的组件的父级开始,组件管理器会检查每个组件的清单,查找目的地与子组件匹配的 offer
声明。优惠将指定 parent
、self
或子项名称作为来源。如果优惠来自组件的领域,则会继续沿着树向上遍历;如果优惠来自组件的某个子级,则会沿着树向下遍历到该子级。
路由开始沿树向下遍历后,会查找 expose
声明,这些声明会指定 self
的来源或子项的名称。如果 capability 来自子级,则组件管理器会继续向下遍历树。
找到源为 self
的 offer
或 expose
声明后,组件管理器便可将通道交给该组件。
如果链条在任何步骤中无效,组件管理器将记录错误并关闭从 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.Foo
到D
的offer
声明,并确认它来自子级B
。 - 在
B
中查找fuchsia.example.Foo
的expose
声明,并确认它来自A
。 - 在
A
中查找fuchsia.example.Foo
的expose
声明,并确认它来自self
。这意味着,A
是提供D
尝试使用的 capability 的组件。
现在,组件管理器找到了提供方组件,可以尝试通过 Open
请求传递它收到的通道。
绑定到组件并发送协议通道
找到提供方后,客户端组件现在会绑定到提供方。如果组件当前处于停止状态,这将导致其开始运行。
每个组件在启动后都会在其句柄表中收到指向出站目录的服务器句柄。
绑定组件后,组件管理器会将协议通道的服务器端转发到提供方组件的传出目录,位于提供方组件的 offer
或 expose
声明中的源路径下。
在上例中,组件管理器将通过组件 A
的出站目录句柄向 /svc/fuchsia.example.Foo
路径发送 Open
请求,并提供在组件管理器调用 Open
时从组件 D
收到的通道句柄。
然后,组件 A
会接收此请求,并开始通过其所提供的通道响应消息。
由于组件管理器会直接将协议通道的服务器端转发到提供方组件的传出目录,因此它不会参与消息代理,并且在完成 capability 路由后完全不在视野范围内。与其他组件建立连接后,它们会直接相互通信,中间没有仲裁者。
注意事项
运行时不可预测性
由于 capability 路由的运行时性质以及提供 capability 的组件的行为,在给定组件尝试访问其命名空间中的 capability 之前,无法确定该组件能否成功访问该 capability。即使该 capability 存在有效的提供/公开链,软件包更新也可能会在运行时破坏此链,并且声称在清单中提供 capability 的组件在运行时完全有可能无法提供该 capability。
提供的功能与 Ambient 功能
某些功能由组件框架本身提供,可直接由组件使用(或将隐式提供给组件),而无需其父级提供这些功能。目前,这些国家/地区包括:
/pkg
:指向用于创建组件的软件包的句柄。/svc/fuchsia.component.Realm
:一种协议,组件可以使用该协议来管理自己的领域。