概念
請先瞭解下列概念,再繼續閱讀本指南:
- capabilities元件向其他元件公開的功能會組合成
- 每個元件都會收到 Directory管道的伺服器端程式介面,稱為元件的程式會透過這個目錄提供可探索的任何功能。
- 在執行階段,元件執行個體樹狀結構和該樹狀結構中的能力路徑,統稱為
- 或使用集合是動態子項的容器,可能會在執行階段使用 fuchsia.component.Realm架構通訊協定建立及刪除。
透過轉送連結功能
元件會透過功能相互互動。元件提供的功能必須在該元件的資訊清單中宣告,並透過 expose 和 offer 宣告,將這些功能路由至父項/子項元件,以便其他人使用。使用該能力的其他元件也必須在其資訊清單中宣告使用方式。為了讓程式在執行階段使用這項能力,所使用的能力必須經過路由傳送至該元件,該元件可以是從父項提供,也可以是從子項公開。
「能力轉送」是指元件管理員執行的遞迴程序,透過遵循資訊清單檔案中所述的個別轉送步驟,識別服務元件。在下列情況下,系統會啟動能力轉送:
- 元件程式會在其
- 元件的程式會在另一個元件的路徑中開啟
- 開發人員叫用 ffx component route。
- 啟動依賴 Resolver 或 Runner 的元件,但其支援的 Resolver 或執行元件功能未經過路由。
路由會以延遲方式執行:雖然某項能力可能會設定由父項或子項提供 (直接或間接透過進一步委派),但在啟動路由作業時,目標元件可能尚未解析。實際上,這表示在嘗試路由之前,可能無法得知從要求元件到最終服務元件的完整路徑。
提供能力實作
提供能力的元件必須使用 capabilities 宣告,在元件資訊清單中宣告能力。
請參閱以下範例,瞭解如何在提供元件的資訊清單中宣告 FIDL 通訊協定功能:
{
    include: [
        "inspect/client.shard.cml",
        "syslog/client.shard.cml",
    ],
    // Information about the program to run.
    program: {
        // Use the built-in ELF runner.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/echo_server_rust",
    },
    // Capabilities provided by this component.
    capabilities: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
    expose: [
        {
            protocol: "fidl.examples.routing.echo.Echo",
            from: "self",
        },
    ],
}
在執行階段,伺服器元件會使用 fuchsia.io 通訊協定,在傳出目錄中提供能力的實作項目。產生的 FIDL 繫結會包裝這個句柄,並讓提供者開始接收傳入的要求:
荒漠油廠
// Wrap protocol requests being served.
enum IncomingRequest {
    Echo(EchoRequestStream),
}
#[fuchsia::main(logging = false)]
async fn main() -> Result<(), anyhow::Error> {
    let mut service_fs = ServiceFs::new_local();
    // Initialize inspect
    component::health().set_starting_up();
    let _inspect_server_task = inspect_runtime::publish(
        component::inspector(),
        inspect_runtime::PublishOptions::default(),
    );
    // Serve the Echo protocol
    service_fs.dir("svc").add_fidl_service(IncomingRequest::Echo);
    service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;
    // Component is serving and ready to handle incoming requests
    component::health().set_ok();
    // Attach request handler for incoming requests
    service_fs
        .for_each_concurrent(None, |request: IncomingRequest| async move {
            match request {
                IncomingRequest::Echo(stream) => handle_echo_request(stream).await,
            }
        })
        .await;
    Ok(())
}
// Handler for incoming service requests
async fn handle_echo_request(mut stream: EchoRequestStream) {
    while let Some(event) = stream.try_next().await.expect("failed to serve echo service") {
        let EchoRequest::EchoString { value, responder } = event;
        responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response");
    }
}
C++
// Handler for incoming service requests
class EchoImplementation : public fidl::Server<fidl_examples_routing_echo::Echo> {
 public:
  void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override {
    completer.Reply({{.response = request.value()}});
  }
};
int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();
  // Initialize inspect
  inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{});
  inspector.Health().StartingUp();
  component::OutgoingDirectory outgoing_directory = component::OutgoingDirectory(dispatcher);
  zx::result result = outgoing_directory.ServeFromStartupInfo();
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
    return -1;
  }
  result = outgoing_directory.AddUnmanagedProtocol<fidl_examples_routing_echo::Echo>(
      [dispatcher](fidl::ServerEnd<fidl_examples_routing_echo::Echo> server_end) {
        fidl::BindServer(dispatcher, std::move(server_end), std::make_unique<EchoImplementation>());
      });
  if (result.is_error()) {
    FX_LOGS(ERROR) << "Failed to add Echo protocol: " << result.status_string();
    return -1;
  }
  // Component is serving and ready to handle incoming requests
  inspector.Health().Ok();
  return loop.Run();
}
連線至已轉送的功能
用戶端元件會使用 use 宣告,在元件資訊清單中宣告可能要求的功能。
請參閱以下範例,瞭解用戶端元件的資訊清單,該資訊清單使用先前元件提供的 FIDL 通訊協定:
{
    include: [
        // Enable logging on stdout
        "syslog/client.shard.cml",
    ],
    // Information about the program to run.
    program: {
        // Use the built-in ELF runner.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/echo_client_rust",
        // Program arguments
        args: [ "Hello Fuchsia!" ],
    },
    // Capabilities used by this component.
    use: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
}
在執行階段,用戶端元件會使用 fuchsia.io 通訊協定,開啟在其命名空間中填入的路徑,以便取得其他元件提供的功能。Fuchsia 元件程式庫可與產生的 FIDL 繫結搭配使用,提供透過管道進行通訊的結構化介面:
荒漠油廠
#[fuchsia::main]
async fn main() -> Result<(), anyhow::Error> {
    // Parse arguments, removing binary name
    let mut args: Vec<String> = std::env::args().collect();
    args.remove(0);
    // Connect to FIDL protocol
    let echo = connect_to_protocol::<EchoMarker>().expect("error connecting to echo");
    // Send messages over FIDL interface
    for message in args {
        let out = echo.echo_string(Some(&message)).await.expect("echo_string failed");
        log::info!("Server response: {}", out.as_ref().expect("echo_string got empty result"));
    }
    Ok(())
}
C++
int main(int argc, const char* argv[], char* envp[]) {
  // Set tags for logging.
  fuchsia_logging::LogSettingsBuilder builder;
  builder.WithTags({"echo_client"}).BuildAndInitialize();
  // Connect to FIDL protocol
  zx::result client_end = component::Connect<fidl_examples_routing_echo::Echo>();
  if (client_end.is_error()) {
    FX_LOGS(ERROR) << "Failed to connect to Echo protocol: " << client_end.status_string();
    return EXIT_FAILURE;
  }
  fidl::SyncClient client(std::move(client_end.value()));
  // Send messages over FIDL interface for each argument
  fidl::StringPtr response = nullptr;
  for (int i = 1; i < argc; i++) {
    fidl::Result response = client->EchoString({argv[i]});
    if (response.is_error()) {
      FX_LOGS(ERROR) << "echo_string failed: " << response.error_value();
      return EXIT_FAILURE;
    }
    if (!response->response().has_value()) {
      FX_LOGS(ERROR) << "echo_string got empty result";
      return EXIT_FAILURE;
    }
    const std::string& response_value = response->response().value();
    FX_LOG_KV(INFO, "Server response", FX_KV("response", response_value));
  }
  return 0;
}
元件的父項負責將所有必要的功能導向至該元件。
將部分使用的功能標示為選用
元件使用的功能並非全部都必須運作才能順利運作。有時,即使缺少某項能力,元件仍可正常執行,而該功能的存在只會啟用一些額外或替代行為。
如要讓元件架構瞭解元件需要哪些功能,以及元件可選用的功能,請使用 availability 欄位。
use: [
    {
        // It is ok if this protocol is unavailable
        protocol: "fuchsia.examples.Echo1",
        availability: "optional",
    },
    {
        // This protocol MUST be provided for the component to function correctly.
        protocol: "fuchsia.examples.Echo2",
        availability: "required",
    },
]
如果元件具有 required 使用宣告的能力 (預設),但其父項以 optional 提供能力,則能力 會產生錯誤,且在執行階段嘗試連線一律會失敗。
使用選用功能
如果元件的父項已使用 availability: "optional" offered 功能,則該能力可能無法在執行階段使用。
元件中的項目若嘗試開啟該能力的路徑,系統會使用 ZX_ERR_NOT_FOUND 銘文關閉 Directory.Open() 呼叫所提供的句柄。
使用 open() 或 stat() 等 libc 方法會傳回 ENOENT。
路由功能
元件只能存取導向至該元件的功能。只要有效的能力路徑存在,功能即可來自元件拓撲中的任何位置,這些路徑是從能力提供者到任何使用者的一連串宣告:
如要將能力提供者與要求這些功能的元件連結,請執行下列操作:
- 在能力供應器元件中新增 - offer或- expose宣告:- { include: [ "inspect/client.shard.cml", "syslog/client.shard.cml", ], // Information about the program to run. program: { // Use the built-in ELF runner. runner: "elf", // The binary to run for this component. binary: "bin/echo_server_rust", }, // Capabilities provided by this component. capabilities: [ { protocol: "fidl.examples.routing.echo.Echo" }, ], expose: [ { protocol: "fidl.examples.routing.echo.Echo", from: "self", }, ], }
- 針對元件執行個體樹狀結構中的每個中繼元件,加入額外的 - expose和- offer宣告,直到您找到包含- use宣告的使用元件:- { // Two children: a server and client. children: [ { name: "echo_server", url: "#meta/echo_server.cm", }, { name: "echo_client", url: "#meta/echo_client.cm", }, ], offer: [ // Route Echo protocol from server to client. { protocol: "fidl.examples.routing.echo.Echo", from: "#echo_server", to: "#echo_client", }, // Route diagnostics protocols to both children. { dictionary: "diagnostics", from: "parent", to: [ "#echo_client", "#echo_server", ], }, ], }
選用依附元件
如果元件對能力有選用依附元件,則由該元件的父項決定元件是否會接收該能力。提供能力時,元件可能會將 availability 欄位設為 optional、required 或 same_as_target。每個值都有以下語義:
- optional:商品目標必須宣告其處理這項能力缺失的功能,方法是將其- use宣告標示為- optional。如果目標無法執行此操作 (也就是目標的- required可用於此能力),則導向此能力會導致錯誤。
- required:目標必須接收這項能力。如果提供來源是- parent,且元件的父項 (目標的祖父項) 提供此功能做為選用能力,則導向該能力會導致錯誤,因為父項無法保證該能力的可用性。
- same_as_target:這項能力的可用性取決於目標的預期。如果目標對這項能力有選用依附元件,這項商品也會是選用項目。如果目標對這項能力有必要的依附性,則也需要這項產品。
offer: [
    {
        // child-1 MUST receive the protocol 'fuchsia.logger.LogSink'.
        protocol: "fuchsia.logger.LogSink",
        to: "#child-1",
        from: "#child-2",
        availability: "required",
    },
    {
        // child-1 MUST be able to handle the absence of the protocol
        // 'fuchsia.tracing.provider.Registry'.
        protocol: "fuchsia.tracing.provider.Registry",
        to: "#child-1",
        from: "parent",
        availability: "optional",
    },
    {
        // child-1 decides if it must receive the protocol
        // 'fuchsia.posix.socket.Provider', or if it must be able to support its
        // absence.
        protocol: "fuchsia.posix.socket.Provider",
        to: "#child-1",
        from: "parent",
        availability: "same_as_target",
    },
]
如同使用宣告,您可以省略 availability 欄位,在這種情況下,系統會預設為 required。
過渡依附元件
元件架構可讓元件以軟性轉換的方式,開始使用並提供選用和必要的功能。有了過渡性可用性標記,使用特定能力的元件就算父項為必填、選填或與目標相同,也不會導致 Scrutiny 驗證錯誤。請注意,雖然這個欄位可用於啟用軟性轉場,但元件最終應設為選用或必填。
如要使用這項功能,子項元件會將其可用性標示為「transitional」(過渡):
use: [
    {
        // It is ok if this protocol is offered as "required" or "optional"
        protocol: "fuchsia.examples.Echo",
        availability: "transitional",
    },
]
管理子元件
只要元件之間存在有效的能力路徑,元件就能透過功能在元件拓撲中的任何位置互動。還有其他方法可讓父項元件與直接子項互動。
以下範例元件會宣告名為 lifecycle 的單一靜態子項,以及名為 echo 的集合,其中可能會在執行階段建立其他子元件:
{
    // ...
    // Statically declared child components
    children: [
        {
            name: "lifecycle",
            url: "lifecycle#meta/default.cm",
        },
    ],
    // Collection to hold dynamically created child components
    collections: [
        {
            name: "echo",
            durability: "transient",
        },
    ],
    // Capabilities required by this component
    use: [
        {
            protocol: "fuchsia.component.Binder",
            from: "#lifecycle",
        },
        {
            protocol: "fuchsia.component.Realm",
            from: "framework",
        },
    ],
    // Capabilities required by child components
    offer: [
        {
            dictionary: "diagnostics",
            from: "parent",
            to: [
                "#echo",
                "#lifecycle",
            ],
        },
    ],
}
請注意,在父項元件的資訊清單中,集合會像是靜態子項例項一樣運作,您可以為集合命名,並提供特定功能。集合中的所有子元件都可以存取提供給集合的功能組合。
開始子項元件
元件架構會提供 fuchsia.component.Binder 通訊協定,讓父項元件明確啟動可能不會公開任何其他功能的子項。由於這個能力是由架構提供,子元件只需透過元件資訊清單公開此功能:
{
    // ...
    // Capabilities exposed from this component to parent.
    expose: [
        {
            // Expose this protocol so that parent component can start it
            // by binding to this capability.
            protocol: "fuchsia.component.Binder",
            from: "framework",
        },
    ],
}
建立動態子項
如要在執行階段建立新的子元件,請使用 fuchsia.component.Realm 通訊協定,在現有集合內建立元件。使用下列參數呼叫 CreateChild 方法:
- CollectionRef:說明要新增元件的集合。
- Child:元件宣告,包括名稱和元件網址。
荒漠油廠
use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker};
use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode};
// ...
// Use the fuchsia.component.Realm protocol to create a dynamic
// child instance in the collection.
async fn create_dynamic_child() {
    let realm = client::connect_to_protocol::<RealmMarker>()
        .expect("failed to connect to fuchsia.component.Realm");
    let collection_ref = CollectionRef { name: String::from("echo") };
    let child_decl = Child {
        name: Some(String::from("lifecycle_dynamic")),
        url: Some(String::from("echo_server#meta/default.cm")),
        startup: Some(StartupMode::Lazy),
        ..Default::default()
    };
    realm
        .create_child(&collection_ref, &child_decl, CreateChildArgs::default())
        .await
        .expect("create_child failed")
        .expect("failed to create child");
}
C++
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/component/decl/cpp/fidl.h>
// ...
// Use the fuchsia.component.Realm protocol to create a dynamic
// child instance in the collection.
void CreateDynamicChild() {
  fuchsia::component::decl::CollectionRef collection_ref = {
      .name = "echo",
  };
  fuchsia::component::decl::Child child_decl;
  child_decl.set_name("lifecycle_dynamic");
  child_decl.set_url("echo_server#meta/default.cm");
  child_decl.set_startup(fuchsia::component::decl::StartupMode::LAZY);
  realm_proxy_->CreateChild(std::move(collection_ref), std::move(child_decl),
                            fuchsia::component::CreateChildArgs(),
                            [&](fuchsia::component::Realm_CreateChild_Result result) {
                              ZX_ASSERT(!result.is_err());
                              FX_LOGS(INFO) << "Dynamic child instance created.";
                              ConnectDynamicChild();
                            });
}
連結至子項功能
由於動態元件的父項在建構期間無法得知,因此無法在元件資訊清單中表示的能力路徑中命名其公開的功能。
如要連線至動態子項執行個體公開的功能,請按照下列步驟操作:
- 使用 - fuchsia.component.Realm通訊協定開啟子項公開目錄。使用子元件名稱和集合名稱呼叫- OpenExposedDir方法:- 荒漠油廠- use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker}; use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode}; // ... // Connect to the fidl.examples.routing.echo capability exposed by the child // instance, and send a request. async fn connect_dynamic_child(message: String) { // Open the child's exposed directory let exposed_dir = client::open_childs_exposed_directory( String::from("lifecycle_dynamic"), Some(String::from("echo")), ) .await .expect("failed to open exposed directory"); // ... }- C++- #include <fuchsia/component/cpp/fidl.h> #include <fuchsia/component/decl/cpp/fidl.h> // ... // Use the fuchsia.component.Realm protocol to open the exposed directory of // the dynamic child instance. void ConnectDynamicChild() { fuchsia::component::decl::ChildRef child_ref = { .name = "lifecycle_dynamic", .collection = "echo", }; fidl::InterfaceHandle<fuchsia::io::Directory> exposed_dir; realm_proxy_->OpenExposedDir( child_ref, exposed_dir.NewRequest(), [this, exposed_dir = std::move(exposed_dir)]( fuchsia::component::Realm_OpenExposedDir_Result result) mutable { ZX_ASSERT(!result.is_err()); std::shared_ptr<sys::ServiceDirectory> svc = std::make_shared<sys::ServiceDirectory>( sys::ServiceDirectory(std::move(exposed_dir))); SendEchoRequest(svc); }); }
- 使用公開目錄句柄做為根目錄,連線至子項公開的功能: - 荒漠油廠- // Access the fidl.examples.routing.echo capability provided there let echo = client::connect_to_protocol_at_dir_root::<EchoMarker>(&exposed_dir) .expect("failed to connect to fidl.examples.routing.echo"); let response = echo .echo_string(Some(&message)) .await .expect("echo_string failed") .expect("echo_string got empty result"); info!("Server response: {}", response);- C++- // Connect to the fidl.examples.routing.echo capability exposed by the child's // service directory. void SendEchoRequest(std::shared_ptr<sys::ServiceDirectory> svc_directory) { // Connect to the protocol inside the child's exposed directory svc_directory->Connect(echo_proxy_.NewRequest()); // Send a protocol request echo_proxy_->EchoString(message_, [&](fidl::StringPtr response) { FX_LOGS(INFO) << "Server response: " << response; DestroyDynamicChild(); }); }
摧毀動態子項
當不再需要動態子項時,請使用 fuchsia.component.Realm 通訊協定來刪除元件執行個體。使用代表集合內子項的 ChildRef 呼叫 DestroyChild 方法。
荒漠油廠
use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker};
use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode};
// ...
// Use the fuchsia.component.Realm protocol to destroy the dynamic
// child instance running in the collection.
async fn destroy_dynamic_child() {
    let realm = client::connect_to_protocol::<RealmMarker>()
        .expect("failed to connect to fuchsia.component.Realm");
    let child_ref = ChildRef {
        name: String::from("lifecycle_dynamic"),
        collection: Some(String::from("echo")),
    };
    realm
        .destroy_child(&child_ref)
        .await
        .expect("destroy_child failed")
        .expect("failed to destroy child");
}
C++
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/component/decl/cpp/fidl.h>
// ...
// Use the fuchsia.component.Realm protocol to destroy the dynamic
// child instance running in the collection.
void DestroyDynamicChild() {
  fuchsia::component::decl::ChildRef child_ref = {
      .name = "lifecycle_dynamic",
      .collection = "echo",
  };
  realm_proxy_->DestroyChild(std::move(child_ref),
                             [&](fuchsia::component::Realm_DestroyChild_Result result) {
                               ZX_ASSERT(!result.is_err());
                               FX_LOGS(INFO) << "Dynamic child instance destroyed.";
                               // Terminate the loop
                               loop_->Quit();
                             });
}
這會導致元件停止運作 (如果目前正在運作)。如要在元件中處理這項事件,請參閱「監聽停止事件」。
控管元件生命週期
元件架構提供功能,可修改元件生命週期的各個部分並與其互動。
如要進一步瞭解生命週期概念,請參閱「元件生命週期」。
生命週期通知
ELF 執行元件會使用 fuchsia.process.lifecycle.Lifecycle 通訊協定通知元件的生命週期事件。
如要在子項元件中監聽停止通知,請按照下列步驟操作:
- 在元件資訊清單中訂閱生命週期事件: - 荒漠油廠- // Information about the program to run. program: { // Use the built-in ELF runner. runner: "elf", // The binary to run for this component. binary: "bin/lifecycle_example_rust", // Subscribe to component lifecycle events lifecycle: { stop_event: "notify" }, },- C++- // Information about the program to run. program: { // Use the built-in ELF runner. runner: "elf", // The binary to run for this component. binary: "bin/lifecycle_example_cpp", // Subscribe to component lifecycle events lifecycle: { stop_event: "notify" }, },
- 使用執行元件提供的啟動句柄,註冊生命週期處理常式: - 荒漠油廠- use fidl_fuchsia_process_lifecycle::{LifecycleRequest, LifecycleRequestStream}; // ... #[fuchsia::main(logging_tags = ["lifecycle", "example"])] async fn main() { // Take the lifecycle handle provided by the runner match fuchsia_runtime::take_startup_handle(HandleInfo::new(HandleType::Lifecycle, 0)) { Some(lifecycle_handle) => { info!("Lifecycle channel received."); // Begin listening for lifecycle requests on this channel let x: zx::Channel = lifecycle_handle.into(); let async_x = AsyncChannel::from(fuchsia_async::Channel::from_channel(x)); let mut req_stream = LifecycleRequestStream::from_channel(async_x); info!("Awaiting request to close..."); if let Some(request) = req_stream.try_next().await.expect("Failure receiving lifecycle FIDL message") { match request { LifecycleRequest::Stop { control_handle: c } => { info!("Received request to stop. Shutting down."); c.shutdown(); process::exit(0); } } } // We only arrive here if the lifecycle channel closed without // first sending the shutdown event, which is unexpected. process::abort(); } None => { // We did not receive a lifecycle channel, exit abnormally. error!("No lifecycle channel received, exiting."); process::abort(); } } }- C++- #include <fuchsia/process/lifecycle/cpp/fidl.h> // ... // Implementation of the fuchsia.process.lifecycle FIDL protocol class LifecycleHandler : public fuchsia::process::lifecycle::Lifecycle { public: explicit LifecycleHandler(async::Loop* loop) : loop_(loop) { // Get the PA_LIFECYCLE handle, and instantiate the channel with it zx::channel channel = zx::channel(zx_take_startup_handle(PA_LIFECYCLE)); // Bind to the channel and start listening for events bindings_.AddBinding( this, fidl::InterfaceRequest<fuchsia::process::lifecycle::Lifecycle>(std::move(channel)), loop_->dispatcher()); FX_LOGS(INFO) << "Lifecycle channel received."; } // This is the Stop event we must override - see the pure virtual function we // need to override at the declaration of fuchsia::process::lifecycle::Lifecycle void Stop() override { FX_LOGS(INFO) << "Received request to stop. Shutting down."; // Shut down our loop - it's important to call Shutdown() here vs. Quit() loop_->Shutdown(); // Close the binding bindings_.CloseAll(); } private: async::Loop* loop_; fidl::BindingSet<fuchsia::process::lifecycle::Lifecycle> bindings_; };
從父項開始
元件資訊清單可讓您將子項標示為 eager,這會導致元件架構會隱含地透過父項啟動該子項。
如果子項因任何原因 (例如缺少元件) 而無法啟動,元件管理員會顯示下列行為:
- 如果父項不是根層級元件,父項會啟動,但繫結至該父項的元件會觀察到連線中斷 (就像其他繫結失敗一樣)。
- 如果父項是根元件,元件管理工具就會當機,並顯示類似以下的錯誤訊息: - [component_manager] ERROR: protocol `fuchsia.component.CoreBinder` was not available for target `startup`: failed to resolve `fuchsia-pkg://fuchsia.com/your_component#meta/your_component.cm`: package not found: remote resolver responded with PackageNotFound For more, run `ffx component doctor `startup`
如果元件標示為 eager,且其祖系 (直到根元件) 也標示為 eager,則在元件不存在時,可能會導致系統當機。這一點很重要,因為許多建構設定都會建立包含可用元件子集的系統映像檔。為避免這個問題,請使用領域宣告這些元件,確保這些元件可安全地從測試版本和產品映像檔中排除。
eager 元件也應與其父項位於相同的套件組合中,因為元件會與其父項同時啟動。一般來說,eager 元件應位於產品的基本套件組合中。
如要判斷套件是否位於基本套件組合中,請執行下列指令:
fx list-packages --verbose my-package這個指令會輸出符合條件的套件組合清單。例如,system-update-checker 位於 base 和 universe 套件組合中:
$ fx list-packages --verbose system-update-checker
system-update-checker [base universe]
您也可以使用 --base 選項查看基本套件組合中的所有套件:
fx list-packages --base終止時重新啟動
元件資訊清單可讓您使用 on_terminate 控制元件的終止政策。如果元件因任何原因終止 (而非成功結束),已設定「終止時重新啟動」政策的元件會導致系統重新啟動。
如要啟用這項功能,請按照下列步驟操作:
- 在父項的元件資訊清單中,將子項標示為 - on_terminate: "reboot":- // core.cml { children: [ ... { name: "system-update-checker", url: "fuchsia-pkg://fuchsia.com/system-update-checker#meta/system-update-checker.cm", startup: "eager", on_terminate: "reboot", }, ], }
- 在 - //src/security/policy/component_manager_policy.json5中,將元件的路徑名稱新增至元件管理員的安全政策許可清單:- // //src/security/policy/component_manager_policy.json5 { security_policy: { ... child_policy: { reboot_on_terminate: [ ... "/core/system-update-checker", ], }, }, }
錯誤
訊號機制
當要求用戶端在其命名空間或Directory.Open()錯誤會導致該句柄的物件以碑文關閉。墓誌銘酬載物一律為 Zircon 狀態碼。
由於路由是延遲且非同步的,因此在路由作業啟動後,這則訊息可能隨時傳送。
注意:轉送作業成功後,服務元件也可以使用所選狀態碼關閉相同的物件。用戶端無法判斷物件是由元件管理員或服務元件關閉,或是由之後委派的其他方關閉。
同樣的錯誤信號機制也適用於 libc 類似的呼叫,例如 open()。
如需實用範例,請參閱「疑難排解」一節。
錯誤狀態碼
元件管理員可能會傳送下列錯誤代碼,表示路由作業失敗:
- ZX_ERR_NOT_FOUND:因下列任一原因,無法將能力路由:
- ZX_ERR_ACCESS_DENIED:由於要求元件無法存取該功能,因此無法轉送該功能。例如:- 存在適用於該能力的政策許可清單,但不包含要求的元件。
- 要求元件要求的權限超過提供的權限 (例如,要求讀取/寫入權限,但目錄已提供唯讀權限)。
 
- ZX_ERR_TIMED_OUT:其中一個路由步驟逾時。
- ZX_ERR_INTERNAL:元件管理員本身發生非預期錯誤,表示平台有錯誤。
只要平台上的軟體未更新,NOT_FOUND、ACCESS_DENIED 和 INTERNAL 錯誤就會重現相同的能力。軟體更新 (即使是單一元件) 都可能會變更能力路徑,並影響該能力的可用性。
路由錯誤語義的規則
- 最小化:由於元件管理員和服務元件之間共用錯誤訊號路徑,因此元件管理員會將大部分的錯誤空間留給服務元件使用。
- 用戶端觀點:雖然轉送作業取決於許多個別的子作業,而這些作業可能會因各種原因失敗 (包括其他元件作者的錯誤),但錯誤語義會根據要求的用戶端和要求的用戶端需求進行調整。舉例來說,中介元件作者的使用者錯誤仍會為要求用戶端傳回 NOT_FOUND。
疑難排解
本節說明您在嘗試 use 並連線至元件功能時可能會遇到的常見問題,並提供建議的解決方案。
能力轉送失敗
元件管理員會在元件嘗試存取能力時執行功能路由,以便找出特定能力的來源。如果轉送路徑中其中一個元件呈現方式設定錯誤,轉送作業就可能失敗。例如,路徑中的某些元件缺少 offer 或 expose 宣告,或是鏈結中無法解析其中一個元件。
請按照下列步驟確認管道關閉是否是因為路由失敗:
- 使用 - ffx component route檢查元件的路由。您可以使用元件的路徑名稱或元件的網址。例如:- # check with the moniker ffx component route /core/ffx-laboratory:echo_realm/echo_client # check with a partial URL ffx component route echo_client.cm
- 請使用 - ffx log檢查元件記錄檔,找出開頭為- Failed to route的訊息,瞭解路由鏈結失敗的位置。例如:- [echo_client][][W] protocol `fidl.examples.routing.echo.Echo` was not available for target `/core/ffx-laboratory:echo_realm/echo_client`: `fidl.examples.routing.echo.Echo` was not offered to `/core/ffx-laboratory:echo_realm/echo_client` by parent For more, run `ffx component doctor /core/ffx-laboratory:echo_realm/echo_client`
- 查看已關閉的頻道上是否有輓文。最常見的轉送失敗情況設定的墓誌銘文為 - ZX_ERR_NOT_FOUND:- [echo_client][][I] Connecting to Echo protocol failed with error "A FIDL client's channel to the service fidl.examples.routing.echo.Echo was closed: NOT_FOUND"- 詳情請參閱路由錯誤。 
如需失敗的能力轉送的獨立範例,請參閱 //examples/components/routing_failed。
接收路由錯誤
能力轉送功能失敗時,基礎 FIDL 管道會關閉。如果管道已關閉,FIDL 通訊協定繫結會傳回錯誤狀態。請參考以下範例:
荒漠油廠
let echo = connect_to_protocol::<EchoMarker>()
    .context("Failed to connect to echo service")?;
let res = echo.echo_string(Some("Hippos rule!")).await;
match res {
    Ok(_) => { info!("Call succeeded!"); }
    Err(fidl::Error::ClientChannelClosed { status, service_name } => { 
        error!("Channel to service {} was closed with status: {}", service_name, status); 
    } 
    Err(e) => {
        error!("Unexpected error: {}", e);
    }
};
C++
fuchsia::examples::EchoPtr echo_proxy;
auto context = sys::ComponentContext::Create();
context->svc()->Connect(echo_proxy.NewRequest());
// Sets an error handler that will be called if an error causes the underlying 
// channel to be closed. 
echo_proxy.set_error_handler([&loop](zx_status_t status) { 
  printf("Channel was closed with status: %d\n", status); 
  // ... 
}); 
echo_proxy->EchoString("Hippos rule!", [&](std::string response) {
  // ...
});
如要判斷管道關閉的根本原因,您可以檢查管道上設定的選用碑文。如要擷取已關閉管道的碑文,請按照下列步驟操作:
荒漠油廠
let stream = echo.take_event_stream();
match stream.next().await {
    Some(Err(fidl::Error::ClientChannelClosed { status, .. })) => {
        info!("Channel was closed with epitaph: {}", status);
    }
    Some(m) => {
        info!("Received message other than epitaph or peer closed: {:?}", m);
    }
    None => {
        info!("Component failed to start or channel was closed by server");
    }
}
C++
echo_proxy.set_error_handler([&loop](zx_status_t status) {
  // If an Epitaph was present on the channel, its error value will be passed as
  // the parameter.
  printf("Channel was closed with epitaph: %d\n", status);
});
元件無法啟動
如果能力轉送成功,但在解析或啟動元件時發生問題,就可能會發生錯誤。錯誤訊息的格式取決於元件執行程式:
- 如要檢查 ELF 執行元件,請使用 - ffx log --filter component_manager檢查元件管理員記錄。找出開頭為- Failed to start component的訊息。例如:- [component_manager] WARN: Failed to start component `fuchsia-pkg://fuchsia.com/components-routing-failed-example#meta/echo_server_bad.cm`: unable to load component with url "fuchsia-pkg://fuchsia.com/components-routing-failed-example#meta/echo_server_bad.cm": error loading executable: "reading object at \"bin/routing_failed_echo_server_oops\" failed: A FIDL client's channel to the service fuchsia.io.File was closed: PEER_CLOSED"
- 如果是其他執行器,請檢查執行元件元件的記錄。您可以執行下列指令來執行此操作: - ffx log --tags runner-name
如要解決這個問題,請確認下列事項:
如需因元件資訊清單設定錯誤而無法啟動元件的範例,請參閱 //examples/components/routing_failed。
元件終止或關閉管道
如果您已確認路由成功且元件已成功啟動,則可能會遇到來源元件關閉管道的情況。這可能發生在元件執行期間,也可能是元件終止的副作用。
如果元件因當機而終止,您可以在 ffx log 中尋找當中包含轉儲檔案中元件名稱的當機報告:
[33860.645][klog][klog][I] crashsvc: exception received, processing
[33860.645][klog][klog][I] <== fatal : process echo_client.cm[21090] thread initial-thread[21092]
<stack trace follows...>
如果是來源元件關閉了管道,請參考以下提示進一步排除原因: