RFC-0173:Component Framework API 中的结构化配置

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 作者)

已咨询:hjfreyer@google.com、xbhatnag@google.com、aaronwood@google.com、 mcgrathr@google.com、shayba@google.com

社交化:此 RFC 是包含组件的设计审核的产物 框架团队,在原型设计过程中进行后续代码审核,以及 了解与原型设计尝鲜者的反馈有关

范围

此 RFC 介绍了为实现“第 1 阶段”而对组件框架进行的更改和 “第 2 阶段”的来自 RFC-0127 的实施计划,内容涵盖 “静态价值”功能以及 家长”在测试环境中使用 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

由于配置值不会存储在组件清单中 解析时,组件框架需要知道在哪里可以找到这些值 组件。向配置的编译表示法中添加了一个新字段 架构:

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 build 通过要求配置值来实现此目的 target 引用组件目标,以便双方就打包好的 地点:

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;
};

ValuesDatafuchsia.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 运行程序提供配置 VMO 作为启动句柄:

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

赛车手跑酷游戏

驱动程序运行程序将配置 VMO 作为 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 还将允许测试作者加载 部分,对其进行批发操作或替换单个字段。这个 例如,可让测试实现特定测试功能 在部分测试之外没有用,同时仍可使用 “production”所有配置

这些方法将添加到 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 打造。我们一直都在对开始时间进行基准化分析 我们将继续监控这些组件, 但在原型设计过程中尚未发现任何回归问题, 功能。

结构化配置可能会增加系统内存用量, 保存在内存中,此设计通过将配置存储在 在将内容解析为 每个组件的实现所使用的域特定类型。

向后兼容性

此 RFC 假定平台版本控制将有足够的资源 运行时支持,然后才能发现任何需要 改进将配置编码为传递给组件的 VMO 的方式。

RealmBuilder 的替换实现允许与 已启动的组件获取组件配置的运行时依赖项 以及他人的组件中使用的组件的作者都需要通过 更改配置字段的类型或移除 。

安全注意事项

结构化配置可用于控制 因此实现提供正确的价值至关重要。

组件的配置与其清单的来源相同,但是 未来我们可能会扩展现有的组件解析器或构建新的解析器 从而使两者之间的关系更加松散我们需要进行 应注意确保组件的配置始终明确且 可审核。

隐私注意事项

根据 RFC-0127,结构化配置并非旨在存储用户 生成数据。

测试

在这种设计中,最敏感的部分是 组件管理器。其原型已集成为“语言后端”在 FIDL 的 GIDL 一致性套件,确保其支持的类型子集 它会生成布局方式与静态类型的 FIDL 绑定相同的消息。

文档

原型的文档目前适用于树内 开发者。

缺点、替代方案和未知问题

替代方案:以类型化格式存储值

我们可以存储不是非类型化的 fuchsia.component.config.ValuesData, 以及在组件的各部分之间 使用 FIDL 编码的载荷且类型与 组件架构,类似于直接传递给 组件。

这可能会使磁盘上的表示形式稍微紧凑一些,因为 而且要符合美学风格,并且与最终 传递给组件

此 RFC 中提出的设计允许各种工具理解配置 而无需重新实现动态类型的 FIDL 解析器。未来,我们会 如果 FIDL 工具链提供更通用的工具,则可能会重新考虑此决定 关于“反射”。

替代方案:在清单中存储值

以内嵌方式在清单中存储值会简化 实现(消除组件解析器的责任),但代价是 不同产品组件清单的 blob 哈希已更改。

替代方案:按文件扩展名查找值文件

无需向组件清单添加值来源,我们可以通过 打包约定,找到值文件的位置。这会创建一个 不必要的运行时依赖项,目前只遵循惯例。

替代方案:按软件包组件索引查找值文件

将第三个 blob 添加到软件包中后,我们可以通过流水线来读取 清单和值文件,这将生成更清晰的构建图, 简化清单编译过程的某些元素,并允许组件 使其能够理解一种更简单的文件格式,而不必解析整个 组件清单。

不过,这会增加系统映像的大小 对组件解析方式所做的更改。

替代方案:将配置编码为 FIDL 表

配置字段可以编码为表,而不是 FIDL 结构体,如下所示: 在 RFC-0127 中建议作为一个选项。这个表有很多实用的 但它们也需要解析器假设 这些字段可以省略。通过结构化配置,组件管理器 始终能够提供所有字段,并且无需调用方处理 未提供值的情况。

FIDL 结构体在传输相同数据时占用的字节数较少,并且速度稍快 进行解析。

替代方案:对配置值使用非 FIDL 编码

可以使用 FIDL 以外的编码,但可以使用 FIDL 有线格式 对于这种用例,选择其他编码将是净增长 理解 Fuchsia 所需的概念数量。使用 FIDL 编码 符合组件框架与 FIDL 更紧密集成的愿望 并且支持使用 FIDL 生成的绑定作为实现方式 访问器,这使得结构化配置能够充分利用 性能和二进制文件大小方面的优化。

替代方案:运行程序对配置进行编码

我们无需在组件管理器中对配置 VMO 进行编码,而是可以对 。这将允许在不同等级的跑步者进行锻炼 抽象化来以不同格式提供配置。例如,一名跑步者 可以将配置作为该语言的对象来提供 而不是要求每个 JavaScript 组件都解析 VMO。

不过,正如测试中所述,FIDL 编码是最敏感的 正确性。今天有多个跑步者 它会对配置使用基于 FIDL 的编码,这意味着 是多个二进制文件(并实现编程语言), 执行此敏感任务。将配置编码的责任集中到 组件管理器可让我们限制实现,并实现更高的 通过广泛的测试对整体功能充满信心。

建议的设计会使运行程序难以配置其 基于组件的结构化配置值的行为, 将需要根据组件声明的架构解析已编码的 FIDL。 这与让每个跑步者负责 这种编码方式也是不可取的。在需要配置运行程序时 我们将设计一个单独的接口,用于传递 从组件配置传递到运行程序。定义显式 运行程序配置接口的优点是可以将 组件的配置命名空间。

替代方案:使用 FIDL 描述 VMO/校验和布局

我们无需将配置校验和编码为 VMO 中的标头字节, 从 FIDL 的角度描述 VMO 的 ABI:

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

这会在每个配置 VMO 中占用略多的空间,并且还需要 对 contents 的字节遍历两次。