RFC-0131:FIDL 有线格式的设计原则

RFC-0131:FIDL 线格式设计原则
状态已接受
区域
  • FIDL
说明

我们将介绍 FIDL 线格式背后的各种设计原则。

Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-06-15
审核日期(年-月-日)2021-09-29

摘要

我们将介绍 FIDL 线格格式的当前(截至 2021 年 9 月)设计原则。

设计初衷

FIDL 线格式指定了消息的编码(和解码)方式,以及传输层元数据(例如事务消息标头)的格式。传输格式规范中隐含了最佳实现可能达到的理论边界。就像数据结构会对操作暗示特定的大 O 上限一样,线格格式也是如此。

在 Fuchsia 中,进程间通信(至少是控制平面)普遍通过 FIDL 进行,或者打算通过 FIDL 进行。因此,线格格式对操作系统的整体目标性能有很大影响。同样,在多层级隐私和安全防护体系中,Wire 格式也发挥着重要作用。

2017 年 3 月,“FIDL 2.0”的设计即将完成。与后续开发的版本相比,FIDL 2.0 是 FIDL 的更静态版本。如需了解更多历史背景信息,请参阅 RFC-0027:用多少、付多少

线格式规范的目标如下1

  • 在进程之间高效传输消息。
  • 通用,适用于设备驱动程序、高级服务和应用。
  • 仅针对 Zircon IPC 进行了优化;可移植性并非目标。(此目标后来已放宽。)2
  • 针对直接内存访问进行了优化;不以机器间传输为目标。
  • 仅针对 64 位进行了优化;不适用于 32 位环境。
  • 使用具有主机字节序的未压缩原生数据类型、元素的首次适合打包,以及正确对齐,以支持对消息内容的原位访问。
  • 与 C 结构内存布局兼容(具有适当的字段排序和打包注解)。
  • 结构是固定大小且内嵌的;可变大小的数据存储在线外。
  • 结构不是自描述的;FIDL 文件会描述其内容。
  • 结构没有版本控制,但可以使用新方法扩展接口以进行协议演变。(此目标后来有所放宽。)[^2]
  • 无需进行偏移量计算,很少可能溢出的算术运算。
  • 支持快速单次编码和验证(作为组合操作)。
  • 支持快速单次解码和验证(作为组合操作)。

虽然线框格式在不断演变,但其遵循了非常具体的设计原则(其中一些已在前文中列出),但这些原则不一定会与理由一起记录下来。本文档旨在明确记录这些设计原则。

设计

我们将介绍 FIDL 线格式背后的各种设计原则。

先显示低级别

在设计时,如果需要做出权衡,以支持低级编程而牺牲高级编程(或相反),我们通常会选择启用低级编程。

FIDL 必须满足 Fuchsia 中低级协议的要求,例如,有时在 malloc 尚不可用时会在启动过程中使用。如果 FIDL 不满足这些要求,则可以手动设计协议。不过,在高级编程中,如果 FIDL 无法满足要求,还可以选择许多其他选项(Protobuf、Cap'n Proto、JSON、Yaml 等)。

单次传递,无堆分配

必须能够在单次传递中进行编码和解码,且不得超出堆栈空间进行分配(即不进行动态堆分配)。

这项原则在某种程度上遵循了过度专注于低级用例的做法,并确保系统上的任何软件都可以完全参与 FIDL 生态系统。

由于 FIDL 提供“解码 + 验证”功能,因此应将单次传递要求与同时提供反序列化和验证的类似系统进行比较,这通常需要两次传递(验证是在解码表单上进行的)。

无分配要求的一个推论是,编码和解码是就地完成的,即通过就地修改完成的。

效率与手动编写的数据结构相当

必须能够编写与手动编写的数据结构一样高效的线格格式实现。

这是“按需付费”原则的专门化,即 FIDL 旨在提供的便利性和人体工学特性不得以牺牲性能为代价。在实践中,许多实现都选择降低效率,以提供额外的工效学优势,但线格格式并不能决定这一点。

规范化表示法

FIDL 值必须有一个明确无误的表示法,即 FIDL 值只有一个编码表示法,并且只有一个解码表示法。

通过强制使用单个表示法,线格格式自然会更严格,这意味着实现必须预期输入的方差较小,并遵循更直线的路径。这有助于减少因数据差异而产生的意外情况,从而确保正确性。借助规范形式,您无需了解架构即可检查两个值是否相等,即对于值类型,只需使用 memcmp 即可(对于资源类型,情况会稍微复杂一些)。

另请参阅规范表单的缺点

指定每个字节

在编码或解码时,必须能够在单次传递且无需任何堆分配的情况下遍历消息的每个字节。

为确保没有数据在发送端不知情的情况下从一个进程泄露到另一个进程,我们确保所有字节都可以高效遍历,并且所有字节都有指定的值(例如,填充必须为 0)。例如,这有助于确保不会无意中跨进程边界共享任何个人身份信息 (PII),或有助于避免泄露可能包含指针值的未初始化内存,这些指针值可能会被用于破解地址空间布局随机化 (ASLR)。另一个示例是,由于必须考虑所有数据和句柄,因此将“尾随垃圾”视为无效。

随时随地进行验证

作为纵深防御的一部分,我们希望在 FIDL 线格格式被使用的任何地方强制执行严格的验证(例如边界检查、字符串是格式正确的 UTF-8 代码单元序列、句柄具有正确的类型和权限)。

严格验证有助于确保平台的安全性,并帮助 API 作者在 API 架构中声明设计的假设和不变项。我们还发现,如果较低层级没有验证,应用往往会自行验证不变量,这会导致代码不够清晰、效率较低,并且更容易出现 bug。

由于严格验证可能会导致高性能开销,并且 FIDL 适合在低级层中使用,因此推论是,此类验证必须高效完成,并且设计为适合在单次传递中完成。

没有开箱即用的反射功能

未经明确选择,对等方不得对协议(无论是公开的方法还是公开的类型)执行反射。

例如,如果对等方调用错误的 FIDL 方法,系统会关闭连接,从而阻止提取有关对等方的任何信息。构建此类功能似乎很方便,但可能会侵犯隐私,并且难以撤消(用户会开始基于此功能构建承载功能)。

同样,缺少自描述格式的结构也符合这一原则,目的是在交互对等应该互不信任的生态系统中避免过度披露信息。(避免使用自描述格式还可以显著提升性能,这与“先低层级”方法相符。)

由于我们更改了 FIDL 线格格式以允许演变(例如),因此我们必须谨慎地在禁止反射与添加足够的功能以允许在不使用架构的情况下进行处理之间取得平衡。

实现

保持冷静,遵循相关原则。如 RFC-0017 所示。

性能

FIDL 线格式中的大多数指导原则都侧重于性能,并且过于专门针对低级别用例。性能是核心关注点。

工效学设计

人体工学没有任何变化。

向后兼容性

此处所述的一些原则与 FIDL 的主要目标(为稳定的 ABI 提供基础)相冲突,例如,在没有反射功能的情况下,实现向后兼容的协议会很有挑战。除此之外,FIDL 线格式在性能(通常是刚性所致)和可扩展性问题(通常是灵活性所致)之间取得了平衡。平衡这些因素正是乐趣所在。

安全注意事项

本文档介绍了 FIDL 在 Fuchsia 上多层安全机制中的角色。

隐私注意事项

此 RFC 介绍了 FIDL 在 Fuchsia 上多层级隐私保护方法中的作用。

测试

测试没有任何变化。

文档

根据需要进行修改:

缺点、替代方案和未知情况

如文中所述。

规范形式的缺点

要求使用规范化形式可能会限制为数据找到良好表示形式的问题,甚至会导致舍弃原本有趣或可行的方法。

在处理稀疏表时,规范化是最难满足的约束条件之一,与格式高性能的要求直接冲突。例如,我们可以考虑按用户提供的顺序写入成员,而无需进行第二次传递来重新排列这些成员以满足规范化要求。

在先技术和参考文档

如文中所述。


  1. 作者:Jeff Brown jeffbrown@google.com

  2. 从那时起,有些目标已放宽(可移植性、结构无版本控制),有些目标则已收紧(字节序)。