在 Fuchsia 中,所有通信都通过 功能(例如 协议和服务)进行。组件框架负责处理组件之间的功能路由,其中包括驱动程序和非驱动程序。 功能会添加到某个组件的出站目录中,如果路由正确,则会在另一个组件的入站命名空间中提供。
驱动程序主要使用 服务功能 (本文档为简洁起见,将其称为“服务”)与其他驱动程序和非驱动程序进行通信。涉及驱动程序的服务通信与非驱动程序之间的服务通信只有两个区别,即服务路由方式。
- 驱动程序到驱动程序的路由使用动态功能路由,其中驱动程序 管理器会在子驱动程序 的组件创建期间创建从父节点到子节点的路由。
- 源自驱动程序并传递给非驱动程序的服务是 聚合的,因为所有驱动程序都位于 集合中。这意味着,驱动程序公开的所有服务实例都将聚合 到以服务名称命名的同一目录中
服务结构解析
服务实例表示包含一个或多个协议的目录。所包含的协议称为服务成员。服务成员有自己的
类型,然后可以在
ServiceMemberWatcher等工具中使用,以指示服务以及其中的特定协议。
请考虑以下 FIDL 定义:
library fuchsia.example;
protocol Echo { ... }
protocol Broadcast { ... }
// Note: most services only contain one protocol.
service EchoService {
speaker client_end:Broadcast;
loopback client_end:Echo;
};
下面介绍了示例中的值:
fuchsia.example.EchoService是服务功能- 这在
.cml文件中使用,是服务目录的名称
- 这在
fuchsia_example::EchoService是服务的 c++ 类型fuchsia_example::EchoService::Speaker是 服务成员-
的 c++ 类型
- 此类型实际上仅供
ServiceMemberWatcher等工具使用。 - 此服务成员将以
speaker的形式显示在<instance_name>目录中 - 连接到
/svc/fuchsia.example.EchoService/<instance_name>/speaker将 为您提供一个通道,该通道需要协议fuchsia_example::Broadcast。
- 此类型实际上仅供
驱动程序端:宣传服务
为每个协议创建服务器实现
对于服务中的每个协议,您都必须具有 fidl 协议的服务器实现 - 一个继承自 fidl::WireServer 或 fidl::Server 的类。
如果您的服务有多个具有相同协议的成员,则可以为每个协议使用相同的服务器实现。
创建 ServiceInstanceHandler
接下来,您需要创建一个 ServiceInstanceHandler:一组函数,每当客户端连接到服务的协议时,都会调用这些函数。幸运的是,fidl::ServerBindingGroup 使此操作变得非常简单。
将绑定组添加到服务器类:
fidl::ServerBindingGroup<fuchsia_examples::Echo> loopback_bindings_;
fidl::ServerBindingGroup<fuchsia_examples::Broadcast> speaker_bindings_;
然后,您可以创建 ServiceInstanceHandler。在此示例中,this 指向上一步中确定的服务实例。
fuchsia_examples::EchoService::InstanceHandler handler({
.loopback = loopback_bindings_.CreateHandler(this, dispatcher(), fidl::kIgnoreBindingClosure),
.speaker = speaker_bindings_.CreateHandler(this, dispatcher(), fidl::kIgnoreBindingClosure),
});
请注意,您需要为服务中的每个协议单独调用 ServerBindingGroup 或至少 CreateHandler。(大多数服务只有一个协议。)
宣传服务
在 DFv1 和 DFv2 中,宣传服务略有不同:
DFv1
zx::result add_result =
DdkAddService<fuchsia_examples::EchoService>(std::move(handler));
DFv2
zx::result add_result =
outgoing()->AddService<fuchsia_examples::EchoService>(std::move(handler));
驱动程序到驱动程序:在创建子项时添加 offer
如果您刚刚宣传的服务应路由到子项,则需要在创建子项时将其添加到传入的 offer 中。对于此示例:
// Add a child with a `fuchsia_examples::EchoService` offer.
std::vector<fuchsia_driver_framework::NodeProperty2> properties = {};
zx::result child_result = AddChild("my_child_name", properties,
std::array{fdf::MakeOffer2<fuchsia_examples::EchoService>()});
这会指示驱动程序管理器将该服务从您路由到您的子项。
由于实例名称不会随机化,因此建议将实例名称指定为子组件的名称,作为 AddService 的实参。
这在 DFv1 中是不可能的。如需详细了解如何将服务路由到
子项,请参阅
服务的 DFv2 迁移指南。
路由服务
现在,您的服务已宣传,需要从驱动程序中 expose,并由客户端 use。如果客户端是子驱动程序,则无需进行额外的路由。如果客户端是非驱动程序组件,则必须将服务 expose 到客户端和驱动程序共享的祖先节点(通常是 #core 组件),然后 offer 到客户端。
从驱动程序中公开服务
您必须将服务添加到驱动程序的 cml 文件中,同时添加到 Capability 和 Expose 字段中。功能节定义了功能,而公开则指定了功能的来源。
capabilities: [
{ service: "fuchsia.examples.EchoService" },
],
expose: [
{
service: "fuchsia.examples.EchoService",
from: "self",
},
],
路由服务
在驱动程序和组件之间,将服务添加到 offer 和 expose 字段。您很可能需要修改多个 cml 文件。如需了解其他驱动程序如何路由其服务,请参阅以下示例。请注意您的服务需要到达哪个 realm。例如,对于 bootstrap realm 中的组件,您的服务必须来自 #boot-drivers 或 #base-drivers。
{
service: "fuchsia.example.EchoService",
from: "parent",
to: "#my_client",
},
如需了解其他驱动程序如何路由其服务,请参阅以下示例:
如需详细了解路由功能,请访问 连接组件页面。如果您遇到问题, 问题排查部分以及 调试部分可能会有所帮助, Devfs 迁移指南。
在客户端中 Use 服务
在客户端中,将服务添加到“use”列表中:
use: [
{ service: "fuchsia.example.EchoService", },
],
以客户端身份连接到服务
服务只是一个包含协议的目录,由组件框架按服务名称在 /svc/ 目录中提供。因此,您可以连接到服务提供的协议,网址为:
/svc/<ServiceName>/<instance_name>/<ServiceMemberName>
对于第 1 步中的示例,这将是:
/svc/fuchsia.example.EchoService/<instance_name>/loopback
and
/svc/fuchsia.example.EchoService/<instance_name>/speaker
实例名称是随机生成的。
如需连接到服务成员,建议的方法是监控服务目录以查看实例是否出现。有多种工具可以
帮助您完成此操作,但对于具有单个协议的服务,建议
ServiceMemberWatcher C++ 使用
,Rust 使用 Service。
同步 C++
SyncServiceMemberWatcher<fuchsia_examples::EchoService::Loopback> watcher;
zx::result<ClientEnd<fuchsia_examples::Echo>> result = watcher.GetNextInstance(true);
异步 C++
#include <lib/component/incoming/cpp/service_watcher.h>
using component::SyncServiceMemberWatcher;
// Define a callback function:
void OnInstanceFound(ClientEnd<fuchsia_examples::Echo> client_end) {...}
// Optionally define an idle function, which will be called when all
// existing instances have been enumerated:
void AllExistingEnumerated() {...}
// Create the ServiceMemberWatcher:
ServiceMemberWatcher<fuchsia_examples::EchoService::Loopback> watcher;
watcher.Begin(get_default_dispatcher(), &OnInstanceFound, &AllExistingEnumerated);
// If you want to stop watching for new service entries:
watcher.Cancel()
Rust
use fuchsia_component::client::Service;
let device = Service::open(fuchsia_examples::EchoServiceMarker)
.context("Failed to open service")?
.watch_for_any()
.await
.context("Failed to find instance")?
.connect_to_device()
.context("Failed to connect to device protocol")?;
您现在应该可以访问驱动程序的服务了。
附录
将服务与 DriverTestRealm 搭配使用
使用 DriverTestRealm 的测试客户端可以使用以下步骤将受测驱动程序的服务功能路由到测试代码。
在选项中使用
driver_exposes设置驱动程序测试 realm:C++
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); std::vector<fuchsia_component_test::Capability> exposes = { { fuchsia_component_test::Capability::WithService( fuchsia_component_test::Service{ {.name = fuchsia_examples::EchoService::Name}}), }}; auto realm_builder = component_testing::RealmBuilder::Create(); driver_test_realm::Setup( realm_builder, loop.dispatcher(), driver_test_realm::OptionsBuilder().driver_exposes(exposes).Build(), {}); auto realm = realm_builder.Build(loop.dispatcher());Rust
let echo_capability = fuchsia_component_test::Capability::service::<fuchsia_examples::EchoServiceMarker>().into(); let exposed_capabilities = vec![echo_capability]; // Create the RealmBuilder. let builder = RealmBuilder::new().await?; builder.driver_test_realm_setup(Options::new().driver_exposes(exposed_capabilities), {}).await?; // Build the Realm. let realm = builder.build().await?;连接到 realm 的
exposed()目录以等待服务实例:C++
fidl::UnownedClientEnd<fuchsia_io::Directory> svc_dir{ realm.component().exposed().unowned_channel()->get()}; component::SyncServiceMemberWatcher<fuchsia_examples::EchoService::Device> watcher( svc_dir); // Wait indefinitely until a service instance appears in the service directory zx::result<fidl::ClientEnd<fuchsia_examples::Echo>> echo_client = watcher.GetNextInstance(false);Rust
use fuchsia_component::client::Service; // Connect to the `Device` service. let device = Service::open_from_dir(realm.root.get_exposed_dir(), fuchsia_examples::EchoServiceMarker) .context("Failed to open service")? .watch_for_any() .await .context("Failed to find instance")? .connect_to_device() .context("Failed to connect to device protocol")?;
示例:本部分中的代码来自以下 CL:
使用 devfs 的旧版驱动程序通信
驱动程序管理器托管一个名为 devfs
(如“设备文件系统”中的“devfs”)的虚拟文件系统。此虚拟文件系统为 Fuchsia 系统中的所有驱动程序服务提供对 Fuchsia 的用户空间服务(即驱动程序外部的组件)的统一访问。这些非驱动程序组件通过在 devfs 中发现目标驱动程序的服务来与驱动程序建立初始联系。
严格来说,devfs 是驱动程序管理器公开的目录功能。因此,按照惯例,希望访问驱动程序的组件会在其命名空间中的 /dev 目录下装载 devfs(尽管并非强制要求 devfs 始终装载在 /dev 下)。
devfs 托管虚拟文件,使 Fuchsia 组件能够将消息路由到 Fuchsia 系统中运行的驱动程序实现的接口。
换句话说,当客户端(即非驱动程序组件)打开 /dev 目录下的文件时,它会收到一个通道,该通道可用于直接向映射到该文件的驱动程序发出 FIDL 调用。例如,Fuchsia 组件可以通过打开并写入类似于 /dev/class/input-report/000 的文件来连接到输入设备。在这种情况下,客户端可能会收到一个通道,该通道会使用 fuchsia.input.report FIDL。
驱动程序可以在添加新节点时使用 DevfsAddArgs 表将其自身导出到 devfs 中。
对于非驱动程序到驱动程序的通信,会发生以下事件:
- 如需发现系统中的驱动程序服务,非驱动程序组件会扫描
devfs中的目录和文件。 - 非驱动程序组件会在
devfs中找到一个文件,该文件表示目标驱动程序提供的服务。 - 非驱动程序组件会打开此文件,从而与目标驱动程序建立连接。
- 建立初始联系后,非驱动程序组件与驱动程序之间会建立 FIDL 连接。
- 从这一点开始,所有通信都通过 FIDL 通道进行。