| RFC-0173:元件架構 API 中的結構化設定 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 定義基本結構化設定功能的公開實作方式。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 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 作者)
Consulted: 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 管道訊息的大小。
這需要元件解析器剖析元件資訊清單,才能解讀資訊清單中的值來源,而先前解析器可直接將原始位元組傳回元件管理服務,不必瞭解已編譯的資訊清單表示法。
編碼設定值
Component Manager 取得設定結構定義和值後,必須將這些值和元件的設定結構定義的總和檢查碼傳遞至元件的執行器,以便透過執行階段專屬介面將元件佈建至元件。
VMO 內容
系統會將元件的設定值編碼為持續性 FIDL 結構體,欄位順序與編譯後的資訊清單相同。遭拒的替代方案是將最終設定編碼為 FIDL 表格,而非結構體。我們也考慮並拒絕使用非 FIDL 編碼來解析設定。
這需要元件管理服務瞭解 FIDL 傳輸格式,才能執行訊息的執行階段編碼,並由產生的 FIDL 繫結成功剖析。
元件管理服務會將編碼後的設定寫入 VMO,內容如下:
- 位元組
0..1包含檢查碼的長度N,以 little-endian 整數表示 - 位元組
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 中實驗性實作並提供使用,最初是透過允許清單提供,使用者瞭解元件架構在疊代時可能會進行重大變更。為配合這項 RFC 的最終設計,系統會在向樹狀結構外客戶提供結構化設定前,先進行變更。
效能
這份 RFC 中的設計可能會影響系統執行階段效能,例如解析及啟動元件所需的時間。據作者所知,這項指標目前並未成為以 Fuchsia 建構產品時的品質瓶頸。我們確實會持續對拓撲中的某些元件進行啟動時間基準化,並持續監控這些元件是否出現回歸情形,但在這個功能的原型設計過程中,我們尚未發現任何回歸情形。
如果設定副本保留在記憶體中,結構化設定可能會增加系統記憶體用量。這項設計會將設定儲存在 VMO 中,存取器程式庫在將內容剖析為每個元件實作使用的網域專屬型別後,可能會關閉 VMO,因此可將這類情況降到最低。
回溯相容性
這項 RFC 假設在發現任何需要演進的項目之前,平台版本控管會在元件架構中提供足夠的執行階段支援,以利將設定編碼至傳遞給元件的 VMO。
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 法則式效應影響元件的設定命名空間。
替代方案:使用 FIDL 說明 VMO/總和檢查碼版面配置
我們不必將設定檢查碼編碼為 VMO 中的標頭位元組,而是可以根據 FIDL 說明 VMO 的 ABI:
type ConfigVmo = struct {
checksum bytes:MAX;
contents bytes:MAX;
};
這會在每個設定 VMO 中佔用稍微多一點的空間,而且還需要遍歷 contents 的位元組兩次。