| RFC-0131:FIDL 线格式的设计原则 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 我们介绍了 FIDL 线格式的各种设计原则。 |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2021-06-15 |
| 审核日期(年-月-日) | 2021-09-29 |
摘要
本文介绍了 FIDL 有线格式当前(截至 2021 年 9 月)的设计原则。
设计初衷
FIDL 线格式指定了如何对消息进行编码(和解码),以及传输级元数据(例如事务性消息标头)的格式。传输格式规范中隐含了最佳实现可能达到的理论界限。正如数据结构对操作具有特定的大 O 界限一样,线格式也是如此。
在 Fuchsia 中,进程间通信(至少是控制平面)普遍通过 FIDL 进行,或者打算通过 FIDL 进行。因此,有线格式对操作系统的整体目标性能有显著影响。同样,作为多层隐私和安全防御的一部分,有线格式也发挥着重要作用。
2017 年 3 月,“FIDL 2.0”的设计已完成。与后来的发展相比,FIDL 2.0 是一个更静态的 FIDL 版本。如需了解更多历史背景信息,另请参阅 RFC-0027:您只需要按照实际用量付费。
线格式规范的目标如下1:
- 在进程之间高效传输消息。
- 通用,可与设备驱动程序、高级服务和应用搭配使用。
- 仅针对 Zircon IPC 进行了优化;可移植性不是目标。(此目标后来有所放宽。)2
- 针对直接内存访问进行了优化;机器间传输不是目标。
- 仅针对 64 位环境进行了优化,不适用于 32 位环境。
- 使用具有主机字节序的未压缩原生数据类型、元素的首次适应打包和正确的对齐方式,以支持对消息内容的就地访问。
- 与 C 结构内存布局兼容(具有合适的字段排序和打包注释)。
- 结构是固定大小且内嵌的;可变大小的数据存储在行外。
- 结构不是自描述的;FIDL 文件描述了它们的内容。
- 结构没有版本控制,但接口可以通过新方法进行扩展,以实现协议演变。(此目标后来有所放宽。)[^2]
- 无需进行偏移计算,几乎没有可能溢出的算术运算。
- 支持快速单次编码和验证(作为组合操作)。
- 支持快速单次解码和验证(作为一项组合操作)。
虽然线格式的持续演变遵循了非常具体的设计原则(如上文所述),但这些原则并不一定有书面记录,也没有附带理由。此 RFC 旨在清晰地记录这些设计原则。
设计
我们介绍了 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 有线格式的设计在性能(通常是刚性的结果)和可演变性问题(通常是灵活性的结果)之间取得了平衡。平衡这些因素才是乐趣所在。
安全注意事项
此 RFC 介绍了 FIDL 在 Fuchsia 多层安全方法中的作用。
隐私注意事项
此 RFC 中介绍了 FIDL 在 Fuchsia 多层隐私保护方法中的作用。
测试
测试方面没有变化。
文档
根据需要进行修改:
缺点、替代方案和未知因素
如文本中所述。
规范形式的缺点
要求采用规范化形式可能会限制为数据找到良好表示形式的问题,甚至会舍弃其他有趣或可行的形式。
在处理稀疏表时,规范化是最难满足的限制条件之一,并且直接与格式需要具备高性能的要求相冲突。例如,我们可以探索按用户提供的顺序写入成员,而无需进行第二次传递来重新排序这些成员以满足规范化要求。
在先技术和参考资料
如文本中所述。
-
作者:Jeff Brown jeffbrown@google.com。↩