RFC-0041:支援整合服務和裝置

RFC-0041:支援統合服務和裝置
狀態已接受
領域
  • FIDL
說明

說明服務的概念:一組通訊協定,其中可能有一或多個集合。

作者
提交日期 (年-月-日)2019-04-08
審查日期 (年-月-日)2019-04-23

摘要

說明服務的概念 - 一組通訊協定,其中 也有可能是集合的一或多個實例。

提振精神

目前在元件架構中,服務已定義為 而且該通訊協定的例項只會存在於該通訊協定的一個執行個體 /svc 下程序的命名空間。 否則我們無法說明更複雜的關係:

  • 根據 分別在兩個不同版本 通訊協定,例如 FontProviderFontProviderV2
  • 為了根據 存取層級 — 例如:一般存取權與管理員 例如 DirectoryDirectoryAdmin,其中後者提供了 特殊權限存取
  • 服務由許多不同的通訊協定所組成 不同的消費者 — 例如使用 Power 管理電源管理,以及 Ethernet 適用於網路堆疊
  • 含有多個執行個體的服務,例如多音訊 提供「AudioRenderer」的裝置,或多部印表機暴露在 Printer 的風險中

只要提供這樣的彈性,服務就能更加清楚明確。 或是使用 service 中樞。 透過這樣的靈活性,我們就能將裝置定義為服務。 具體來說,我們打算改善 /svc/$Protocol 這意味著「每個程序命名空間只有一個通訊協定」改為:

/svc/$Service/$Instance/$Member

而會產生兩個額外的指示:服務 (例如 印表機、乙太網路) 和執行個體 (例如預設、deskjet_by_desk e80::d189:3247:5fb6:5808). 通訊協定的路徑由以下部分組成:

  • $Service:服務的完整類型, 在 FIDL 中宣告
  • $Instance:服務的執行個體名稱,其中 「預設」,以指出偏好 (或只) 使用 可用的執行個體
  • $Member — 與 FIDL 中宣告的服務成員名稱,其中 該成員宣告的類型,表示預期的通訊協定

設計

服務型態

首先,我們來思考我們希望支援的各種服務:

  • 單一且不重複的通訊協定:ONE 執行個體、ONE 通訊協定:

    /svc/fuchsia.Scheduler/default/profile_provider
    
  • 由多個通訊協定組成:ONE 個執行個體、MANY 個通訊協定:

    /svc/fuchsia.Time/default/network
                          .../rough
    
  • 服務的多個執行個體,設有單一通訊協定:MANY 個執行個體,ONE 通訊協定:

    /svc/fuchsia.hardware.Block/0/device
                            .../1/device
    
  • 多個含有不同通訊協定組合的執行個體:MANY 個執行個體,MANY 個通訊協定:

    /svc/fuchsia.Wlan/ff:ee:dd:cc:bb:aa/device
                                    .../power
                  .../00:11:22:33:44:55/access_point
                                    .../power
    

語言

向 FIDL 介紹服務的概念並支援各種 我們將對 FIDL 語言進行下列變更:

  1. 新增service關鍵字。
  2. 移除 Discoverable 屬性。

service 關鍵字會允許我們撰寫服務宣告, 我們可以用來將一組通訊協定定義為服務成員。 舉例來說,我們可以宣告不同的服務型態,如下所示:

  • 單一且不重複的通訊協定:ONE 執行個體、ONE 通訊協定:

    service Scheduler {
      fuchsia.scheduler.ProfileProvider profile_provider;
    };
    
  • 由多個通訊協定組成:ONE 個執行個體、MANY 個通訊協定:

    service Time {
      fuchsia.time.Provider network;
      fuchsia.time.Provider rough;
    };
    
  • 服務的多個執行個體,設有單一通訊協定:MANY 個執行個體,ONE 通訊協定:

    service Block {
      fuchsia.hardware.block.Device device;
    };
    
  • 多個含有不同通訊協定組合的執行個體:MANY 個執行個體、MANY 個通訊協定

    service Wlan {
      fuchsia.hardware.ethernet.Device device;
      fuchsia.wlan.AccessPoint access_point;
      fuchsia.hardware.Power power;
    };
    

服務宣告可能含有多位成員 但每個成員宣告必須使用不同的 ID。 請參閱「包含多個通訊協定的組合」。

當服務的執行個體包含一組不同的通訊協定時 服務宣告會宣告所有可能的 所有執行個體中可能存在的通訊協定。 請參閱「通訊協定組合不同的多個執行個體」一節。

服務宣告不會提及特定執行個體的名稱 服務或元件的 URI 到目前為止根據元件資訊清單列出元件架構 宣告,並在執行階段使用其 API。

語言繫結

系統將修改語言繫結,以便更連線至服務 非常便利 具體來說,這些技術將更加註重服務,例如:

  • 連接至「預設」設有單一通訊協定的服務執行個體:ONE 執行個體、ONE 通訊協定:

    • C++:
    Scheduler scheduler = Scheduler::Open();
    ProfileProviderPtr profile_provider;
    scheduler.profile_provider().Connect(profile_provider.NewRequest());
    
    • Rust:
    let scheduler = open_service::<Scheduler>();
    let profile_provider: ProfileProviderProxy = scheduler.profile_provider();
    
  • 連接至「預設」具有多個通訊協定的服務執行個體:ONE instance, MANY 個通訊協定:

    • C++:
    Time time = Time::Open();
    ProviderPtr network;
    time.network().Connect(&network);
    ProviderPtr rough;
    time.rough().Connect(&rough);
    
    • Rust:
    let time = open_service::<Time>();
    let network = time.network();
    let rough = time.rough();
    
  • 透過單一通訊協定 (MANY 個執行個體、ONE 通訊協定,連至多個執行個體:

    • C++:
    Block block_0 = Block::OpenInstance("0");
    DevicePtr device_0;
    block_0.device().Connect(&device_0);
    
    Block block_1 = Block::OpenInstance("1");
    DevicePtr device_1;
    block_1.device().Connect(&device_1);
    
    • Rust:
    let block_0 = open_service_instance::<Block>("0");
    let device_0 = block_0.device();
    let block_1 = open_service_instance::<Block>("1");
    let device_1 = block_1.device();
    
  • 連線至服務的多個執行個體,其中的多個通訊協定為 MANY 個執行個體,MANY 個通訊協定:

    • C++:
    Wlan wlan_a = Wlan::OpenInstance("ff:ee:dd:cc:bb:aa");
    DevicePtr device;
    wlan_a.device().Connect(&device);
    Power power_a;
    wlan_a.power().Connect(&power_a);
    
    Wlan wlan_b = Wlan::OpenInstance("00:11:22:33:44:55");
    AccessPoint access_point;
    wlan_b.access_point().Connect(&access_point);
    Power power_b;
    wlan_b.power().Connect(&power_b);
    
    • Rust:
    let wlan_a = open_service_instance::<Wlan>("ff:ee:dd:cc:bb:aa");
    let device = wlan_a.device();
    let power_a = wlan_a.power();
    
    let wlan_b = open_service_instance::<Wlan>("00:11:22:33:44:55");
    let access_point = wlan_b.access_point();
    let power_b = wlan_b.power();
    

以下說明建議的函式簽章。

請注意,Open()OpenInstance() 方法也接受 選用參數,可指定命名空間。 根據預設,系統會使用程序的全域命名空間 (可以擷取 使用 fdio_ns_get_installed)。

// Generated code.
namespace my_library {
class MyService final {
public:
  // Opens the "default" instance of the service.
  //
  // |ns| the namespace within which to open the service or nullptr to use
  // the process's "global" namespace as defined by |fdio_ns_get_installed()|.
  static MyService Open(fdio_ns_t* ns = nullptr) {
    return OpenInstance(fidl::kDefaultInstanceName, ns);
  }

  // Opens the specified instance of the service.
  //
  // |name| the name of the instance, must not be nullptr
  // |ns| the namespace within which to open the service or nullptr to use
  // the process's "global" namespace as defined by |fdio_ns_get_installed()|.
  static MyService OpenInstance(const std::string& instance_name,
                                fdio_ns_t* ns = nullptr);

  // Opens the instance of the service located within the specified directory.
  static MyService OpenAt(zxio_t* directory);
  static MyService OpenAt(fuchsia::io::DirectoryPtr directory);

  // Opens a directory of available service instances.
  //
  // |ns| the namespace within which to open the service or nullptr to use
  // the process's "global" namespace as defined by |fdio_ns_get_installed()|.
  static fidl::ServiceDirectory<MyService> OpenDirectory(fdio_ns_t* ns = nullptr) {
    return fidl::ServiceDirectory<MyService>::Open(ns);
  }

  // Gets a connector for service member "foo".
  fidl::ServiceConnector<MyService, MyProtocol> foo() const;

  // Gets a connector for service member "bar".
  fidl::ServiceConnector<MyService, MyProtocol> bar() const;

  /* more stuff like constructors, destructors, etc... */
}

而繫結程式碼則是:

/// FIDL bindings code.
namespace fidl {
constexpr char[] kDefaultInstanceName = "default";

// Connects to a particular protocol offered by a service.
template <typename Service, typename Protocol>
class ServiceConnector final {
public:
   zx_status_t Connect(InterfaceRequest<Protocol> request);
};

// A directory of available service instances.
template <typename Service>
class ServiceDirectory final {
public:
  // Opens a directory of available service instances.
  //
  // |ns| the namespace within which to open the service or nullptr to use
  // the process's "global" namespace as defined by |fdio_ns_get_installed()|.
  static ServiceDirectory Open(fdio_ns_t* ns = nullptr);

  // Gets the underlying directory.
  zxio_t* directory() const;

  // Gets a list of all available instances of the service.
  std::vector<std::string> ListInstances();

  // Opens an instance of the service.
  Service OpenInstance(const std::string& name);

  // Begins watching for services to be added or removed.
  //
  // Invokes the provided |callback| to report all currently available services
  // then reports incremental changes.  The callback must outlive the returned
  // |Watcher| object.
  //
  // The watch ends when the returned |Watcher| object is destroyed.
  [[nodiscard]] Watcher Watch(WatchCallback* callback,
                              async_dispatcher_t* dispatcher = nullptr);

  // Keeps watch.
  //
  // This object has RAII semantics.  The watch ends once the watcher has
  // been destroyed.
  class Watcher final {
  public:
    // Ends the watch.
    ~Watcher();
  };

  // Callback invoked when service instances are added or removed.
  class WatchCallback {
  public:
    virtual void OnInstanceAdded(std::string name) = 0;
    virtual void OnInstanceRemoved(std::string name) = 0;
    virtual void OnError(zx_status_t error) = 0;
  };
}

語言繫結除了可以進一步延伸, 反覆探索服務的執行個體,看看 執行個體才會變為可用

服務演進

為了改進服務,我們可以逐步在服務中加入新的通訊協定。 為了維持原始碼相容性,現有通訊協定不應 否則原始碼相容性可能會因為使用者 取決於語言繫結從服務產生的程式碼。

由於服務中的所有通訊協定都是選擇性的,因此可以 。 最終的發展 AI 可簡化我們在 AI 技術發展 服務:

  • 您隨時可以將通訊協定成員新增至服務
  • 為避免來源相容性,請避免移除通訊協定成員
  • 將通訊協定成員重新命名時,需要新增通訊協定成員,且 退出現有的通訊協定成員

為了改進服務本身,我們訂有類似的限制。 服務不保證一定會存在於元件的命名空間中,且 服務可以顯示在命名空間中的多個不同位置 因此:

  • 你隨時可以新增服務
  • 為確保來源相容性,請避免移除服務
  • 如要將服務重新命名,包括複製服務,以及使用新的 名稱,同時保留服務的原始副本 (用於原始碼) 相容性)

可能的擴充功能

預期 service 個例項最終會成為「第一類別」且 就像 protocol P 帳號一樣可加入訊息 以 Prequest<P> 的形式傳遞。 這可能會以 service_instance<S> 之類的形式, service S。 我們會盡力擴充這個擴充功能,但不會加以運作 。

我們讓這個大門保持開啟 (並計劃) 拓展會員類型 而非僅允許通訊協定。 舉例來說,我們可能希望讓服務公開 VMO (handle<vmo>):

service DesignedService {
    ...
    handle<vmo>:readonly logo; // gif87a
};

執行策略

本提案應分階段實施,以免違反現有提案 再也不是件繁重乏味的工作

第 1 階段
  1. 修改 component_manager,讓元件 v2 支援新的 服務目錄結構定義
  2. 修改 appmgr 和 sysmgr,讓元件 v1 支援新的 服務目錄結構定義
第 2 階段
  1. 新增服務宣告支援。
  2. 修改語言繫結來產生服務。
第 3 階段
  1. 針對具有 Discoverable 屬性的所有通訊協定,建立 適當的服務宣告 >注意:在這個階段,我們會確認沒有名稱 >服務的新目錄結構定義可能發生衝突。
  2. 遷移所有原始碼以使用服務。
第 4 階段
  1. 請從 FIDL 檔案中移除所有 Discoverable 屬性。
  2. 從 FIDL 和語言繫結移除對 Discoverable 的支援。
  3. 移除 component_manager 對舊目錄結構定義的支援。 appmgr 和 sysmgr。

說明文件和範例

因此我們必須展開 FIDL 教學課程,解釋服務的使用狀況 宣告內容,以及它們與通訊協定的互動方式。 接著,我們會說明服務的不同結構:單例模式和 以及語言繫結的使用方式。

詞彙解釋

通訊協定宣告說明系統可能傳送或 以及各自的二進位表示法

服務宣告說明瞭以單元形式提供的能力 由服務供應商提供 包含服務名稱,以及零或多個已命名的成員通訊協定 以及用戶端與能力互動的方式

同一個通訊協定可能會以服務成員的形式出現多次 ,其中成員姓名表示預定的解讀 通訊協定的依附元件清單

service Foo {
    fuchsia.io.File logs;
    fuchsia.io.File journal;
};

元件宣告是指一個可執行軟體的單位 包括元件二進位檔的位置 打算使用公開優惠到 其他元件

這項資訊通常會編碼為元件資訊清單檔案 套件中:

// frobinator.cml
{
    "uses": [{ "service": "fuchsia.log.LogSink" }],
    "exposes": [{ "service": "fuchsia.frobinator.Frobber" }],
    "offers": [{
        "service": "fuchsia.log.LogSink",
        "from": "realm",
        "to": [ "#child" ]
    }],
    "program": { "binary": ... }
    "children": { "child": ... }
}

服務執行個體是符合指定服務的能力 宣告內容 在 Fuchsia 中,會以目錄的形式表示。 其他系統可能會使用不同的服務探索機制。

元件執行個體是元件的特定執行個體 或自有隱私沙箱 在執行階段,它會使用其他元件提供的服務執行個體 會在新的命名空間中開啟目錄。 相反地,此方法也會向其他元件公開自己的服務執行個體 ,使其在傳出目錄中存取這些檔案。 元件管理員會做為服務探索的代理程式。

  • 元件執行個體通常 (但不一定) 對元件執行個體
  • 元件執行器通常可以在 與「自己的」傳入命名空間都是相同的程序。

慣用服務的使用方式

回溯相容性

此提案將會淘汰,最終移除Discoverable 屬性。

電匯格式則維持不變。

如要導入新的資料類型或語言功能,請考量以下幾點: 您預期使用者會在沒有定義的 FIDL 定義的情況下進行 FIDL 定義 藉此破壞產生的程式碼 如果您的功能會影響任何新的來源相容性 產生語言繫結的限制,請在這裡列出。

成效

這應該不會影響連接 服務的預設執行個體,或稱為 Pori 的執行個體。

連線至無法辨識的執行個體 ID 的其他執行個體 aprii,使用者必須列出服務的目錄並定位 才能連線

這對建構和二進位檔大小的影響微乎其微 定義必須由後端產生特定語言繫結的定義。

安全性

本提案將協助我們強制執行更精細的存取權控管機制 因為我們可以將服務分割成分別具有不同存取能力的通訊協定 權利。

此提案不會對安全性產生其他影響。

測試

編譯器中的單元測試,並變更為相容性測試套件 檢查服務包含的通訊協定是否可連線。

缺點、替代方案和未知

我們探討了以下問題:

問題 1:服務宣告為何會出現在 FIDL 中?

回應

  • 我們使用 FIDL 描述 Fuchsia 的系統 API,包括通訊協定 與元件交換的環境
  • 視情況而定,許多方式可能會運用相同的通訊協定。 代表這些通訊協定的各種用途,使其成為服務 可讓開發人員輕鬆存取適合 情境。
  • FIDL 已提供可立即擴充的語言繫結 讓開發人員以一致且方便的方式 免費 Google Cloud 服務

討論

  • [ianloic] 那麼有關元件資訊清單呢?為何不將 FIDL 用於 我也跟你說明一下嗎?
  • [jeffbrown] 元件資訊清單說明超出處理序間通訊 (IPC) 以外的概念 個疑慮
  • [abdulla] 說明元件資訊清單中的服務會帶來 重複的服務說明
  • 我們可以透過資訊清單產生元件的骨架嗎?
  • [drees] 在 FIDL 中加入服務宣告,僅是 這點是否合理?
  • [jeffbrown] 我們希望服務宣告屬於元件外部 因為需要在不同元件之間共用 服務交換協議
  • [ianloic] 超網服務宣告可能相近
  • [pascallouis] 根據我們所知需求, 您的兒時朋友不久後就會遭到解僱 並要求您暫時保密我們之後有需要再視情況調整。
  • [pascallouis] FIDL 是 Fuchsia 最先驅,因此引人入勝 功能提供的功能,根據我們 但也可以視其他情境將長期趨勢
  • [dustingreen] 如果是獨立的檔案呢?
  • [pascallouis] 那些檔案很小而孤獨, 對於靜態型別檢查我們是否保留在 FIDL 中,則遷移後似乎是低風險 可以視需要

問題 2:通訊協定、服務和元件有何不同?

回應

  • 通訊協定宣告說明一組可能 及其二進位表示法
  • 能力宣告是指以 每個服務供應商 包含服務名稱,以及零或多個已命名的成員通訊協定 用戶端儲存空間和用戶端能力
    • 同一個通訊協定可能會重複出現在 服務聲明;成員名稱會指出 通訊協定的解釋
      • 例如:service Foo { fuchsia.io.File logs; fuchsia.io.File journal; };
  • 元件宣告是指一個可執行軟體的單位 包括元件二進位檔的位置 打算使用公開優惠到 其他元件

    • 這項資訊通常會編碼為元件資訊清單 檔案。 範例:

      // frobinator.cml
      {
          "uses": [{ "service": "fuchsia.log.LogSink" }],
          "exposes": [{ "service": "fuchsia.frobinator.Frobber" }],
          "offers": [{ "service": "fuchsia.log.LogSink",
                       "from": "realm", "to": [ "#child" ]}],
          "program": { "binary": ... }
          "children": { "child": ... }
      }
      
  • 服務執行個體是符合指定物件的能力 服務宣告 在 Fuchsia 中,會以目錄的形式表示。 其他系統可能會使用不同的服務探索機制。

  • 元件執行個體是元件中特定元件的執行個體 和各自的私密沙箱 執行階段會使用其他元件提供的服務執行個體 開啟其收到的命名空間中的目錄。 相反地,它也會向其他元件公開自己的服務執行個體 存取這類目錄: 元件管理員會做為服務探索的代理程式。

    • 元件執行個體通常 (但不一定) 對元件執行個體
    • 元件執行器通常可以執行多個元件執行個體 具備「專屬」連入命名空間相同的程序,

討論

  • [韓文] 選擇通訊協定組合時,我們該提供什麼指引 還是服務宣告?
  • [abdulla] 通訊協定組成表示通訊協定本身 代表了一組功能最強的功能 (可能無關) 分別提供
  • [pascallouis] 用單一管道編寫多工通訊協定 訊息排序和服務個別通訊協定的影響 擁有不同頻道
  • [jeffbrown] 可在各種不相關、組合的情況下委派代表 無法使用這項功能,服務允許「探索」執行時 例如:列出哪些通訊協定

問題 3:我們提出的一般拓撲是否足夠表達服務執行個體?

回應

  • 平面拓撲很容易使用,因為不需要以遞迴方式進行 找出所有執行個體 這會影響使用便利性和效能。
  • 平地拓撲可做為階層式拓撲的表達 表示相關資訊已編碼入執行個體名稱中,例如 /svc/fuchsia.Ethernet/rack.5,port.9/packet_receiver
  • 您可以透過 Open()Open(命名空間)OpenAt(directory)。 換句話說,並非所有服務都須來自 `/svc"的 程序的全域命名空間 這麼做就能建立任意服務拓撲 無從得知

問題 4:我們應如何持續擴展服務?

回應

  • 我們可以在現有的服務宣告中新增成員。 新增成員不會破壞來源或二進位檔相容性 因為每個成員都是選擇性的 (嘗試連線至 「通訊協定」為可失敗的作業)。
  • 我們可以將現有成員從服務聲明中移除。 移除 (或重新命名) 現有成員可能會導致來源和二進位檔中斷 相容性,並可能需要製定嚴謹的遷移計畫以減少不良影響 這種做法
  • 服務的說明文件應清楚說明服務方式 設計或實作服務,尤其是在這類情況下 不會顯而易見,例如說明服務有哪些功能 且日後將不再提供。
  • 預期的版本管理模式:將新成員新增至服務,做為 持續改良通訊協定。 通訊協定列舉 (列出目錄) 可讓用戶端找出 支援的文件類型 範例:

    • 版本 1...

      service Fonts {
          FontProvider provider;
      };
      
      protocol FontProvider {
          GimmeDaFont(string font_name) -> (fuchsia.mem.Buffer ttf);
      };
      
    • 在第 2 版中,漸進式更新...

      service Fonts {
          FontProvider provider;
          FontProvider2 provider2;
      };
      
      protocol FontProvider2 {
          compose FontProvider;
          GetDefaultFontByFamily(string family) -> (string family);
      };
      
    • 在第 3 版中,全新改版...

      service Fonts {
          [Deprecated]
          FontProvider provider;
          [Deprecated]
          FontProvider provider2;
          TypefaceChooser typeface_chooser;
      }
      
      protocol TypefaceChooser {
          GetTypeface(TypefaceCriteria criteria);
      };
      
      table TypefaceCriteria {
          1: Family family;
          2: Style style;
          3: int weight;
      };
      

問題 5:如果元件執行個體想要公開與單一基礎邏輯資源相關的多項服務,該以何種形式呈現?

回應

  • 元件可定義多個服務 元件資訊清單。 範例:

    // frobinator.cml
    {
        ...
        "exposes": [
            { "service": "fuchsia.frobinator.Fooer" },
            { "service": "fuchsia.frobinator.Barer" },
        ],
        ...
    }
    
  • 接著,元件還會實作這些服務 但這些服務的使用者不需要留意 資訊。