RFC-0235:元件字典 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 這項提案旨在為元件架構引入字典類型,以便將能力進行捆綁。 |
問題 | |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2023-10-16 |
摘要
本 RFC 建議使用執行階段和宣告式 API,用於建立及轉送功能的套件,稱為字典。
提振精神
目前,元件架構只支援針對其定義的功能進行「點對點」路由:為了將能力 C 從元件 A 路由至元件 B,每個中介元件都必須包含路由路徑片段,才能在相鄰元件之間路由 C。
許多用途都會從將一組功能路由為單一邏輯單元的功能中獲得極大助益。沒有這項功能,這些客戶就必須使用成本高昂、不靈活且不穩定的替代方法。以下是這些用途的範例:
- 幾乎所有元件都會使用
LogSink
、InspectSink
和追蹤提供者等診斷功能,如果能將這些功能以套件形式進行路由,就能大幅簡化路由拓撲。 - 剖析器功能 (例如
fuchsia.debugdata.Publisher
) 與診斷功能類似,但只會在特定版本中啟用。目前我們沒有適當的方法,讓建構作業設定需要跨越整個拓撲的能力。這也是為什麼目前只有在測試領域中啟用分析器的原因。 - 測試領域 Proxy 會將介面公開給測試案例,讓測試用來測試元件。目前,這是一個與元件連線 API 非常相似的自訂介面。這個介面可替換為測試領域 Proxy 傳送至測試的一系列功能。這樣一來,測試領域代理程式就不需要定義自己的抽象層,而且可以讓任何元件架構功能可供測試使用。
- Anywhere 功能會透過多個層級進行轉送,因此您可以透過捆綁簡化轉送作業:
session_manager
:從core
轉送至session_manager
的所有功能都需要由session_manager.cml
重新轉送。這項功能的功能繁多,因此難以維護,且意味著某些非平台功能會流入session_manager.cml
。chromium
:Chromium cml 檔案包含許多重複內容,如果功能可在單一名稱下進行分組及路由,則可大幅簡化。https://fxbug.dev/42072339)
- 元件架構環境是一項功能,可讓您將執行常式和解析器設為可供整個子樹狀結構使用,並隱含地進行路由。如果我們改用常見的捆綁 API 來達成這項目標,將更能與其他元件架構轉送 API 相容,並減輕以環境為基礎的隱含轉送作業的最低權限疑慮。
以下也是推動能力套件的用途,但這些用途有某些特殊考量,需要在本 RFC 中提出建議以外,進行後續設計工作。
- 某些板卡專屬的驅動程式會公開未為平台定義的自訂服務。
bootstrap
領域應會公開這些服務,因為所有驅動程式庫元件都位於該領域,但在平台拓撲中明確命名這些元件並不合理。如果 cml 有辦法將這些服務組合在一起,我們就能處理這個問題。 - 使用
driver_test_realm
的測試也會發生類似問題。這些測試希望將來自驅動程式庫的不同服務轉送至測試。在這些測試中,驅動程式庫測試領域元件位於驅動程式和測試之間,我們希望能夠在這些測試中重複使用驅動程式庫測試領域,而無須修改。
最後,目前已有幾個元件架構 API 涉及分組功能。不過,這些規則是獨立的,只適用於特定情況。以下是一些例子:
- 命名空間是將所有導向至程式的功能 (其
use
宣告中的功能) 分組。 - 公開目錄是元件將功能導向其父項 (也就是公開介面) 的群組。
- 服務功能是將通訊協定分組的一種方式,而服務功能本身也可以分組,形成「匯總」的服務功能。
我們提供這麼多 API 的用意,其實是希望使用者能透過一般抽象化方式,自行定義字典並進行路由,以便發揮最大效益。
相關人員
根據上述用途,我們已將下列團隊視為利害關係人:
- 建築
- 診斷
- 測試
- 工具鏈
- 驅動程式架構
- 安全性
導師:hjfreyer@
審查者:
- abarth@ (架構)
- crjohns@ (Testing)
- markdittmer@ (安全性)
- miguelfrde@ (Diagnostics)
- surajmalhotra@ (Drivers)
- ypomortsev@ (元件架構)
諮詢:
- phosek@
- kjharland@
- anmittal@
- wittrock@
- novinc@
社會化:
在本 RFC 之前,我們已發布兩份內部文件:用途和需求文件,以及核心設計文件。這些文件已獲得利益相關者非正式核准。
我們已將這些文件中的資訊納入本 RFC (如有相關性)。
需求條件
以下是作業字典必須支援的作業,我們是根據用途分析並從現有分組 API 推論而得:
- 第一類:字典是 CF API 中的第一類概念,應以能力形式呈現。
- 匯總:有一個「匯總」運算可從一組功能建構字典。
- 擷取:有一個「擷取」作業可從字典中擷取個別能力,這類功能可像其他能力一樣進行路由和使用。這大致是匯總的反向運算。
- 委派:可在元件之間傳遞字典。
- 巢狀:由於字典是一種能力,因此字典可以包含其他字典。
- 結構:字典會加上中繼資料標記,指出其包含哪些功能。
- 擴充功能:有一個作業可建構新的字典
B'
,繼承B
的內容,並新增其他功能。 - 可變性:字典的內容可能會隨時間變更。不過,可能會有更高層級的政策,對特定字典的可變動性設下限制。
設計
定義
字典定義為一組鍵/值組合,其中鍵為功能名稱 (例如 fuchsia.example.Echo
),而值則為 CF 能力。
能力名稱是 [A-Za-z0-9_-.]
集合中一或多個字元的序列,大小為 1 到 N (目前 N = 100,但我們日後可能會擴充)。
執行階段的字典
我們將介紹公開的 FIDL 通訊協定,提供字典的介面。以 FIDL 虛擬程式碼表示:
library fuchsia.component;
type DictionaryEntry = resource struct {
key DictionaryKey;
value Capability;
};
protocol Dictionary {
Insert(DictionaryEntry) -> ();
Remove(DictionaryKey) -> (Capability);
Lookup(DictionaryKey) -> (Capability);
Enumerate() -> Iterator<DictionaryKey>;
Clone();
};
我們也會推出可公開探索的 FIDL 通訊協定,讓呼叫端建立空字典。
後續設計工作將決定 Capability
的確切類型定義。
元件宣告中的字典
我們先從一些正式化作業開始,以便定義運算。我們將定義四個與每個元件相關聯的特殊字典:元件輸入字典、元件輸出字典、程式輸入字典和程式輸出字典。這些項目統稱為「根字典」。
元件輸入字典是一種字典,其中包含父項為元件提供的所有功能offer
,換句話說,就是元件可轉送from parent
的所有功能。
元件輸出字典是指包含元件將所有功能expose
給父項的字典;換句話說,就是父項可轉送from #component
的所有功能。
程式輸入字典是字典,其中包含元件 use
的所有功能。
程式輸出字典是包含元件所有 capability
宣告的字典。
我們可以根據以下定義表達能力轉送作業:
use
、offer
、expose
和capabilities
是路由運算,可在字典之間路由功能。use
會將能力從根字典轉送至程式輸入字典。expose
會將能力從根字典轉送至元件輸出字典。offer
會將能力從根字典轉送至子項的元件輸入字典。capabilities
會將程式輸出字典中的能力用於路由。
有了這個設計,我們將通用這項功能,讓轉譯作業使用任意字典做為來源,而非僅限於根字典:
use
會將能力從字典轉送至程式輸入字典。expose
會將能力從字典轉送至元件輸出字典。offer
會將能力從字典轉送至子項的元件輸入字典 或其他字典。
宣告
如要定義全新的空字典,請按照下列步驟操作:
capabilities: [
{
dictionary: "diagnostics-bundle",
},
],
如要定義內容繼承自現有字典的字典,請使用 extends
提供該字典的路徑 (請參閱「擴充功能」):
{
dictionary: "diagnostics-bundle",
extends: "parent/logging-bundle/sys",
},
匯總
如要將功能匯總至字典,請在 offer
中使用 to
關鍵字將功能路由至其中。由於字典是由這個元件定義,因此包含該字典的根字典為 self
。系統支援所有相同的關鍵字,就像一般 offer
一樣。舉例來說,您可以使用 as
關鍵字,在目標字典中變更能力名稱。
capabilities: [
{
dictionary: "diagnostics-bundle",
},
],
offer: [
{
protocol: "fuchsia.logger.LogSink",
from: "#archivist",
to: "self/diagnostics-bundle",
},
{
protocol: "fuchsia.inspect.InspectSink",
from: "#archivist",
to: "self/diagnostics-bundle",
},
{
directory: "publisher",
from: "#debugdata",
to: "self/diagnostics-bundle",
rights: [ "r*" ],
as: "coverage",
},
],
委派
委派是指轉送字典:
offer: [
{
dictionary: "diagnostics-bundle",
from: "parent",
to: "#session",
},
],
如同其他能力路徑,您可以使用 as
關鍵字變更目標名稱。
offer: [
{
dictionary: "logging-bundle",
from: "parent",
to: "#session",
as: "diagnostics-bundle",
},
],
動態商品也會提供對應的執行階段 API。
巢狀結構
您可以使用匯總語法將字典路由至另一個字典,讓字典包含其他字典:
capabilities: [
{
dictionary: "session-bundle",
},
],
offer: [
{
dictionary: "driver-services-bundle",
from: "parent",
to: "self/session-bundle",
},
],
擷取
在正式化後,我們將擴充 from
關鍵字,不僅可接受根字典,還可接受巢狀在根字典中的字典。
如要從字典中擷取能力,請在 from
中命名字典。這個字典相對於根字典 (parent
、#child
等)
offer: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "parent/session-bundle",
to: "#window_manager",
},
],
這也適用於 use
:
use: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "parent/session-bundle",
},
],
當字典嵌套在其他字典中時,也可以進行擷取:
use: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "parent/session-bundle/gfx",
},
],
擴充功能
在字典定義中使用 extends
選項,即可繼承其他字典:
capabilities: [
{
dictionary: "session-bundle",
// `session-bundle` is initialized with the dictionary the parent
// offered to this component, also called `session-bundle`.
extends: "parent/session-bundle",
},
],
offer: [
{
dictionary: "session-bundle",
from: "self",
to: "#session-manager",
},
{
protocol: "fuchsia.ui.composition.Flatland",
from: "#ui",
to: "self/session-bundle",
},
],
可變動性
以宣告方式建構的字典是不可變動的,這是實用的安全性屬性。如要讓字典可變動,必須在執行階段建立。
字典中功能的中繼資料
將功能放入字典後,這些功能會保留所有類型資訊和中繼資料,這些資料與字典本身相關聯的中繼資料是分開的。
舉例來說,如果元件 A
將具備 optional
可用性的能力新增至字典,而元件 B
擷取該能力,則在擷取時,該功能將具備 optional
可用性,即使字典本身的可用性為 required
也一樣。
我們可能會在匯總時對可用性設下特定限制。舉例來說,禁止將 required
能力放入 optional
字典可能會違反一般不變量,因為這會違反從目標路由至來源的情況下,可用性不會變弱的規則。
執行階段與陳述式字典之間的互通性
在執行階段建立的字典必須與元件宣告中的字典相容。如果不是這樣,就會迫使使用者只能選擇其中一種,這也證明瞭捆綁的概念基礎不夠廣泛,無法以類似的方式解決這兩種用途。
後續提案將說明互通性設計的詳細資訊。
這項功能的主要用途是驅動程式庫架構,用於在執行階段填入的服務套件路由。
實作
cml 中的到達字典會按照一般管道引入新的 cml 功能。首先,我們會在 cml 和 component.decl 架構中新增字典。接著,我們會更新 cmc
、cm_fidl_validator
、cm_rust
和 realm_builder
,以便編譯、驗證及呈現字典。Scrutiny 也會更新,以便辨識字典,並驗證字典路徑。
我們已著手將字典 (做為 Rust 類型) 整合至元件模型和路由引擎。字典 API 的實作應建立在此工作之上,以便將這些字典用作公開字典 API 的後端,以及轉譯字典功能的傳輸工具。
成效
無需考量特殊效能問題。定義字典的路由應比個別定義組成功能的路由更快,甚至更快。
人體工學
這項設計的主要動機,就是改善建構元件拓樸的符合人體工學。
雖然引入新功能會自然增加 API 的複雜度,但我們認為這會因在拓樸中納入字典而降低複雜度,因此兩者相抵後,複雜度會降低。
回溯相容性
cmc 目前尚未支援元件資訊清單功能的版本控制,因此請務必小心操作,避免與預先建構的資訊清單不相容。幸運的是,針對字典引入的所有新語法都與舊語法相容,因此可讓工作更輕鬆。舉例來說,from
中目前的名稱語法會成為新路徑語法的特殊情況。
如果部分能力路徑會經過字典,則與該能力相關的任何安全性政策都必須套用。
安全性考量
當能力在字典中進行路由時,由於字典中的功能身分會隱藏在路徑中的中介元件,因此會失去部分透明度。不過,您還是可以透過追蹤路徑,將其推斷為供應商。這是為了讓字典發揮彈性和效能,而刻意做出的妥協。
以宣告方式建構的字典無法變更。針對這些字典,您可以執行從目標到來源的字典匯總路徑深度優先搜尋作業,取得完整的內容說明。
如果字典取代環境,系統的安全防護機制將會強化,因為字典與其他功能不同,會以明確的方式進行路由。
隱私權注意事項
這項提案不會影響隱私權。
測試
我們會像測試大多數元件管理員功能一樣測試這項功能,包括在 component_manager
和 cmc
中進行單元測試,以及在 component_manager/tests
中進行整合測試。我們也會在審查中加入整合測試,以便測試字典和政策如何套用至含有字典的路徑。
說明文件
我們會在 //tools/lib/cml
中更新 rustdoc。
新增頁面至 //docs/concepts/components
,以便說明字典。
在 //examples/components
中新增範例。
缺點、替代方案和未知事項
替代方案 1:使用字典的 in
和 into
關鍵字
宣告
字典功能是 cml/component.decl 能力類型,可授予字典存取權。
您可以像宣告任何其他元件架構能力一樣宣告字典。字典建立有兩種變化版本,取決於是否有 extends
關鍵字。
首先,您可以定義全新的空字典:
capabilities: [
{
dictionary: "diagnostics-bundle",
},
],
或者,您也可以在 extends
中指定字典路徑,並在 from
中指定來源,藉此定義字典,讓其內容繼承現有字典 (請參閱「擴充功能」):
{
dictionary: "diagnostics-bundle",
extends: "logging-bundle/sys",
from: "parent",
},
匯總
如要將功能匯總至字典,請使用 into
關鍵字將功能導向至其中:
capabilities: [
{
dictionary: "diagnostics-bundle",
},
],
offer: [
{
protocol: "fuchsia.logger.LogSink",
from: "#archivist",
into: "diagnostics-bundle",
},
{
protocol: "fuchsia.inspect.InspectSink",
from: "#archivist",
into: "diagnostics-bundle",
},
{
protocol: "fuchsia.debugdata.Publisher",
from: "#debugdata",
into: "diagnostics-bundle",
},
],
委派
與主要設計相同。
巢狀結構
只要使用匯總語法將字典路由至字典,即可讓字典包含其他字典:
capabilities: [
{
dictionary: "session-bundle",
},
],
offer: [
{
dictionary: "driver-services-bundle",
from: "parent",
into: "session-bundle",
},
],
擷取
我們推出了新的 in
關鍵字,用來指定要從中擷取能力的字典。
當 in
存在時,能力關鍵字 (protocol
等) 會參照此字典中的功能,而非 from
中所述元件直接提供的功能。
in
可以是名稱或路徑 (名稱可視為退化情況)。如果是名稱,則是指 from
提供的字典。如果是路徑,路徑的第一個區段會指定 from
提供的字典,而路徑的其餘部分會指定這個字典中巢狀的字典路徑。
in
受 use
、offer
、expose
支援。這項設定一律為選用。
offer: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "parent",
in: "session-bundle/gfx",
to: "#window_manager",
},
],
expose: [
{
protocol: "fuchsia.ui.composition.Flatland",
from: "#scenic",
in: "gfx-bundle",
},
],
use: [
{
protocol: "fuchsia.ui.composition.Flatland",
in: "session-bundle/gfx",
},
],
擴充功能
在字典定義中使用 extends
關鍵字,即可繼承其他字典:
capabilities: [
{
dictionary: "diagnostics-bundle",
extends: "parent/logging-bundle/sys",
},
],
offer: [
{
dictionary: "diagnostics-bundle",
from: "self",
to: "#session-manager",
},
{
protocol: "fuchsia.tracing.provider.Registry",
from: "#trace_manager",
into: "diagnostics-bundle",
},
],
替代方案 2:能力 ID 中的路徑
您不必在 from
中指定字典路徑,而是可以將路徑納入能力 ID (protocol
、directory
等)
匯總
與主要設計相同。
委派
與主要設計相同。
巢狀結構
與主要設計相同。
擷取
如要從字典中擷取能力,請在能力 ID (protocol
、directory
等) 中指定字典中能力的路徑。
offer: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
from: "parent",
to: "#window_manager",
},
],
目標中的名稱預設為最後一個路徑元素,或「dirname」(fuchsia.ui.composition.Flatland
)。您也可以使用 as
重新命名:
offer: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
from: "parent",
to: "#window_manager",
as: "fuchsia.ui.composition.Flatland-windows",
},
],
這也適用於 use
,可讓字典中的能力提供給程式。與其他 use
宣告一樣,預設目標路徑會在 /svc
上重新命名 (最後一個路徑元素):
use: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
path: "/svc/fuchsia.ui.composition.Flatland", // default
},
],
路徑語法也適用於巢狀在其他字典中的字典:
use: [
{ protocol: "session-bundle/gfx/fuchsia.ui.composition.Flatland" },
],
擴充功能
在字典定義中使用 origin: #...
選項,即可繼承其他字典:
capabilities: [
{
dictionary: "session-bundle",
// Source of the dictionary to extend (in this case, the one named
// "session-bundle" from the parent)
origin: "#session",
from: "parent",
},
],
offer: [
{
dictionary: "session-bundle",
from: "self",
to: "#session-manager",
},
{
protocol: "fuchsia.ui.composition.Flatland",
from: "#ui",
into: "session-bundle",
},
],
替代方案 3:能力 ID 變成路徑
名稱 -> 路徑
根據官方說明,cml 中的能力 ID 是名稱,沒有內在的巢狀結構。例如:
offer: [
{
protocol: "fuchsia.fonts.Provider",
from: "#font_provider",
to: "#session-manager",
},
],
不過,能力 ID 會對應至 capabilities
和 use
部分的路徑。對於通訊協定,這通常是隱含的:如果未提供路徑,cmc 會填入 /svc/${capability-name}
的預設路徑。例如:
use: [
{
protocol: "fuchsia.fonts.Provider",
// path in namespace
path: "/svc/fuchsia.fonts.Provider",
},
],
capabilities: [
{
protocol: "fuchsia.fonts.Provider",
// path in outgoing directory
path: "/svc/fuchsia.fonts.Provider",
},
],
這個替代方案會支援能力 ID 中的路徑。更正式的說法如下:
- 能力 ID 是字元集
[A-Za-z0-9_-.]
中一或多個名稱的序列,包含 1 到 100 個字元,並以/
字元分隔。不允許前置/
。- 或是使用規則運算式:
[A-Za-z0-9_-]{1,100}(/[A-Za-z0-9_-]{1,100})*
- 現有的能力 ID 與新語法具有向前相容性。
- 或是使用規則運算式:
我們將在下文中說明這項語法如何自然地為捆綁作業奠定基礎。
匯總
如要將功能匯入字典,請使用相同的路徑前置字元進行路由:
offer: [
{
protocol: "fuchsia.logger.LogSink",
from: "#archivist",
to: "all",
as: "diagnostics/fuchsia.logger.LogSink",
},
{
protocol: "fuchsia.inspect.InspectSink",
from: "#archivist",
to: "all",
as: "diagnostics/fuchsia.inspect.InspectSink",
},
{
protocol: "fuchsia.debugdata.Publisher",
from: "#debugdata",
to: "all",
as: "diagnostics/fuchsia.debugdata.Publisher",
},
],
委派
委派作業就是將字典轉送至原始位置:
offer: [
{
dictionary: "diagnostics",
from: "parent",
to: "#session",
},
],
巢狀結構
您可以將巢狀字典的路徑設為巢狀字典的前置字串,讓字典包含其他字典:
offer: [
{
dictionary: "driver-services-bundle",
from: "parent",
to: "#session-manager",
as: "session/driver-services",
},
],
擷取
只要在要擷取的字典中命名能力即可:
offer: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
from: "parent",
to: "#window_manager",
as: "fuchsia.ui.composition.Flatland",
},
],
這也適用於 use
:
use: [
{
protocol: "session-bundle/fuchsia.ui.composition.Flatland",
path: "/svc/fuchsia.ui.composition.Flatland",
},
],
字典在其他字典中巢狀時,擷取功能也能正常運作 (待辦事項:範例)
擴充功能
重新命名功能,讓路徑前置字元與字典相符:
offer: [
{
protocol: "session-bundle",
from: "parent",
to: "#session-manager",
},
{
protocol: "fuchsia.ui.composition.Flatland",
as: "session-bundle/fuchsia.ui.composition.Flatland",
from: "#ui",
to: "#session-manager",
},
],
為什麼要使用字典,而不是目錄?
我們可以使用 fuchsia.io
目錄做為套件的基礎類型,而非引入字典。從某方面來說,這項功能很實用:目錄已經存在,且提供自己的階層捆綁形式。不過,使用目錄有許多缺點:
- VFS 類型系統會傳遞與 CF 類型系統不同的資訊;舉例來說,服務、目錄和儲存空間都會對應至 VFS 中的子目錄,即使在 CF 中是不同類型也一樣。
- 目錄介面比支援能力包裝所需的介面複雜許多。NODE_REFERENCE、連結、標記、屬性、資料檔案等功能與套件用途無關。
- 對於某些應用程式 (尤其是驅動程式) 而言,VFS 程式庫的大小過大。因此,
//sdk/lib/component/outgoing
程式庫會連結至共用程式庫墊片 (//sdk/lib/svc
) 而非直接連結 VFS 程式庫,但這會犧牲功能和透明度。 - 我們並未實作單一 VFS,而是實作了兩個 C++ 和一個 Rust 實作項目。這些實作方式之間存在細微差異和功能差距。這不是字典的問題,因為元件執行階段中只有單一實作的字典。
- 目錄會使編寫 codegen 繫結的難度提高,因為這類繫結會以個別語言元素代表每項能力,供程式提供或使用。
- 目錄本身不支援「匯總」或「擴充」作業。您必須透過提供新目錄來模擬這些項目,其中部分節點會重新導向至舊節點,這項操作難以實作且容易出錯。
日後的作業
我們將另外提出伴隨設計,以便針對驅動程式庫用途進行設計,因為這類用途無法完全透過本提案中的功能解決。
這項設計為能力轉送功能開啟了更經濟的語法。我們可以將能力名稱和 from
合併為單一路徑,其根目錄在概念上是包含所有根字典的字典。例如:
offer: [
{
protocol: "#ui/fuchsia.ui.composition.Flatland",
to: "#session-manager",
},
],
這個語法具有不錯的屬性:它會自然地泛化,讓使用者能夠轉送整個根字典:
offer: [
{
// Plumb all capabilities from parent to child #session-manager
dictionary: "parent",
to: "#session-manager",
},
],
另外值得一提的是,更通用的版本可進一步統一語法:
route: [
{
// Path to source capability in dictionary
src: "#ui/fuchsia.ui.composition.Flatland",
// Path of target capability in dictionary
dst: "#session-manager/fuchsia.ui.composition.Flatland",
},
],
route: [
{
src: "parent",
dst: "#session-manager/parent",
},
],
既有技術與參考資料
能力套件是舊有的概念。許多內部前身文件都提出類似的想法。