RFC-0131:FIDL 有线格式的设计原则 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 我们将介绍支持 FIDL 有线格式的各种设计原则。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-06-15 |
审核日期(年-月-日) | 2021-09-29 |
总结
我们将介绍当前(截至 2021 年 9 月)的设计原则,这些原则支撑着 FIDL 有线格式。
设计初衷
FIDL 传输格式指定了如何对消息进行编码(和解码),以及传输级元数据(例如事务消息标头)的格式。传输格式规范中的隐式是指该规范的最佳实现方式可能达到的理论边界。就像数据结构隐含了某些操作上的大 O 边界一样,有线格式也是如此。
在 Fuchsia 中,进程间通信(至少是控制平面)普遍通过 FIDL 或计划实现。因此,传输格式对操作系统的整体目标性能有重大影响。同样,在隐私和安全的多层防御中,有线格式发挥着重要作用。
“FIDL 2.0”的设计已于 2017 年 3 月完成。与后续开发相比,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、Yam 等)。
单次传递,无堆分配
必须能够在一次传递中进行编码和解码,而无需在堆栈空间之外进行分配(即没有动态堆分配)。
这一原则在某种程度上遵循了过度专业化处理低级用例,并确保系统上的所有软件都可以完全参与 FIDL 生态系统。
由于 FIDL 提供“解码 + 验证”,因此应将单次通过要求与同时提供反序列化和验证的类似系统相比较,后者通常以两次传递(对解码形式进行验证)完成。
无分配要求的后果是编码和解码是在就地完成的,即通过就地修改。
与手动数据结构一样高效
必须能够编写与手动数据结构一样高效的有线格式的实现。
这是“用多少,付多少”原则的专项领域,即 FIDL 旨在提供的便利和工效学设计不得以牺牲性能为代价。在实践中,许多实现选择效率较低,以提供额外的人体工程学,但有线格式并不决定该选择。
规范表示法
FIDL 值必须有一个明确的表示形式,即 FIDL 值必须有且仅有一个编码表示形式,并且 FIDL 值必须有且只能有一个解码表示形式。
通过强制一种表示形式,传输格式自然会更加严格,这意味着实现可以期望输入的方差较小,并且遵循更直线的路径。这有助于确保正确,因为这样可以减少数据差异导致的意外。规范形式可让您在无需了解架构的情况下检查两个值是否相等,也就是说,对于值类型来说,memcmp
就已足够(对于资源类型而言,这点会比较复杂)。
另请参阅规范表单的缺点。
指定每个字节
编码或解码时,必须能够在单次传递且没有任何堆分配的情况下遍历消息的每一个字节。
为了确保发送者不知道有一个进程的数据泄露到另一个进程,我们必须确保所有字节都能得到高效遍历,并且所有字节都有指定的值(例如填充值必须为 0)。例如,这有助于确保不会无意中跨进程边界共享个人身份信息 (PII),或者帮助避免泄露可能包含指针值的未初始化内存,而这些指针值可用于使地址空间布局随机化 (ASLR) 失效。另一个例子是将“尾随垃圾”视为无效,因为必须考虑所有数据和句柄。
随时随地进行验证
作为深度防御的一部分,我们希望 FIDL 有线格式对所有使用位置的 FIDL 传输格式都强制执行严格的验证(例如,绑定检查、字符串是格式正确的 UTF-8 代码单元序列、句柄的类型和权限均正确无误)。
执行严格验证有助于确保平台的安全性,并帮助 API 作者在 API 架构上声明设计的假设和不变。此外,根据我们的经验,如果缺少在较低层进行验证,应用往往会自行验证不变量,从而导致代码不够清晰,往往效率降低,并且更容易出现 bug。
严格验证可能是高性能成本的根源,并且 FIDL 适合用于低级别层,因此推而广之,这种验证必须高效完成,并且设计为单次通过。
没有开箱即用的反射功能
如果没有显式选择启用,则不得允许对等方对协议执行反射,无论是公开方法还是公开类型。
例如,如果对等方调用了错误的 FIDL 方法,系统会关闭连接,阻止提取有关该对等方的任何信息。构建此类功能看似很方便,但可能会损害隐私权并且难以撤消(用户可能会开始通过此功能构建承载功能)。
同样,缺少自描述形式的结构也符合这一原则,在生态系统中互动的对等方应相互信任时,应避免披露超出必要内容的信息。(避免使用自描述格式也可以显著提升性能,这与低级别优先方法保持一致。)
由于我们更改了 FIDL 传输格式以允许演变(例如表),因此我们不得不小心地在禁止反射和添加足以在没有架构的情况下进行处理之间的平衡。
实现
保持冷静,遵守原则。如 RFC-0017 中所示。
性能
FIDL 有线格式的大多数指导原则都针对性能,专门用于低级别用例。性能是核心关注点。
工效学设计
工效学设计没有变化。
向后兼容性
此处介绍的一些原则与 FIDL 的主要目标相冲突,FIDL 为实现稳定的 ABI 奠定了基础,例如,在缺少反射功能的情况下,实现向后兼容的协议很困难。除此之外,FIDL 有线格式的设计在性能(通常是硬性因素)和改进性问题(通常是灵活性的因素)之间取得了平衡。平衡这些因素才是乐趣所在。
安全注意事项
此 RFC 介绍了 FIDL 在提高 Fuchsia 安全性的多层式方法中的作用。
隐私注意事项
此 RFC 说明了 FIDL 在 Fuchsia 上实现多层隐私保护的方法中的作用。
测试
测试没有变化。
文档
根据需要进行修改:
缺点、替代方案和未知情况
如文字中所述。
规范形式的缺点
要求规范化形式可能会限制寻找良好的数据表示形式的问题,以至于舍弃其他有趣或可行的形式。
在处理稀疏表时,规范化是最需要满足的约束条件之一,直接与对格式高性能的需求相冲突。例如,我们本可以探索按用户提供的顺序编写成员,而无需进行第二次遍历,从而对这些成员进行重新排序以满足规范化要求。
早期技术和参考资料
如文字中所述。
-
作者:Jeff Brown jeffbrown@google.com。↩