RFC-0173:Component Framework API 中的結構化設定

RFC-0173:元件架構 API 中的結構化設定
狀態已接受
區域
  • 元件架構
說明

定義基本結構化設定功能的公開實作方式。

問題
  • 95372
變更
作者
審查人員
提交日期 (年/月)2022-04-11
審查日期 (年/月)2022-06-30

摘要

元件架構 API 和結構化設定實作方式變更。

提振精神

結構化設定已通過 RFC-0127 規範,其描述了功能的整體架構和發展藍圖,但並未指定根據元件架構 RFC 條件進行統合所需的一些實作詳細資料。RFC-0146 說明瞭用於宣告設定結構定義的 CML 語法,而 RFC-0158 則說明使用者將產生哪些用戶端程式庫來存取設定。

fuchsia.git 提供結構化設定的初始原型。元件架構團隊正在等待功能在樹狀結構外公開,直到此 RFC 中的 API 和行為獲得統合為止。

相關人員

講師:leannogasawara@google.com

審查者:

  • geb@google.com (元件架構)
  • jsankey@google.com (RFC-0127 作者)

諮詢:hjfreyer@google.com、xbhatnag@google.com、aaronwood@google.com、mcgrathr@google.com、shayba@google.com

社交化:這項 RFC 產品包括與 Component Framework 團隊一起的設計審查產品、原型設計程序中後續的程式碼審查,以及早期採用者的意見回饋。

範圍

本 RFC 描述有關元件架構的變更,包括使用 RealmBuilder 測試環境中的「第 1 階段」和「第 2 階段」導入計畫的第 2 階段內容,僅涵蓋「靜態值」功能,以及使用 RealmBuilder 測試環境中「來自父項的值」的有限版本。

設計

元件架構包含幾項新責任:

  • 編譯的元件資訊清單包含設定值來源
  • 元件解析器會擷取元件的設定值
  • 元件管理服務會將元件設定編碼為永久性的 FIDL 訊息
  • 元件執行器會將編碼設定傳送給元件
  • RealmBuilder 允許設定測試中已啟動子項的設定值

設定值

具有設定結構定義的每個元件都必須定義設定值。這些項目會以 fuchsia.component.config.ValuesData 的形式在元件架構的不同部分之間儲存及轉移:

library fuchsia.component.config;

type ValuesData = table {
    1: values vector<ValueSpec>:MAX;
    2: checksum fuchsia.component.decl.ConfigChecksum;
};

// NOTE: table to allow defining mutability in future revisions
type ValueSpec = table {
    1: value Value;
};

type Value = flexible union {
    1: single SingleValue;
    2: vector VectorValue;
};

type SingleValue = flexible union {
    1: bool bool;
    2: uint8 uint8;
    3: uint16 uint16;
    4: uint32 uint32;
    5: uint64 uint64;
    6: int8 int8;
    7: int16 int16;
    8: int32 int32;
    9: int64 int64;
   10: string string:MAX;
};

type VectorValue = flexible union {
    1: bool_vector vector<bool>:MAX;
    2: uint8_vector vector<uint8>:MAX;
    3: uint16_vector vector<uint16>:MAX;
    4: uint32_vector vector<uint32>:MAX;
    5: uint64_vector vector<uint64>:MAX;
    6: int8_vector vector<int8>:MAX;
    7: int16_vector vector<int16>:MAX;
    8: int32_vector vector<int32>:MAX;
    9: int64_vector vector<int64>:MAX;
   10: string_vector vector<string:MAX>:MAX;
};

值的儲存順序與編譯資訊清單中的對應欄位相同。

每個設定結構定義都包含總和檢查碼,這是所有欄位名稱和類型的雜湊。ValuesData 中的總和檢查碼必須與已編譯元件資訊清單中的總和檢查碼相符。

我們已考慮並拒絕一個替代方法,以便使用與傳送至元件本身相同的編碼來儲存定義的值。

元件值來源

以封裝元件來說,設定檔值會儲存在「設定檔值檔案」中。設定檔是 fuchsia.component.config.ValuesData 的永久 FIDL 編碼。

由於設定值不會儲存在元件資訊清單中,因此元件架構必須在解析元件時知道要在哪裡找到值。系統會將一個新欄位新增至設定結構定義的編譯表示法:

library fuchsia.component.decl;

type ConfigSchema = table {
    // ...existing fields...

    3: value_source ConfigValueSource;
};

type ConfigValueSource = flexible union {
    /// The path within the component's package at which to find config value files.
    1: package_path string:MAX;
};

彈性聯集可用來隨著時間新增設定值來源。

按照慣例,值檔案會封裝在 meta/${manifest_basename}.cvf 中。舉例來說,如果元件的資訊清單封裝在 meta/foo.cm 中,其值檔案將會封裝至 meta/foo.cvf

產生結構化設定的建構規則必須確保元件資訊清單包含封裝元件設定的路徑。舉例來說,樹狀結構內結構 GN 建構作業會要求設定值目標參照元件目標,以便同意封裝位置:

import("//build/components.gni")

# NOTE: results in a package path of `meta/my_component.cm`
fuchsia_component("my_component") {
  manifest = "meta/my_component.cml"
  deps = [ "..." ]
}

# NOTE: internally calls `get_target_outputs(":my_component")` to determine
# the correct packaging path
fuchsia_structured_config_values("my_config_values") {
  component = ":my_component"
  values_source = "config/my_component.json5"
}

fuchsia_package("my_package") {
  deps = [
    ":my_component",
    ":my_config_values",
  ]
}

為了將值儲存在資訊清單本身中,我們考慮並拒絕了替代方法

為根據資訊清單的封裝路徑推斷套件內值檔案的位置,我們考慮了「替代方法」,並遭到拒絕。

為了將索引 blob 新增至指向元件資訊清單和值檔案的套件,我們已考慮並拒絕了替代方法

元件解析度

元件解析器負責擷取元件的封裝值,並以 fuchsia.sys2.Component 資料表中的新欄位的形式,將這些值傳回至元件管理服務:

library fuchsia.sys2;

// NOTE: This type is returned by fuchsia.component.resolution.Resolver/Resolve.
type Component = resource table {
    // ... existing fields ...

    /// Binary representation of the component's configuration values
    /// (`fuchsia.component.config.ValuesData`).
    4: config_values fuchsia.mem.Data;
};

為配合從解析器傳回元件資訊清單的方式,ValuesData 會以 fuchsia.mem.Data 的形式傳回。如此一來,已編譯的資訊清單和設定值的總和就能超過單一 Zircon 管道訊息的大小。

這需要元件解析器剖析元件資訊清單,以便解讀資訊清單中的值來源;而先前解析器則能在不瞭解已編譯的資訊清單表示法的情況下,將原始位元組直接傳回至元件管理服務。

編碼設定值

元件管理員擁有設定結構定義和值後,必須將這些值和元件的設定結構定義檢查碼傳遞至元件的執行元件器,以便透過執行階段專屬介面佈建到元件。

VMO 內容

系統會按照已編譯資訊清單的順序,將元件的設定值編碼為永久 FIDL 結構,其中的欄位順序也會相同。拒絕的替代方法是將最終設定編碼為 FIDL 資料表而非結構。我們也針對已解析的設定,判定並拒絕非 FIDL 編碼。

為此,元件管理服務必須瞭解 FIDL 傳輸格式,才能執行產生的 FIDL 繫結能夠成功剖析的訊息執行階段編碼。

元件管理服務會將編碼設定寫入 VMO,包含以下內容:

  • 位元組 0..1 包含總和檢查碼長度的 N,以小整數表示
  • 位元組 2..2+N 包含總和檢查碼
  • 位元組 3+N..ZX_PROP_VMO_CONTENT_SIZE 包含永久 FIDL 訊息,其中元件的設定值已編碼為結構體

總和檢查碼會儲存為標頭的變數長度部分,將 VMO 編碼與任何特定雜湊函式輸出的大小分離。變更用於生成檢查碼的雜湊演算法時,不需要變更 VMO 編碼。

將 VMO 傳遞至執行器

設定 VMO 建立完成後,會以 fuchsia.component.runner.ComponentStartInfo 的欄位的形式傳遞至執行元件:

library fuchsia.component.runner;

// NOTE: Passed to fuchsia.component.runner.ComponentRunner/Start.
type ComponentStartInfo = resource table {
    // ... existing fields ...

    /// Binary representation of the component's configuration.
    ///
    /// # Layout
    ///
    /// The first 2 bytes of the data should be interpreted as an unsigned 16-bit
    /// little-endian integer which denotes the number of bytes following it that
    /// contain the configuration checksum. After the checksum, all the remaining
    /// bytes are a persistent FIDL message of a top-level struct. The struct's
    /// fields match the configuration fields of the component's compiled manifest
    /// in the same order.
    7: encoded_config fuchsia.mem.Data;
};

對於將執行器編碼延遲使用 FIDL 描述 VMO 中的總和檢查碼版面配置,我們也考慮並拒絕這些替代方案。

執行編碼設定的元件

每個執行元件都必須與執行中的元件建立合約,以便他們存取編碼設定。這份合約由存取者程式庫行使。

ELF 執行元件

ELF 執行元件會提供設定 VMO 做為啟動控制代碼:

// in //zircon/system/public/zircon/processargs.h:
#define PA_VMO_COMPONENT_CONFIG 0x1Du

驅動程式執行元件

驅動程式庫執行元件會在 fuchsia.driver.framework.DriverStartArgs 資料表中提供設定 VMO 做為欄位:

library fuchsia.driver.framework;

// NOTE: Passed directly to a driver upon starting.
type DriverStartArgs = resource table {
    // ... existing fields ...
    7: config zx.handle:VMO;
};

測試執行者

我們尚未發現有能力限制及引導設計的設計,因此尚未在測試執行工具中實作結構化設定支援。

使用 RealmBuilder 覆寫設定值

RealmBuilder 支援控制其啟動的元件設定值。使用者可以為個別欄位提供值,而且根據預設,他們必須指定元件結構定義中所有欄位的值。這會建議元件本身的整合測試,以完整列舉要測試的設定選項矩陣。

RealmBuilder 也可讓測試作者使用批發或覆寫個別欄位,載入元件的封裝值。如此一來,測試就能啟用在部分測試外不實用的特定測試功能,同時仍繼續使用該元件的其餘「正式版」設定。

下列方法會新增至 RealmBuilder:

    LoadPackagedConfigValues(struct {
        name fuchsia.component.name;
    }) -> (struct {}) error RealmBuilderError2;

    SetConfigValue(struct {
        name fuchsia.component.name;
        key fuchsia.component.decl.ConfigKey;
        value fuchsia.component.config.ValueSpec;
    }) -> (struct {}) error RealmBuilderError2;

系統會擴充 RealmBuilder 用戶端程式庫,藉此公開這項功能。

實作

我們最初提議的這個 RFC 內容已在 fuchsia.git 中實作,並於 fuchsia.git 中提供,首先是取得使用者瞭解元件架構在疊代時可能會進行破壞性變更的許可清單。凡是符合這項 RFC 最終設計的變更,都會先發生變更,然後再向樹狀結構外結構客戶提供結構化設定。

效能

這項 RFC 中的設計可能會影響系統執行階段效能,但以完成解析和啟動元件所需的時間為基準。使用 Fuchsia 建構的產品品質時,作者的知識不是什麼指標。我們在拓撲中持續針對部分元件的開始時間進行基準測試,也會繼續監控這些元件是否有迴歸問題,但尚未在這項功能的原型設計過程中發現任何發現。

如果設定儲存在記憶體中,結構化設定可能會增加系統記憶體用量,而這種設計會將設定儲存在 VMO 內,而存取子程式庫可能會在將內容剖析為各元件實作所使用的特定領域類型後關閉,藉此盡可能降低系統記憶體用量。

回溯相容性

這個 RFC 假設平台版本管理在元件架構中可以獲得足夠的執行階段支援,然後才發現需要改變將設定編碼至元件傳遞的 VMO 之前。

RealmBuilder 的覆寫實作方式可讓與已啟動元件相同的套件中的測試,在元件的設定結構定義中取得執行階段依附元件,而在其他測試中使用的元件作者,則在變更設定欄位類型或移除設定欄位時,必須執行預防措施。

安全性考量

結構化設定可能會用來控管元件中的重要安全性功能,因此實作作業必須傳送正確的值。

元件設定的來源與資訊清單相同,但日後我們可能會擴充現有元件解析器或建構新的解析器,讓兩者之間產生較鬆關係。我們需要練習,確保元件設定一律清晰且可供稽核。

隱私權注意事項

根據 RFC-0127,結構化設定並非專為儲存使用者產生的資料而設計。

測試

這項設計中最機密的部分,就是元件管理服務使用的動態 FIDL 編碼器。其原型已在 FIDL 的 GIDL 符合套件中整合為「語言後端」,確保其支援的類型子集能夠產生與靜態類型 FIDL 繫結相同的發布訊息。

說明文件

樹狀結構內開發人員目前可以使用原型的說明文件

缺點、替代方案和未知

替代做法:以輸入格式儲存值

我們避免使用未設為類型的 fuchsia.component.config.ValuesData,而是使用 FIDL 編碼酬載且其類型符合元件結構定義的類型,在元件架構之間儲存並轉移元件的設定值,類似直接傳遞至元件的 VMO 編碼方式。

這可能會使磁碟上呈現略為精簡,且在視覺上與最終傳送至元件的已輸入酬載保持一致。

本 RFC 中提議的設計可讓各種工具瞭解設定,而不必重新實作動態類型的 FIDL 剖析器。日後,如果 FIDL 工具鍊提供更適用於「反思」訊息的一般工具,我們可能會重新考慮這項決定。

替代方法:在資訊清單中儲存值

在資訊清單中儲存內嵌值可簡化實作的數個元素 (從元件解析器移除責任),但產品之間元件資訊清單經過變更的 blob 雜湊會產生費用。

替代方案:依副檔名尋找值檔案

除了在元件資訊清單中新增值來源之外,我們也可以從封裝慣例中推斷出值檔案的位置。這樣就可以對目前唯一的慣例,造成不必要的執行階段依附元件。

替代做法:透過套件元件索引尋找值檔案

將第三個 blob 新增至套件後,我們就能對資訊清單和值檔案進行讀取,進而產生更簡潔的建構圖、簡化資訊清單編譯程序的某些元素,並讓元件解析器瞭解比剖析整個元件資訊清單更為簡單的檔案格式。

不過,這麼做會增加系統映像檔的大小,並讓使用者明顯瞭解元件解析方式。

替代做法:將設定編碼為 FIDL 資料表

將設定欄位編碼為資料表而非 FIDL 結構,如同 RFC-0127 中的建議選項。資料表有很多實用的屬性用於演進,但也需要剖析器,以假設可以省略任何欄位。透過結構化設定,元件管理服務一律可提供所有欄位,而且呼叫端不必處理未提供任何值的情況。

FIDL 結構會耗用較少的位元組來存放相同資料,而且剖析速度稍微快一點。

替代方法:使用非 FIDL 編碼設定設定值

FIDL 以外的編碼或許可行,但 FIDL 傳輸格式適用於這個用途,並選擇其他編碼,會使瞭解 Fuchsia 概念數量的淨增加。使用 FIDL 編碼符合日後的元件架構,希望能夠更緊密地與 FIDL 整合,並允許使用 FIDL 產生的繫結做為存取子的實作,讓結構化設定可以享有已經達到的效能和二進位檔大小最佳化帶來的好處。

替代方法:執行器編碼設定

我們可以在每個執行元件中對 VMO 進行編碼,而不必在元件管理服務中對設定進行編碼。這可讓在不同抽象層級運作的執行器以不同格式提供設定。例如,JavaScript 程式碼的執行元件能以該語言執行階段的形式傳送設定,而不必要求每個 JavaScript 元件剖析 VMO。

但是,如測試所述,FIDL 編碼是此設計之正確性最機密的部分。現今有多個執行器都會使用以 FIDL 為基礎的編碼進行設定,也就是說,會有多個二進位檔 (並實作程式設計語言) 負責這個敏感工作。集中處理元件管理服務的設定編碼責任,我們可以限制實作,並透過廣泛測試提升整體功能的信心。

我們提出的設計會使執行元件難以根據元件的結構化設定值設定行為,因為每個執行元件都必須根據元件宣告的結構定義剖析編碼的 FIDL。這個情況相當複雜,使每個執行元件負責處理程式設計,而這也是不理想的做法。為透過元件設定設定執行器時,我們會設計獨立的介面,以便將元件設定的值傳送至執行元件。為執行元件設定定義明確介面也有利於從海倫定律效果模擬元件的設定命名空間。

替代方案:使用 FIDL 說明 VMO/checksum 版面配置

我們不必將 VMO 中的設定檢查碼編碼為標頭位元組,而是以 FIDL 描述 VMO 的 ABI:

type ConfigVmo = struct {
    checksum bytes:MAX;
    contents bytes:MAX;
};

這在每項設定 VMO 中會佔用較多空間,而且也需要周遊 contents 的位元組兩次。