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