駕駛人溝通

在 Fuchsia 中,所有通訊都會透過功能 (例如通訊協定和服務) 進行。元件架構會處理元件之間的功能路由,包括驅動程式和非驅動程式。能力會新增至一個元件的傳出目錄,如果路由正確,則可在另一個元件的傳入命名空間中使用。

駕駛員與其他駕駛員和非駕駛員的互動,主要透過服務功能 (本文為了簡便,將其稱為「服務」) 進行。涉及駕駛員的服務通訊與非駕駛員之間的服務通訊之間只有兩個差異,這涉及服務的路由方式。

  1. 驅動程式對驅動程式庫轉送會使用動態能力轉送,在子項驅動程式庫程式的元件建立期間,驅動程式管理工具會建立從父項到子項的路徑。
  2. 來自驅動程式並傳遞至非驅動程式的服務會「匯總」,因為所有驅動程式都位於集合中。這表示驅動程式公開的所有服務執行個體都會匯入以服務名稱命名的相同目錄

服務的剖析

服務執行個體代表包含一或多個通訊協定的目錄。包含的通訊協定稱為服務成員。服務成員有其專屬類型,可用於 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::Speakerservice member 的 C++ 類型
    • 這類型實際上只會由 ServiceMemberWatcher 等工具使用。
    • 這個服務成員會以 speaker 的形式顯示在 <instance_name> 目錄中
    • 連線至 /svc/fuchsia.example.EchoService/<instance_name>/speaker 會提供預期通訊協定 fuchsia_example::Broadcast 的管道。

駕駛端:宣傳服務

為每個通訊協定建立伺服器實作

針對服務中的每個通訊協定,您都必須有 fidl 通訊協定的伺服器實作項目,也就是繼承自 fidl::WireServerfidl::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));

駕駛人對駕駛人:建立子帳戶時新增優惠

如果您剛宣傳的服務應轉送給孩子,請將該服務新增至您在建立孩子時傳入的優惠。舉例來說:

  // 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>()});

這麼做會指示 Driver Manager 將該服務從您轉給孩子。由於執行個體名稱不會隨機產生,建議您將執行個體名稱指定為子元件的名稱,並做為 AddService 的引數。但在 DFv1 中無法執行這項操作。如要進一步瞭解如何將服務路由至子項,請參閱服務的 DFv2 遷移指南

路由服務

服務已宣傳完成,現在需要由駕駛端 expose,並由客戶端 use。如果用戶端是子驅動程式庫,則不需要額外路由。如果用戶端是是非驅動程式庫元件,您必須將服務 expose 至用戶端和驅動程式庫共用的祖系節點 (通常是 #core 元件),然後 offer 至用戶端。

公開驅動程式中的服務

您必須將服務新增至驅動程式的 cml 檔案,包括 CapabilityExpose 欄位。功能段落會定義能力,而公開功能會指定能力的來源。

capabilities: [
    { service: "fuchsia.examples.EchoService" },
],
expose: [
    {
        service: "fuchsia.examples.EchoService",
        from: "self",
    },
],

路由服務

在驅動程式和元件之間,將服務新增至 offerexpose 欄位。您很可能需要修改多個 cml 檔案。請參閱下列範例,瞭解其他駕駛人如何安排服務。請記下服務需要存取的領域。舉例來說,如果是 bootstrap 領域中的元件,服務必須來自 #boot-drivers#base-drivers

{
      service: "fuchsia.example.EchoService",
      from: "parent",
      to: "#my_client",
},

請參閱以下範例,瞭解其他駕駛人如何安排服務路線:

如要進一步瞭解轉送功能,請參閱 Connect 元件頁面。如果遇到問題,請參閱「疑難排解」一節,以及 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

系統會隨機產生執行個體名稱。

如要連線至服務成員,建議您監控服務目錄,看看是否有執行個體出現。有各種工具可協助您完成這項工作,但如果服務採用單一通訊協定,建議您針對 C++ 使用 ServiceMemberWatcher,針對 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()

荒漠油廠

  use fuchsia_component::client::Service;
  let device = Service::open(fidl_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 的測試用戶端需要額外幾個步驟,才能將服務能力從測試中的驅動程式庫重新導向至測試程式碼。

  1. 在呼叫 realm.Build() 之前,您需要呼叫 AddDtrExposes

    C++

    auto realm_builder = component_testing::RealmBuilder::Create();
    driver_test_realm::Setup(realm_builder);
    async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
    std::vector<fuchsia_component_test::Capability> exposes = { {
        fuchsia_component_test::Capability::WithService(
            fuchsia_component_test::Service{ {.name = "fuchsia_examples::EchoService"}}),
    }};
    driver_test_realm::AddDtrExposes(realm_builder, exposes);
    auto realm = realm_builder.Build(loop.dispatcher());
    

    荒漠油廠

    // Create the RealmBuilder.
    let builder = RealmBuilder::new().await?;
    builder.driver_test_realm_setup().await?;
    
    let expose = fuchsia_component_test::Capability::service::<ft::DeviceMarker>().into();
    let dtr_exposes = vec![expose];
    
    builder.driver_test_realm_add_dtr_exposes(&dtr_exposes).await?;
    // Build the Realm.
    let realm = builder.build().await?;
    
  2. 將公開項目新增至領域啟動引數:

    C++

    auto realm_args = fuchsia_driver_test::RealmArgs();
    realm_args.root_driver("fuchsia-boot:///dtr#meta/root_driver.cm");
    realm_args.dtr_exposes(exposes);
    fidl::Result result = fidl::Call(*client)->Start(std::move(realm_args));
    

    荒漠油廠

    // Start the DriverTestRealm.
    let args = fdt::RealmArgs {
        root_driver: Some("#meta/v1_driver.cm".to_string()),
        dtr_exposes: Some(dtr_exposes),
        ..Default::default()
    };
    realm.driver_test_realm_start(args).await?;
    
  3. 連線至領域的 exposed() 目錄,等待服務例項:

    C++

    fidl::UnownedClientEnd<fuchsia_io::Directory> svc = launcher.GetExposedDir();
    component::SyncServiceMemberWatcher<fuchsia_examples::EchoService::MyDevice> watcher(
        svc);
    // Wait indefinitely until a service instance appears in the service directory
    zx::result<fidl::ClientEnd<fuchsia_examples::Echo>> peripheral =
        watcher.GetNextInstance(false);
    

    荒漠油廠

    // Connect to the `Device` service.
    let device = client::Service::open_from_dir(realm.root.get_exposed_dir(), ft::DeviceMarker)
        .context("Failed to open service")?
        .watch_for_any()
        .await
        .context("Failed to find instance")?;
    // Use the `ControlPlane` protocol from the `Device` service.
    let control = device.connect_to_control()?;
    control.control_do().await?;
    

範例:本節的程式碼來自下列 CL:

使用 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

非驅動程式庫與驅動程式庫通訊會產生下列事件:

  1. 為了探索系統中的驅動程式庫服務,非驅動程式庫元件會掃描 devfs 中的目錄和檔案。
  2. 非驅動程式庫元件會在 devfs 中找到代表目標驅動程式庫提供的服務的檔案。
  3. 非驅動程式庫元件會開啟這個檔案,並與目標驅動程式庫建立連線。
  4. 初始接觸後,系統會在非驅動程式庫元件和驅動程式庫之間建立 FIDL 連線。
  5. 從這裡開始,所有通訊都會透過 FIDL 管道進行。