RFC-0158:結構化設定存取子 | |
---|---|
狀態 | 已接受 |
領域 |
|
說明 | 針對面向使用者的設定存取子的概要理念和要求。 |
問題 | |
毛皮變化 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2021-12-20 |
審查日期 (年-月-日) | 2022-05-04 |
摘要
針對以下項目的相關規定、設計理念和高階實作細節 面向使用者產生的存取子程式庫,實作 RFC-0127 的結構化做法 設定。
提振精神
此 RFC 以 RFC 適用於結構化設定,概述設計原則和 開發人員在存取自家資源時 此外還會從 0 自動調整資源配置 您完全不必調整資源調度設定
相關人員
講師:hjfreyer@google.com
審查者:
- geb@google.com (元件架構)
- jsankey@google.com (RFC-0127 作者)
- yifeit@google.com (FIDL)
諮詢:xbhatnag@google.com、hjfreyer@google.com、jamesr@google.com
社交:此 RFC 是規範其他人員之間的討論產品 元件架構團隊和 FIDL 團隊,回答現有的問題 ,在原型 RFC-0127 的初始階段進行原型設計時。
設計
RFC-0127 建立面向使用者的介面草圖,以便使用設定值 程式碼。它提供專為 而且只有單一進入點 傳回有效的設定。
目標
我們針對存取子設下了幾個目標:存取子應傳達不為失 覆寫值不透明、可進行偵錯,並提供類型介面 Fuchsia 開發人員熟悉這套系統,並盡可能重複使用現有的工具。
無法滿足
元件作者必須能夠假設值隨時可用,並加上
所有欄位都會填入資料設定欄位是否可為空值必須與
輸入宣告 (寫入可為空值欄位時,
設定結構定義) 和存取子函式不應出現
傳回錯誤或擲回例外狀況。存取子應能快速失敗,並
當元件執行時 (例如 abort()
) 收到設定時就終止執行
無法剖析的酬載
不透明
根據 RFC-0127,元件架構最終會提供 在執行階段覆寫元件的設定值從作者的角度來看 剖析的類型不會說明任何可能出現的覆寫值。 產生的程式庫不需處理覆寫作業
可偵錯
存取者應提供輸出元件設定的選項 做為檢查階層這麼做可在當機報告中納入設定 且幾乎不需要強制新增元件 將所有設定值儲存在記憶體中,複製元件本身的副本。
產生檢查輸出內容的替代方案之一,是在 元件管理員的檢查。
符合人體工學
產生的程式庫應有單一頂層類型,內含所有 元件的設定欄位它應由單一頂層內容傳回 存取子函式
使用者只能與單一的命名空間或程式庫名稱互動 因此可以控管
熟悉
針對設定結構定義產生的類型應讓 FIDL 的使用者熟悉 繫結。
語法
請使用 RFC-0146 的語法來考慮這個 config
字段:
config: {
check_interval_ns: { type: "int64" },
data_path: {
type: "string",
max_size: 256,
},
test_only: { type: "bool" },
},
建構範本
在 fuchsia.git
中,必須在與
元件,其中定義設定鍵。舉例來說,使用 Rust 二進位檔:
import("//build/components.gni")
import("//build/rust/rustc_binary.gni")
fuchsia_component("my_component") {
manifest = "meta/my_component.cml"
deps = [ ":my_bin" ]
}
fuchsia_structured_config_rust_lib("my_component_config") {
library_name = "my_component"
# NOTE: This target internally depends on a target which is generated by the
# fuchsia_component() template, so the graph is not cyclic:
#
# :my_component -> :my_bin -> :config_lib -> :my_component_manifest_compile
#
# where :my_component_manifest_compile does not depend on :my_component
component = ":my_component"
# By default all fields are included, this Inspect won't have `data_path`
inspect_skip_fields = ["data_path"]
}
rustc_binary("my_bin") {
sources = [ "src/main.rs" ]
deps = [ ":my_component_config" ]
}
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",
]
}
我們會在下列情況中,為樹狀結構外客戶開發類似的版本整合: 而且結構化設定已準備就緒,可供 OOT 使用。
C++
#include "src/my_project/my_component/config.h"
int main(int argc, char** argv) {
auto config = my_component::Config::TakeFromStartupHandle();
auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
auto inspector_ = std::make_unique<sys::ComponentInspector>(context);
config.RecordInspect(inspector_.GetRoot().CreateChild("config"));
if config.test_only() {
// ...
}
std::ifstream file(config.data_path());
// ...
while (true) {
// ...
std::this_thread::sleep_for(
std::chrono::nanoseconds(config.check_interval_ns()));
}
}
驅動程式須有起始引數,才能避免 process-global 依附元件:
#include "src/my_project/my_component/config.h"
zx_status<> Init(fdf::wire::DriverStartArgs& start_args, /*...*/) {
auto config = my_component::Config::TakeFromStartArgs(start_args);
// ...
}
在 fuchsia.git 建立範本時,系統會在 target_gen_dir
中加入標頭,並新增
提供給程式庫的 include_dirs
,方便使用者加入產生的
做為元件的導入標頭。
在 SDK 中建構存取子支援時,我們會確保使用者 設定 include 目錄版面配置,使其符合任何的樣式指南。
Rust
#[fuchsia::component]
async fn main() {
let inspector = fuchsia_inspect::component::inspector();
let config_node = inspector.root().create_child("config");
let config = my_component::Config::take_from_args();
config.record_inspect(&config_node);
if config.test_only {
// ...
}
let contents = std::fs::read(&config.data_path).unwrap();
let mut interval = Interval::new(Duration::from_nanos(config.check_interval_ns));
let _checker = Task::local(async move {
while let Some(()) = interval.next().await {
// ... use `contents` ...
}
});
// for completeness, set up an /out directory for our inspect and serve it
let mut fs = ServiceFs::new_local();
inspect_runtime::serve(inspector, &mut fs).unwrap();
fs.take_and_serve_directory_handle().unwrap();
while let Some(()) = fs.next().await {}
}
請注意,本範例使用非同步和 VFS 來示範執行檢查作業,但 執行者和 VFS 實作時並不需要 結構化設定值
實作
版本管理
存取子會從以下項目接收元件架構中的設定值: 編碼設定結構定義的總和檢查碼 (請參閱 適用於 CML 中的 RFC,日後的 RFC 將指定 交付機制)。存取子必須檢查已收到的總和檢查碼 完全符合產生存取子的檢查碼,取消 。如此可避免誤解酬載 扮演最終守衛,避免元件二進位檔發生封包遺失的情形,且/或 列出從不同結構定義編譯而成的資訊清單。
這項設計的意味著需要重新編譯元件 專案設定結構定義變更時
拒絕的替代方案是執行元件 與元件管理服務合作,在開始前確認檢查碼是否正確無誤 元件。
程式庫內部構造
根據 RFC-0127,結構化設定酬載會編碼為永久性 以 struct 做為主要物件的 FIDL 訊息 (進一步詳述 會在日後的 RFC 中記錄得來)。
我們會產生 FIDL 程式庫來剖析編碼訊息, 產生小型的執行階段專屬包裝函式程式庫,瞭解如何
- 從語言或執行元件專屬的執行階段擷取編碼訊息
- 依據元件資訊清單中的資訊檢查編碼訊息的檢查碼
- 叫用 FIDL 繫結解碼功能
- 將解碼的 FIDL 網域物件轉換為產生的類型
我們已考慮並拒絕
- 一般化 FIDL 中的設定相鄰功能
- 在 FIDL 中新增自訂設定屬性
- 定義用於產生設定存取子的 fidlgen 後端
- 直接從產生的輔助程式公開 FIDL 網域物件
- 產生沒有 FIDL 依附元件的設定存取子
命名
產生的包裝函式程式庫將包含
存取設定根據預設,包裝函式的命名空間與
產生此 GN 規則的目標名稱,且可以使用
library_name
引數。
產生的 FIDL 程式庫會從建構作業收到名稱
範本預設會移除底線的 GN 目標名稱,並在後方加上
平台名稱 (fuchsia.
代表樹狀結構內元件)。例如,在
語法片段,因此 GN 範本叫用會建立 FIDL 程式庫名稱
fuchsia.mycomponent
。
產生的結構會命名為 Config
,我們可能會允許使用者覆寫
也就是一般要求的功能
設定欄位 ID 的規則代表所有設定 索引鍵是有效的 FIDL 欄位 ID,不需要進行管理。
產生的 FIDL 程式庫
每個結構化設定結構定義都會編譯成 FIDL 結構, 轉換成在結構化設定包裝函式中定義的類型。 使用者不會看到 FIDL 工具鍊產生的類型。
以上述語法範例來說,這會是:
library fuchsia.mycomponent;
type Config = struct {
check_interval_ns int64;
data_path string:256;
test_only bool;
};
產生的包裝函式程式碼
每個包裝函式都會包含與產生的 FIDL 網域相對應的類型 物件的型別,但使用其他工廠函式來擷取設定 以及記錄要檢查設定的方法。
產生的 C++ 程式庫將提供使用者熟悉的介面 統合 C++ FIDL 繫結中自然型別的種類。例如,上述 語法範例會在 C++ 中產生:
namespace my_component {
class Config {
public:
static Config TakeFromStartupHandle() noexcept;
void RecordInspect(inspect::Node* node);
const uint64_t& check_interval_ns() const { return check_interval_ns_; }
uint64_t& check_interval_ns() { return check_interval_ns_; }
const std::string& data_path() const { return data_path_; }
std::string& data_path() { return data_path_; }
const bool& test_only() const { return test_only_; }
bool& test_only() { return test_only_; }
private:
// ...
};
};
在 Rust 中,我們會產生與單一繫結變種版本類似的型別。 上述的語法範例會為 Rust 元件產生這個項目:
pub struct Config {
pub check_interval_ns: u64,
pub data_path: String,
pub test_only: bool,
}
impl Config {
pub fn take_from_args() -> Self { ... }
pub fn record_inspect(&self, node: &fuchsia_inspect::Node) { ... }
}
我們會產生一個「變種版本」每個語言或執行階段的存取子程式庫 提供以不同方法提供編碼設定的環境 (將在日後的 RFC 中評估執行器實作的詳細資料)。例如: C++ 驅動程式與 C++ 元件執行的存取子程式庫不同 這些指令就會直接由 ELF 執行元件指派
支援的語言
我們一開始會支援 C++ 和 Rust。一段時間後,我們將涵蓋 支援的語言。
依附元件
每個生成程式庫的依附元件都代表對 OOT 整合商的稅金 請盡量避免使用
樹狀結構外支援
樹狀結構外客戶最終仍須自行 與 FIDL 工具鍊整合 花瓣建構應用程式
成效
剖析結構化設定酬載可能會增加 各元件的開始時間,而在 元件先前編譯的設定值,直接整合到其二進位檔中。 我們預期使用者也不會看見任何影響,因為設定剖析作業 預期為一次性作業。
內部重複使用剖析器可以減輕這個潛在影響 所產生的效能已根據基準測試 持續不斷。
我們會監控「TimeToStart
」的成效指標。
回溯相容性
設定存取子程式庫是由元件編譯而成 並嵌入與 資訊清單。我們不支援元件元件之間的任何版本偏差 設定結構定義、其值檔案及其存取子程式庫日後 整體元件或許可以支援設定值的演變 但從元件的角度來看 並確保設定保持一致
安全性考量
元件應信任收到的設定酬載 也就是元件架構 只有在可提供相容的設定時,才能啟動元件。 後續 RFC 將詳細定義這個解析度和編碼的詳細資訊。
我們希望提供不可為空值且不可為空值的存取器,以減少 在程式碼中使用預設設定值,以便您更容易稽核 實際值會在執行階段執行,並減少可能的面積 設定錯誤與後續攻擊
隱私權注意事項
日後如要擴充結構化設定,可能需要調整才能支援 處理 PII,但我們預期不會有任何設定存取子的變更 需要注意的事項
不必在產生的檢查輔助程式中遮蓋使用者資料, 讀取值時,Archivist 會執行遮蓋作業。結構化使用者 必須將設定欄位新增至產品的 選取器清單 (顯示在當機報告中)。
測試
如要協助開發人員編寫測試,則應建構 定義物件
存取子程式庫的適用範圍將受到多語言合規性測試的約束 結構化設定,確保可順利載入設定 解析、編碼、傳送及剖析的元件 結果會出現合規套件
說明文件
系統會記錄存取子程式庫,並附上各種支援語言的範例 ,進一步瞭解結構化設定
缺點、替代方案和未知
替代做法:在元件管理服務的檢查頁面中公開設定
與其在存取子程式庫中產生檢查程式碼 將已解析的設定值新增至元件管理服務的檢查輸出內容。另有 使用 CPU 統計資料的前例,但會針對此情況追蹤資源使用量 功能不易,因此我們偏好使用會「結算」的解決方案記憶體用量 加入設定值的元件
替代做法:存取用的全域變數
我們可以考慮透過公開對單一全域執行個體存取權的存取子 API 專屬屬性舉例來說,在 Rust 中:
static CONFIG: Lazy<Config> = Lazy::new(|| /* ...retrieve from runner... */);
使用單一設定值全域執行個體相當吸引到 因為 Google Cloud 會運用語言功能 將元件設定的單一副本例項化至記憶體 逐步完成任務。從概念上來說,這牽涉到開發人員經常思考的構想 此外還會從 0 自動調整資源配置 您完全不必調整資源調度設定
但是許多語言不建議使用全域/靜態 (在必要時 並選用這種 API 模式會強制開發人員採用 並非完全必要的個別元件的開發人員仍 選項,納入產生的存取子結果成為全域變數。
此外,某些環境 (例如目前的驅動程式庫架構) 不建議 禁止使用隱式初始化的全域變數。風格 開發人員應該會盡可能熟悉產生的設定存取權 同時使用函式呼叫 只有在需要額外的參數時,環境才會不同。
替代做法:執行者驗證
若是 ELF 型元件,則可納入設定 自訂標頭中的核對和,可由執行元件在說明時驗證 。這樣元件架構就能確認 執行元件的 程式碼,將錯誤直接回報至記錄,而不必依賴 以便先正確設定輸出元件 讀取設定儘管如此,我們預期總和檢查碼不相符為 在極少數情況下,如果元件是以 SDK 中的工具封裝 設計並實作更快失敗的檢查碼,會耗費更多心力 驗證步驟
一個設計在建立程序之前驗證設定總和檢查碼的設計 需處理使用單一二進位檔提供多個元件的情況 而且設定介面不同
我們預期用來驗證總和檢查碼的失敗是很常見的情形,因此 提前遷移錯誤的價值有限。我們也有 透過在二進位檔中加入存取子程式庫中繼資料的方式 產品組合工具可驗證 以顯示這些錯誤。 導入預先程序建立驗證程序的複雜度較高, 改善體驗的預期價值,即有機會 平台外部工具發生錯誤,建議我們 功能。
替代方法:修正核對和與將支援檢查為 FIDL 功能
我們可以決定將需要的類型雜湊和偵錯功能加以一般化 ,並提供給 FIDL 的所有使用者。
這個做法長期下來很有影響力,CF 與 FIDL 團隊會 確保他們的技術與時俱進 詢問有關 RFC 及其他主題的討論具體而言,我們將探討以下內容:
- 設計私人或「元件-local」產生 FIDL 程式碼的命名空間 我們可以在沒有依附元件的情況下 影響一般處理序間通訊 (IPC) 用途,也解決相關問題 命名和平台版本管理
- FIDL 中的「lockstep versioning」選項以便提供工具 防止元件資訊清單與實作二進位檔之間出現偏差
- 為 FIDL 類型產生額外的檢查偵錯程式碼
為了支援這些概念,我們需要透過 FIDL 端執行 因此我們選擇不封鎖結構化設定存取者 。我們預計未來的 RFC 將會來描述這些元件及具備其他元件感知能力 FIDL 的功能,以及將結構化設定使用者遷移至新的存取子 API 並視需要顯示。
替代做法:支援 FIDL 繫結,以及可重複使用的執行階段程式庫
我們可以修改 FIDL 後端,以發出其他程式碼以支援結構化
設定用途方法是使用將旗標傳遞至
後端 (例如 --enable-config-codegen
) 或透過自訂屬性
會傳遞到後端 (例如 @structured_config_checksum()
和
@structured_config_inspect()
)。
如此一來,我們就能發出程式碼,用於檢查碼驗證及檢查偵錯 然後透過可重複使用的程式庫叫用這項功能 每個執行階段將編碼設定傳遞至元件的方法
這個選項的優點在於減少「口味」數量我們所說的 因為不需要程式碼產生器瞭解每個執行階段 所提供的設定,這些知識隨時可能存放在可重複使用的程式庫中。這項服務 還能更輕鬆地整合結構化設定 並採用 OOT 建構系統,因為現有建構系統 已整合 FIDL 工具鍊。
不過,這個方法有很大的缺點。FIDL 工具鍊沒有 現在可以將繫結中的特徵標示為不穩定或實驗功能。 繫結產生的所有程式碼都具有相同的穩定性需求,也就是說 在 SDK 可用的 C++ 後端中新增結構化設定屬性 立即開放給所有 SDK 客戶使用這與我們的 目標,可逐步推出結構化設定,不過 在我們繼續參考使用者的同時,儘可能修改 API。
此外,目前尚不確定 FIDL 工具鍊是否適合 產生繫結層,而這些繫結層會依賴較高層級的 Fuchsia 使用目前產生的繫結時檢查這類概念 以實作這些較高階的概念
替代做法:適用於設定的 fidlgen 後端
我們可以定義單獨的後端,產生結構化的設定支援程式碼。 這樣一來,我們就能提供的穩定性資源與目前的 並在產生的專案中加入其他依附元件 存取子。
但為了避免讓使用者進入多個產生的命名空間, ,在單一單一控制中,發出設定支援程式碼和 FIDL 網域物件 命名空間這個產生的程式庫無法連結至同一個二進位檔 同一個程式庫的基本/非設定 FIDL 繫結。實際上 預期使用者都會嘗試產生「config-aware」和「基本」FIDL 繫結 卻會對 FIDL 轉碼器造成新的限制 可以抵禦這類用途或設計我們認為這樣 除非我們與 CF 和 FIDL 技術協調完成,否則這項解決方案只是暫時的解決方案 我們傾向於承擔技術債,可提高設計和 同時保有營運彈性 FIDL 團隊。
替代做法:使用其他層公開 FIDL 程式庫
我們可以產生一個「基礎」所有與全部內容都相同的 FIDL 繫結 先繫結,然後產生額外的「圖層」設定支援代碼 ,瞭解如何從元件的執行階段擷取該 FIDL 類型。這個 可讓我們實現 FIDL IPC 之間的簡潔區隔 和其他問題,但會將使用者公開到兩個產生的命名空間,而沒有 即可享有其他好處
替代做法:產生沒有 FIDL 依附元件的程式庫
我們或許沒有 FIDL 依附關係,因此我們可以 剖析及剖析。如果我們發現我們無法 從針對存取子程式庫產生的 FIDL 依附元件插入使用者。
既有藝術品和參考資料
這裡的工作與 FIDL 有許多相似之處,而此 RFC 讀取方式會 成功導入 Fuchsia 的二進位格式。
Fuchsia 提供多種手寫存取器範例
例如 fshost
的設定類別
用來在切換前剖析以換行符號分隔的自訂格式
以及初步的結構化設定原型
有許多語言專屬的程式庫可提供「型別剖析器」的
動態「設定介面」例如 argv
或 JSON 設定檔
fuchsia.git
中的 argh
是熱門的 Rust 程式庫
剖析 argv
,而 serde
/serde_json
經常用於類似做法
從啟動檔案系統和套件剖析設定檔。