駕駛人溝通

在 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::Speaker服務成員的 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",
},

請參閱下列範例,瞭解其他車主如何規劃服務路線:

如要進一步瞭解轉送功能,請參閱連線元件頁面。如果遇到問題,請參閱「疑難排解」部分,以及「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(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 的測試用戶端可以按照下列步驟,將測試中的驅動程式服務能力,從驅動程式庫導向測試程式碼。

  1. 在選項中選取 driver_exposes,設定驅動程式庫測試領域:

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

    荒漠油廠

    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?;
    
  2. 連線至領域的 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);
    

    荒漠油廠

    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 的虛擬檔案系統 (如「裝置檔案系統」)。這個虛擬檔案系統可讓 Fuchsia 系統中的所有驅動程式庫服務,以統一方式存取 Fuchsia 的使用者空間服務 (也就是驅動程式外部的元件)。這些非驅動程式庫元件會透過探索 devfs 中目標驅動程式的服務,與驅動程式建立初始聯絡。

嚴格來說,devfs 是驅動程式庫管理員公開的目錄能力。因此,按照慣例,如要存取驅動程式,元件會在命名空間的 /dev 目錄下掛接 devfs (雖然不一定必須掛接在 /dev 下)。devfs

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 管道進行。