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

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

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

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

摘要

針對結構化設定的元件架構 API 和實作方式進行變更。

提振精神

結構化設定在 RFC-0127 中獲得核准,該 RFC 說明瞭該功能的整體架構和路線圖,但並未指定在元件架構 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 是與元件架構團隊進行設計審查、在原型設計過程中進行後續程式碼審查,以及原型設計的早期採用者提供的意見回饋所產品。

範圍

本 RFC 說明瞭元件架構的變更,以便實作 RFC-0127 實作計畫中的「第 1 階段」和「第 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 執行元件會提供設定 VMOs 做為啟動句柄:

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

驅動程式執行元件

驅動程式庫執行元件會將設定 VMOs 做為 fuchsia.driver.framework.DriverStartArgs 表格中的欄位提供:

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 中提供,最初是在許可清單下進行,讓使用者瞭解元件架構可能會在迭代時進行重大變更。我們會先根據此 RFC 的最終設計進行變更,再向樹狀結構外客戶提供結構化設定。

成效

就解析和啟動元件所需的時間而言,本 RFC 中的設計可能會影響系統執行階段效能,但這並非作者目前所知,以 Fuchsia 建構的產品品質瓶頸。我們確實會持續對拓樸結構中某些元件的開始時間進行基準測試,並會繼續監控這些元件的回歸情形,但在為這項功能製作原型時,我們尚未發現任何回歸情形。

如果設定的副本保留在記憶體中,結構化設定可能會增加系統記憶體用量,而這項設計會將設定儲存在 VMO 中,以便在剖析內容並轉換為各個元件實作所使用的特定網域類型後,關閉存取子程式庫。

回溯相容性

本 RFC 假設在我們發現需要改進將設定編碼至傳遞至元件的 VMOs 的方式之前,平台版本控制會在元件架構中提供足夠的執行階段支援。

RealmBuilder 的覆寫值實作功能可讓與啟動元件位於相同套件中的測試,在元件的設定結構定義上採用執行階段依附元件,而其他測試中使用的元件作者在變更設定欄位類型或移除設定欄位時,則需謹慎行事。

安全性考量

結構化設定可用於控管元件中的安全性重要功能,因此實作方式必須提供正確的值。

元件的設定會來自與其資訊清單相同的來源,但日後我們可能會擴充現有的元件解析器,或建構新的解析器,讓這兩者之間的關係更加鬆散。我們必須謹慎處理,確保元件的設定一律明確且可稽核。

隱私權注意事項

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

測試

這項設計中最敏感的部分,是元件管理服務使用的動態 FIDL 編碼器。其原型已整合為 FIDL 的 GIDL 相容性套件的「語言後端」,以確保針對所支援的類型子集,產生的訊息會與靜態類型 FIDL 繫結相同。

說明文件

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

缺點、替代方案和未知事項

替代做法:以指定格式儲存值

我們可以使用 FIDL 編碼的酬載,在元件架構的各個部分之間儲存及傳輸元件的設定值,而該酬載的類型必須與元件的結構定義相符,類似於直接傳遞至元件的 VMO 所使用的編碼。fuchsia.component.config.ValuesData

這可能會產生稍微精簡的磁碟上呈現方式,並與最終傳送至元件的類型酬載相符。

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

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

將值內嵌在資訊清單中,可簡化實作中的幾個元素 (從元件解析器移除職責),但代價是產品之間的元件資訊清單會變更 blob 雜湊。

替代做法:依副檔名尋找價值檔案

我們不必在元件資訊清單中加入值來源,而是可以從封裝慣例推斷出值檔案的位置。這會在目前僅是慣例的內容上,建立不必要的執行階段依附元件。

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

將第三個 blob 新增至套件,即可管道讀取資訊清單和值檔案,進而產生更簡潔的建構圖表、簡化資訊清單編譯程序的部分元素,並讓元件解析器瞭解更簡單的檔案格式,而無須解析整個元件資訊清單。

不過,這麼做會增加系統映像檔大小,且會對使用者可見的元件解析方式造成重大變更。

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

設定欄位可以編碼為表格,而非 FIDL 結構體,這也是 RFC-0127 中建議的選項。表格具有許多實用的演進特性,但也需要解析器假設可省略任何欄位。有了結構化設定,元件管理服務就能隨時提供所有欄位,呼叫端也不必處理未提供值的情況。

FIDL 結構體占用的位元組較少,可用於傳送相同的資料,且剖析速度略快。

替代做法:使用非 Fidl 編碼來設定值

除了 FIDL 之外,其他編碼也適用於此用途,但 FIDL 線路格式適用於此用途,選擇其他編碼會增加瞭解 Fuchsia 所需的概念數量。使用 FIDL 編碼可讓元件架構與 FIDL 更緊密整合,以便在日後整合,並可將 FIDL 產生的繫結用於存取子的實作,讓結構化設定能享有效能和二進位檔大小最佳化的優點。

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

我們不必在元件管理服務中編碼設定 VMO,而是可以在各個執行元件中編碼。這可讓在不同抽象層級運作的執行程式,以不同格式提供設定。舉例來說,JavaScript 程式碼的執行元件可將設定做為該語言執行階段中的物件提供,而不需要要求每個 JavaScript 元件解析 VMO。

不過,如測試一節所述,從正確性角度來看,FIDL 編碼是這個設計中最敏感的部分。目前有許多執行程式會使用以 FIDL 為基礎的編碼方式設定設定檔,這表示會有多個二進位檔 (以及實作程式語言) 負責執行這項敏感任務。將設定編碼的責任集中在元件管理服務中,可讓我們限制實作方式,並透過廣泛測試,對整體功能更有信心。

由於每個執行元件都需要根據元件的宣告型別別解析已編碼的 FIDL,因此建議的設計會讓執行元件難以根據元件的結構化設定值設定行為。這與讓每個執行元件負責編碼的複雜度相當,而這也是不理想的做法。當您需要從元件的設定檔設定執行項時,我們會設計一個獨立的介面,用於將元件設定檔中的值傳遞至執行元件。為執行元件設定定義明確的介面,也有助於將元件的設定命名空間與 Hyrum 的 Law 樣式效果隔離。

替代方案:使用 FIDL 描述 VMO/校驗和版面配置

我們可以使用 FIDL 說明 VMO 的 ABI,而非將設定總和檢查碼編碼為 VMO 中的標頭位元組:

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

這會在每個設定 VMO 中占用較多空間,且需要兩次遍歷 contents 的位元組。