RFC-0196:FIDL 大型邮件 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 支持通过 FIDL 协议发送大型消息。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2022-06-28 |
审核日期(年-月-日) | 2022-10-27 |
摘要
目前,FIDL 语言会限制基于 Zircon 的消息的大小
将通道和套接字等传输到 64KiB。本文档提出了
用于处理过大的消息,即使消息会溢出,
最大消息字节限制其底层传输。这是通过
提升与成熟现有模式类似的解决方案,
使用 fuchsia.mem.Data
,实现一流、自动推导的 FIDL
语言支持。
设计初衷
目前,通过网络发送的所有 FIDL 消息的大小上限为
ZX_CHANNEL_MAX_MSG_BYTES
,目前相当于 64KiB,派生自
可通过 Zircon 通道发送的消息的最大大小。符合以下条件的邮件:
超过此上限就无法编码。
目前,此问题的常见解决方法是将任意大的数据以
blob 或 fuchsia.mem.Data
,替换为zx.handle:VMO
包含正在发送的数据 blob 的底层 VMO 本身。通常,这些 blob
包含最终用户想要在其中表示的结构化数据
编码/解码为 FIDL,但不能且被迫自行进行类型转换。
如今,fuchsia.git
中大量使用这些封装容器类型。
缺乏大型消息支持导致了一些问题。最重要的
其中就有大量错误是由很少需要
发送大型消息,但从技术层面来讲是可以的。例如:
超大网址或在 WiFi 扫描期间生成的超大网络列表。
每个需要接受 :MAX
大小的 vector
或 string
的 API 都存在漏洞
以及其他极端情况,例如 table
布局
其所有字段很少会填充数据。一般情况下
接受无法证明低于 64KiB 的消息形式的用户数据
可能会受到此故障模式的影响
通过 VMO 发送非类型化数据 blob 不符合人体工程学,因为它会丢失所有
类型信息,必须在接收端手动重建。
不利用 FIDL 来描述数据的形状并抽象化处理
“编码->发送->解码->调度”流水线,用户必须对
将其封装为另一个 FIDL 消息,然后重复上述操作
反过来操作。例如,ProviderInfo
API 具有子类型 InspectConfig
和 InspectSource
它们现在以 fuchsia.mem.Buffer
和 zx.handle:VMO
表示,
而是表示可以被描述和处理的结构化数据
。
使用 zx.handle:VMO
或 fuchsia.mem.Data
会造成
仅限数据的 FIDL 类型会强制带有 resource
修饰符。这包含
对绑定 API 的下游影响,导致在如下语言中生成类型:
Rust 无法派生 Clone
trait,即使它们在合理情况下也是如此。
因大型消息支持不足而导致的错误和人体工程学问题包括 无处不在。在起草这份 RFC 期间进行的调查显示,至少 过去和现在的 30 个案例,更强大的大型消息支持 帮助了 FIDL 用户。
利益相关方
教员:hjfreyer@google.com
审核者:abarth@google.com、chcl@google.com、cpu@google.com、 mseaborn@google.com、nickcano@google.com、surajmalhotra@google.com
已咨询:bprosnitz@google.com、geb@google.com、hjfreyer@google.com、 jmatt@google.com、tombergan@google.com、yifeit@google.com
社交化:五个团队(组件解析器、DNS 解析器、驱动程序) WLAN SME 和 WLAN 政策)审核了使用此 设计:
- 组件解析器:geb@google.com
- DNS 解析器:dhobsd@google.com
- 驱动程序开发:dgilhooley@google.com
- WLAN 政策:nmccracken@google.com
- WLAN SME:chcl@google.com
此外,现有fuchsia.mem.Data
和
fuchsia.mem.Buffer
个类型已就设计反馈和使用接受采访
大小写适用性。
设计
关键字“必须”“不得”“必需”“会”“不会”“应” “不应该”“建议”“可以”和“可选”文档内容 如 IETF RFC 2119 中所述。
消息溢出属于传输级别的问题。怎样才算是大型消息? 以及处理符合该定义的邮件的最佳方式,差异很大 zircon 信道之间、驱动程序框架、overnet 等。这意味着 调用特定方法请求或响应“large”它不是抽象的 语句:必须始终为“large”提供明确定义的定义用于 该方法的拥有协议。
以下代码指定了消息如何声明“我相对于
期望的运输方式,因此需要特殊的
处理”。此声明必须在接口定义中清晰易读
*.fidl
文件中指定协议的时间,
发送响应
具体而言:在这种设计之前,发件人可以发送其他完全有效的
超过底层传输的最大消息字节限制的消息,
从而导致 PEER_CLOSED
运行时出人意料且难以调试
失败。完成这些更改后,fidlc
编译器将进行静态检查,以查看
负载类型可能大于传输的最大
消息字节数限制,如果是,则会生成特殊的“溢出”处理代码
来解释这一点此模式支持辅助运行时消息传送
机制,即没有边界的边信道(如果
zircon 通道(即 VMO)用于存储消息的内容。这种新的
消息传送路径完全增加了所生成的绑定的
代码,从而保持 API 和 ABI 的兼容性。FIDL 方法
实现人员现在可以确信没有可分配的消息
PEER_CLOSED
,原因是遇到任意字节大小限制。
电汇格式变更
我们在动态 API 中向动态byte_overflow
FIDL 事务消息的 flag 部分
标题。该标记在翻转时表示
保留的消息仅包含该消息的控制平面,并且
消息的其余部分存储在单独的可能不连续的缓冲区中。
这个单独的缓冲区的位置以及可以如何访问该缓冲区,
取决于传输如果 byte_overflow
标志处于有效状态,则传输中的
控制平面消息必须包含 16 字节事务性消息标头
然后是一条额外的 16 字节的补充消息,用于说明
大型消息。也就是说,此消息必须正好是 32 个字节:
默认 FIDL 消息标头,后跟所谓的 message_info
结构体
包含三段数据:uint32
用于标记,uint32
用于标记
可能会指定
以及一个 uint64
表示
VMO 本身:
type MessageInfo = struct {
// Flags pertaining to large message transport and decoding, to be used for
// the future evolution and migration of this feature.
// As of this RFC, this field must be zeroed out.
flags uint32;
// A reserved field, to be potentially used for storing the handle count in
// the future.
// As of this RFC, this field must be zeroed out.
reserved uint32;
// The size of the encoded FIDL message in the VMO.
// Must be a multiple of FIDL alignment.
msg_byte_count uint64;
};
由于需要生成一个额外的句柄来指向溢出缓冲区, 大型 FIDL 消息可能只附加 63 个句柄,而不是通常的 64 个。 此行为不恰当且会让用户感到惊讶,仅报告 导致应用崩溃这种不幸的极端情况被承诺 开发内核改进来修复 。
byte_overflow
标志必须占用 6 位(即倒数第二个位)
有效位)。位 5 保留
未来的 handle_overflow
位,尽管此位目前未使用。
该位不得用于其他目的。
运行时要求
在解码期间违反多种条件时,必须
将导致 FIDL 传输错误,并立即关闭通信
。如果设置了 byte_overflow
标志,则控制层面的大小
消息必须正好为 32 个字节(如上所述),即
消息必须通过其他媒介传输。
对于 zircon 通道传输,字节溢出的媒介
缓冲区必须为 VMO。也就是说,
控制平面消息必须至少为 1。指向的内核对象
最后一个句柄必须是 VMO,以及从
接收器的 VMO 必须等于 msg_byte_count
的值
message_info
结构体的相应字段。如果已知该消息是有界限的,
此值必须小于或等于静态推断出的大小上限
相关载荷。
消息发送者必须通过 zx_vmo_create
系统调用创建新的 VMO,
之后紧接着是 zx_vmo_write
,以便使用
消息。它们必须确保表示溢出 VMO 的句柄
没有适当的 ZX_RIGHT_WRITE
。
在接收端,消息的接收者必须读出
使用 zx_vmo_read
的保全。因此,通过 Zircon 发送常规 FIDL 消息
频道只需要两个系统调用(zx_channel_write_etc
对发送者和
zx_channel_read_etc
),而字节溢出消息需要
更多(zx_channel_write_etc
、zx_vmo_create
和 zx_vmo_write
,
以及针对发件人的 zx_channel_read_etc
、zx_vmo_read
和 zx_handle_close
接收方)。这是一个非常严重的惩罚;虽然将来会进行优化,例如
对 zx_channel_write_etc
API 的改进,可能会收回一些
这些费用。消息接收器不得尝试对收到的消息执行写入操作
溢出 VMO。
代码生成方面的变更
FIDL 绑定实现必须为任何载荷生成溢出处理程序 消息的最大字节数可能大于其协议 传输的限制。为此,FIDL 消息可能会大致拆分为 三个类别:
- 有边界:累计字节数上限始终已知的消息。
此类别包含大多数 FIDL 消息。对于此类消息,绑定
生成器必须使用计算出的消息的最大字节数
确定是否包含设置
byte_overflow
标志的功能 编码时的大小,以及是否在解码时对其进行检查。具体而言, 如果最大累积字节数大于 协议传输的限制(Zircon 信道为 64KiB)、 在编码时设置byte_overflow
标志,在强制解码时设置标志 检查必须包含在生成的代码中;否则,它们必须 不会。 - 半限定: 最大累积字节数仅为
是已知状态。此类别包括任何
是有界限的,但以传递方式包含
flexible union
或table
定义。对于此类消息,绑定生成器必须使用 消息的最大字节数,以确定是否将 能够在编码时设置byte_overflow
标志,但此标志必须 必须始终在解码时进行检查。 - 无限制: 累计字节数上限
定义总是不可知的。此类别包括
以传递方式包含递归定义或无界限
vector
。对于此类 消息,生成的绑定代码必须始终包含设置 编码中的byte_overflow
标志,并且必须始终在 解码。
@transport("Channel")
protocol Foo {
// This request has a well-known maximum size at both encode and decode time
// that is not larger than 64KiB limit for its containing transport. The
// generated code MUST NOT have the ability to set the `byte_overflow` on
// encode, and MUST NOT check it on decode.
BoundedStandard() -> (struct {
v vector<string:256>:16; // Max msg size = 16+(256*16) = 4112 bytes
});
BoundedStandardWithError() -> (struct {
v vector<string:256>:16; // Max msg size = 16+16+(256*16) = 4128 bytes
}) error uint32;
// This request has a well-known maximum size at both encode and decode time
// that is greater than the 64KiB limit for its containing transport. The
// generated code MUST have the ability to set the `byte_overflow` on encode,
// and MUST check it on decode.
BoundedLarge() -> (struct {
v vector<string:256>:256; // Max msg size = 16+(256*256) = 65552 bytes
});
BoundedLargeWithError() -> (struct {
v vector<string:256>:256; // Max msg size = 16+16+(256*256) = 65568 bytes
}) error uint32;
// This response's maximum size is only statically knowable at encode time -
// during decode, it may contain arbitrarily large unknown data. Because it
// is not larger than 64KiB at encode time, the generated code MUST NOT have
// the ability to set the `byte_overflow` on encode, but MUST check for it on
// decode.
SemiBoundedStandard(struct {}) -> (table {
v vector<string:256>:16; // Max encode size = 32+(256*16) = 4128 bytes
});
SemiBoundedStandardWithError() -> (table {
v vector<string:256>:16; // Max encode size = 16+32+(256*16) = 4144 bytes
}) error uint32;
// This response's maximum size is only statically knowable at encode time -
// during decode, it may contain arbitrarily large unknown data. Because it
// is larger than 64KiB at encode time, the generated code MUST have the
// ability to set the `byte_overflow` on encode, and MUST check for it on
// decode.
SemiBoundedLarge(struct {}) -> (table {
v vector<string:256>:256; // Max encode size = 32+(256*256) = 65568 bytes
});
SemiBoundedLargeWithError(struct {}) -> (table {
v vector<string:256>:256; // Max encode size = 16+32+(256*256) = 65584 bytes
}) error uint32;
// This event's maximum size is unbounded. Therefore, the generated code MUST
// have the ability to set the `byte_overflow` on encode, and MUST check for
// it on decode.
-> Unbounded (struct {
v vector<string:256>;
});
};
ABI 和 API 兼容性
此设计在全面推出后,完全兼容 ABI 和 API。时间是
始终具有 ABI 安全性,因为任何将之前有界限载荷转换为
无界限或半界限属性,例如将 struct
更改为 table
,或者
更改 vector
大小边界已经是一项破坏 ABI 合规性的更改。
对于无界限或半有界限载荷,byte_overflow
标志始终为
(无论邮件大小如何)。也就是说,任何消息
即使
如果进化添加了未知数据导致了该消息,
解码器的载荷类型视图,异常大。
在部署的中间阶段,一方很有可能 具有 FIDL 绑定,该绑定可在 则大型邮件将无法解码。这类似于 这类消息在编码过程中会失败,尽管 但现在距离来源的距离会更远一些
我们认为在中间发布期内出现解码失败的风险 因为大多数会发送大型消息的 API 都已经有协议 或分块等缓解措施主要风险途径是 协议开始通过现有方法发送现在允许的大型消息。此类 协议应倾向于引入能够处理大消息的新 方法。
设计原则
这种设计遵循了几个关键原则。
用多少,付多少
FIDL 语言的一个主要设计原则是,您只需为实际使用的资源付费 使用。本文档中介绍的大型邮件功能旨在 坚守这一理想
对于此 RFC,使用有界限载荷的方法不会降低性能 存在的。使用半有界限或无界限有效负载的方法, 发送超出协议传输的再发送次数限制的消息,则只需支付 接收端单位标记检查的开销。仅限 实际使用较大的消息溢出缓冲区时 。
不需要大型消息支持(即大多数方法/协议)的用户 可能以 FIDL 的形式表示的)无需支付任何费用, 以及写入过程中的运行时性能成本和心理开销 FIDL API。
不迁移
现在,对于任何可能存在
直接使用它们,而无需迁移现有 FIDL API 或其客户端/服务器
实现。以前会导致 PEER_CLOSED
运行时的情况
错误现在是“只管工作”。
量身定制的交通
这种设计可以灵活满足不同传输方式的需求,
推测的。例如,基于单个 IP 地址的
网络共享是可行的,只要 byte_overflow
位被翻转并且
transport 知道如何对包含数据包的溢出进行排序。
实现
此功能将在实验性 fidlc
标记后方推出。每个
然后,绑定后端将根据
此 RFC,以明确指定实验性标志的输入。部署
功能被视为稳定版,则该标记将被移除,
。
此属性应该不需要额外的 fidlc
支持,因为它只是
将执行溢出检查所需的信息传递给
选择用来教会如何支持大型消息的后端。
在 RFC 之前,绑定一般将编码/解码缓冲区放置到
堆栈。今后,我们的建议是绑定应继续
这一行为。byte_overflow
对于具有此功能的消息,绑定应改为在堆上分配。
性能
所提议的交付方式的性能影响可使用 内核 Microbenchmark 对两种情况进行汇总和比较:发送 大小 B 与发送 16 字节通道消息和发送 VMO 总和 大小 B - 16,对于以下 B 值:16KiB、32KiB、64KiB、128KiB、256KiB 512KiB、1024KiB、2048KiB 和 4096KiB。
列表 1:显示1预计送货时间表现的表格 “税费”当以 16 字节通道消息和 VMO 发送 B 字节的数据时付费 大小为 B 到 16 的通道消息,而不是大小为 B 的通道消息。
邮件大小 / 策略 | 仅限频道 | 频道 + VMO | VMO 使用税 |
---|---|---|---|
16KiB | 2.5 微秒 | 5.9 微秒 | 136% |
32KiB | 4.5 微秒 | 7.7 微秒 | 71% |
64KiB | 7.9 微秒 | 13 微秒 | 65% |
128KiB | 16.5 微秒 | 23.3 微秒 | 41% |
256KiB | 35.8 微秒 | 54.4 微秒 | 52% |
512KiB | 71.3 微秒 | 107.4 微秒 | 51% |
1024KiB | 157.0 微秒 | 223.4 微秒 | 42% |
2048KiB | 536.2 微秒 | 631.8 微秒 | 18% |
4096KiB | 1328.2 微秒 | 1461.8 微秒 | 10% |
清单 2:显示预计送货时间表现“tax”的图表已支付 以 16 字节通道消息和大小 B 的 VMO 传送 B 字节的数据时 - 16,而不是大小为 B 的通道消息。
清单 3:不同国家/地区之间送货时间表现的线性比例比较 以 16 字节通道消息和大小 B - 16 的 VMO 传送 B 字节的数据 而不是大小为 B 的通道消息。
从这些数据中可以得出一些有趣的观察结果。我们可以看到 数据大小和交付时间之间的关系大致是线性的。还有 很有意思的是,这两种方法之间 随着邮件大小的增加,这一差距也越来越小。
结合这些结果,我们可以模拟发送 FIDL 的预期性能 处理大型消息。我们可以预期, 即所谓的“VMO 税”以给定大小使用普通的旧频道消息 可以实现大约 20-60% 的 送货时间。有趣的是,随着百分比数值的增加, 所发送的邮件的大小增加,这表明 VMO 税费增加了 与载荷大小有关。
清单 4:显示设计中根据模型估算的送货时间表现的表 如本文档中所述。
邮件大小 / 策略 | 仅限频道 | 消息 + VMO |
---|---|---|
16KiB | 2.5 微秒 | -- |
32KiB | 4.5 微秒 | -- |
64KiB | 7.9 微秒 | 13 微秒 |
128KiB | -- | 23.3 微秒 |
256KiB | -- | 54.4 微秒 |
512KiB | -- | 107.4 微秒 |
11024KiB | -- | 223.4 微秒 |
2048KiB | -- | 631.8 微秒 |
4096KiB | -- | 1461.8 微秒 |
清单 5:线性比例图,显示根据模型估算的送货时间表现 本文档中介绍的设计。请注意 64KiB 处的不连续性 从常规邮件切换到大型邮件。
工效学设计
这一变化大大提升了人体工程学,因为基本上
zx.handle:VMO
、fuchsia.mem.Buffer
和
现在可以改用一级 FIDL 概念来描述 fuchsia.mem.Data
。
下游绑定代码也有诸多优势,
通过电线发送时,可以使用常规 FIDL 路径来处理非类型化处理。在
从本质上讲,为大型消息生成的 FIDL API 现在与其
非大型消息副本。
向后兼容性
这些更改将完全向后兼容。现有 API 的 语义差异略有不同(从每条消息 64KiB 的上限到 是无限制的),但由于对前面的限制放宽了,因此 现有 API 都会受到影响
安全注意事项
这些更改对安全性的影响微乎其微。所提升的模式
更改为“头等舱”状态已可使用 fuchsia.mem.Data
结构,并且未观察到对安全性造成负面影响。这一点仍然很重要
不过,为了确保其实现在所有情况下都是安全的。
这种设计也增加了拒绝服务攻击的风险
与 FIDL 协议相关联。之前,发送 VMO 的
分配的内存太多,导致接收器崩溃,但只有协议可以使用
明确发送了 fuchsia.mem.Data
/fuchsia.mem.Buffer
/zx.handle:VMO
包含各种类型。现在,所有至少包含一个具有
无界限或半界限的载荷这被视为
可以接受的仅仅因为目前存在许多拒绝服务攻击途径
锆石中除此以外,我们还将寻求更全面的解决方案来解决此问题
限制。
此设计引入了一种额外的拒绝服务攻击途径,它不强制要求
确保在接收端检查 ZX_INFO_VMO
。这样可让分页器支持的
VMO 通过从未提供其承诺的网页导致服务器挂起
提供。在实际操作中,意外发生这种情况的风险被认定为较低
因为只有相对少数的程序会使用由分页器支持的 VMO
机制。与上述推理类似,这种拒绝服务攻击途径
直到可以在未来的设计中实施更全面的解决方案为止。
隐私注意事项
一个重要的隐私权注意事项是,邮件发件人必须确保 它们会为每个基于 VMO 的消息使用新创建的 VMO。且不得 在消息之间重复使用 VMO,否则存在数据泄露的风险。绑定是 来实施这些限制条件
测试
针对 fidlc
进行单元测试的标准 FIDL 测试策略,以及针对
将扩展下游和绑定输出,以适应大型消息的使用
案例
文档
FIDL 传输格式规范必须为 进行了更新,介绍了本文档中引入的传输格式变化。
缺点、替代方案和未知问题
缺点
这种设计有很多缺点。虽然这些被评判为 尤其是相对于什么都不做或实施 被视为替代方案,仍值得指出。
性能悬崖
正如效果探索中所详述,策略
会导致性能“悬崖”在
用户开始缴纳“税费”的ZX_CHANNEL_MAX_MSG_BYTES
截止点更改为
发送更大尺寸的邮件。具体而言,大于 1 个字节的消息
与 64KiB 相比,接收 64KiB 需要大约 60%
正好为 64KiB 的消息。虽然这样的悬崖并不理想,但相对来说
并且可能通过将来的内核更改加以改善。
拒绝服务攻击
至少具有一种采用无界限或
就网络性能而言,
恶意攻击者可能会发送
message_info
结构体的 msg_byte_count
字段中的值,替换为
大型虚拟机连接 (VMO)然后,接收方将不得不为
处理此有效负载所需的足够内存量;如果
恶意载荷量足够大。
如安全注意事项中所述 这确实是一种非常实际的风险,这种设计也意味着 解决问题,直到在未来某个日期找到更全面的解决方案。
处理溢出边缘情况
这种设计并不能完全消除
由于消息过长,导致 PEER_CLOSED
在运行时出现意外:
包含超过 64 个标识名的常规邮件,或包含超过 63 个标识名的大型消息。
仍会触发错误状态。目前可接受
作者知道实际中没有使用此类有效负载。这种极端情况
可以根据需要加以处理。添加了 reserved
字段,
message_info
结构体可确保未来具有灵活性
句柄溢出支持的设计。
依赖于上下文的消息属性
byte_overflow
和 标志将是第一个表示不同含义的标头标志,
(可以说,静态存储
标志,具体取决于我们是否将静态 FIDL 视为“传输”
)。这会带来一些歧义:只看一个有线编码的 FIDL
事务性消息,而不知道它所发送的是哪个
足以处理消息现在有一个“预处理”步
这取决于标头标记和邮件传输
邮件发出之后,我们会执行特殊程序来组合邮件的全部内容。
例如,非句柄携带消息溢出到 Zircon 通道上。
transport 现在会在其句柄数组中获取句柄,而 fdf
消息
溢出情况。
被拒备选方案
在设计 Responsible AI 的过程中考虑了许多替代解决方案, 此 RFC。下面列出了一些最值得关注的提案。
提高 Zircon 消息大小限制
大型邮件最迫切的需求是通过 zx.channel
传输,
目前的消息大小限制为 64KiB。一个显而易见的解决方案是
只需提高此限制即可
由于一些原因,这种做法不可取。第一个是 解决遇到的棘手问题。原因在于会破坏 ABI 的内核 限制迁移(例如,这并非易事),因为需要谨慎管理 确保在上限提高后编译的二进制文件不会意外 所发送的数据要比编译的二进制文件多。
许多 FIDL 实现还围绕该限制做出了有用的假设。
某些绑定(如适用于 Rust 的绑定)会采用“猜测和检查”方法,分配
针对已接收消息的策略它们会分配一个小缓冲区,然后尝试
zx_channel_read_etc
。如果该系统调用失败并显示 ZX_ERR_BUFFER_TOO_SMALL
,则会
返回实际的邮件大小。这样一来,绑定就可以分配
适当大小的缓冲区并重试。
其他绑定(例如用于 C++ 的绑定)会谨慎对待,并始终 为传入消息分配 64KiB,从而避免 系统调用的代价是较大的分配。后一种策略无法扩大规模 任意大的邮件。
最后,使用 VMO 是一种经过充分测试的解决方案:
经由 fuchsia.mem.Data
和
fuchsia.mem.Buffer
类型。提高内核限制并不是经过实践检验的解决方案
以及更多潜在的未知因素。
使用 zx_vmar_map
而非 zx_vmo_read
当前设计的优化会使 FIDL 编码器直接读取
使用 zx_vmar_map
从 VMO 缓冲区提取数据。这种方法存在两个问题
方法。
主要的问题在于这种方法不安全
需要修改内核基元以进行补救。这里的问题是
映射记忆导致了邮件发件人可能会修改邮件的情况
在接收方读取内容时,会创建 TOCTTOU
风险。读取器可以尝试直接从映射中读取数据,
在不事先复制的情况下可能可变的 VMO,即使防御性复制
因此很难安全地执行此操作。这些安全措施
通过强制实施 VMO 的不变性来降低风险,
zx_vmo_create_child
调用,代价是额外的系统调用和最坏的情况
复制开销
关于内存映射性能的其他问题,以及 线缆 C++ 绑定的复杂性,例如决定何时可以 因此不适合使用此选项。
分包
这里的想法是获取非常大的邮件,然后将其拆分为多个邮件, 每个分块不大于 64KiB,然后在另一端组装起来。 事务性消息标头会有某种接续标志 指示它是否需要“更多数据”它包含 内置流控制的优势, 从标准库中具有流式基元的语言的 API 中。
这种方法的缺点在于,如果 不容易分块也要复杂得多: 令人困惑的部分消息块将堵塞 在另一端重新组装运输系统
最令人担忧的是,此策略具有拒绝服务风险, 以后不能仅通过新的内核基元进行修复, 有界限协议:恶意或有漏洞的客户端可能会 发送很长的消息数据包流,但未能将“结束”消息 数据包。接收器必须将所有剩余数据包都保存在内存中 等待最后一个数据包时,客户端便可“预订”有可能 无限的持久内存分配您可以通过多种方式 比如超时和政策限制,但这很快就会变成 通过 FIDL 重新实现 TCP。
显式溢出
本文中提议的设计脱离了 由最终用户递送。用户只需定义其载荷 绑定将在后台完成剩下的工作。
另一种设计是允许用户以声明方式指定 VMO
或按载荷成员级别使用。在
这只需要修改 FIDL 语言,以提供更简洁的
fuchsia.mem.Data
的拼法。
现有设计需要牺牲性能和 API 实现较低的确定性和精细控制, 物有所值。
只允许值类型
此设计的早期版本建议仅针对 值启用 overflowing
类型。原因很简单:没有现有的应用场景
因此单个 FIDL 消息不太可能
需要同时发送超过 64 个标识名,因此我们将其认定为
优先级。
在为
fuchsia.component.resolution
库,一个小改进
。某些方法已经使用表来承载其载荷,并且
而不是批量替换方法,而是希望将表扩展到
逐步弃用 fuchsia.mem.Data
。具体而言:
// Instead of adding a new method to support large messages, the preferred
// solution is to extend the existing table and keep the current method.
protocol Resolver {
Resolve(struct {
component_url string:MAX_COMPONENT_URL_LENGTH;
}) -> (resource struct {
component Component;
}) error ResolverError;
};
type Component = resource table {
// Existing fields - note the two uses of `fuchsia.mem.Data`.
1: url string:MAX_COMPONENT_URL_LENGTH;
2: decl fuchsia.mem.Data;
3: package Package;
4: config_values fuchsia.mem.Data;
5: resolution_context Context;
// Proposed additions for large message support.
6: decl_new fuchsia.component.decl.Component;
7: config_values_new fuchsia.component.config.ValuesData;
};
这些方法存在一个有趣的问题: 且带有标识名的情况实际上是相互通信的 但保真编译器并不知道这一点。从角度来看 其实就是资源类型虽然可以想象,有些块屋可以用于教学 因此我们认为仅允许 63 个 处理大型消息,直到可以找到更合适的内核基元, 开发。
overflowing
修饰符
这种设计的早期迭代让用户可以在overflowing
定义其协议方法的 FIDL,如下所示:
// Enumerates buckets for maximum zx.channel message sizes, in bytes.
type msg_size = flexible enum : uint64 {
@default
KB64 = 65536; // 2^16
KB256 = 262144; // 2^18
MB1 = 1048576; // 2^20
MB4 = 4194304; // 2^22
MB16 = 16777216; // 2^24
};
@transport("Channel")
protocol Foo {
// Requests up to 1MiB are allowed, responses must still be less than or equal
// to 64KiB.
Bar overflowing (BarRequest):zx.msg_size.MB1
-> (BarResponse) error uint32;
};
该框架最终被认为过于复杂和微妙, 而没有明显的选择指南。最终,大多数用户很可能会 需要回答一个简单的是非问题(“我需要大型邮件支持吗?”)、 而无需担心特定产品 限制。
此外,还考虑使用单独的 overflowing
关键字,并且该关键字不包含
以便让用户清楚地知道他们接受的可能
高性能 API。我们最终决定不在这方面
而且在任何情况下应尽可能缩小
语言本身。
未来可能会做的工作
您需要做一些配套的努力,虽然不是绝对强制要求 支持显示大量消息功能,这些都是重要的补充和优化措施 完成这项工作。
内核变更
有许多内核变更,尽管它们不在关键路径上
无疑有助于减少系统调用抖动和
并优化广告效果。对于大型消息,面向用户的 API 使其
实施这些额外的优化措施时,不应发生更改。大多数人
fuchsia.mem.Data
的现有用户对延迟不是特别敏感
(否则它们就不会使用 fuchsia.mem.Data
!),因此
修改内核可以提高性能,以应对
在 FIDL 中启用大型消息后启动。
一流的直播
每当出现大型消息用例时,都会不可避免地有人提出这样的问题, “能否通过在 FIDL 中实现一流的数据流来解决这个问题?”接收者 回答这一问题时,有两个实用属性可用于 大量数据都可以进行分类,应该考虑以下事项: 分块性和可附加性。
可分块性是指相关数据是否可以拆分为
更重要的是,数据的接收方
只对一部分非常有用。从本质上讲,
T
和 vector<T>
或 array<T>
类型的数据,其中
列表的部分视图仍然可供操作,而另一部分则是可操作。分页列表是
可分块,而用于排序的列表项列表不可分块。同样,
树也不是可分块的:通常来说
树的(任意)部分。
可附加性是指数据在发送后是否可以修改。 可附加 API 的一个典型示例是 Unix 管道: 从读取端开始读取时,即便在预期之内, 从撰写内容开始添加的内容。数据发送后 可附加。对于在发送时不可变的数据,即便是列表形式, 错误。
清单 6:所有首选大型数据处理策略的矩阵 分块性和可附加性的可能组合。
这两个区别很有用,因为将它们组合到矩阵中可以 指导哪些大型消息或信息流更合适。
对于静态 blob,如数据转储、B 树或大型 JSON 字符串,用户 他们希望在线访问:只向他们发送一条信息,而且该信息正(或 (最低可能性)对 FIDL 来说过大,这通常是一种意外的复杂程度 也远远超出了他们的顾虑在这种情况下,他们需要通过某种方式告诉系统 获取除最不合理大小的邮件以外的所有内容 “整条线缆”的设计。在数据被具体化到 因此在进程之间逐个移动毫无意义。
对于可分块的动态数据结构(例如网络数据包流), 流是显而易见的选择(就在名义上!)。用户已经 自定义迭代器来处理这种情况,从而编写用于处理 并在发送端彻底公开该视频流 看上去很适合获得一流治疗。它也是一种 自然模式,在大部分领域都拥有强大的支持,并且对编程人员十分熟悉。 FIDL 绑定的语言(C++、Dart、Rust)。
那些可分块且大体为静态的消息(如快照)怎么办? 列出连接到设备的外围设备?要实现这一目的 将这些对象分块并作为流公开 在某些情况下,API 会提供此类信息, 作者将分页视为他们添加的库来满足 FIDL, 而不是作为核心功能在这种情况下,数据似乎与情境密切相关, 信息流或大型消息是更好的选择。
总而言之:大型邮件只是 通过网络获取大量数据的潜在方法。FIDL 非常 未来可能会采用一流的流式传输实现方案, 是对大型消息功能的功能的补充,而不是取代这些大型消息的功能。
有界限协议和灵活的信封大小限制
这种设计存在非常真实的拒绝服务攻击风险
特别是一些相互独立的客户端共享的协议
可能希望避免的方面。为此,可以想象添加一个 bounded
修饰符来
提供编译时强制执行,使它们的所有方法仅使用
有界限类型:
// Please note that this syntax is very speculative!
bounded protocol SafeFromMemoryDoS {
// The payload is bounded, so this method compiles.
MySafeMethod(resource struct {
a bool;
b uint64;
c array<float32, 4>;
d string:32;
e vector<zx.handle, 64>;
});
};
这种设计的一个后果是,FIDL 协议作者面临着
“分叉式”选择:添加 bounded
会尽可能安全地保护协议
因为消息量过大而发起拒绝服务攻击,但会阻止该方法
使用 table
或 flexible union
类型传递载荷。这是一个
但遗憾的是,开发和 ABI 兼容性是
FIDL 语言。通过强制用户使用 ABI 稳定的类型,我们可以大大限制
在未来改进有效负载的能力。
一种可能的折衷方案是为 flexible
引入明确的大小限制
信封布局。这可以提供 ABI 兼容性,因为灵活定义是
会随时间的推移而发生变化,但仍然会对类型的
大小上限:
// Please note that this syntax is very speculative!
@available(added=1)
type SizeLimitedTable = resource table {
1: orig vector<zx.handle>:100;
// Version 2 still compiles, as it contains <=4096 bytes AND <=1024 handles.
@available(added=2)
2: still_ok string:3000;
// Version 3 fails to compile, as its maximum size is greater than 4096 bytes.
@available(added=2)
3: causes_compile_error string:1000;
}:<4096, 1024>; // Table MUST contain <=4096 bytes AND <=1024 handles.
这种类型的大小限制提供了某种“软”灵活性: 能够随时间而变化,但很难(即破坏 ABI 合规性) 首次定义载荷时,针对该增长的范围施加限制。
先验技术和参考资料
这个提案是前所未有的“fuchsia.mem.Data
”,而在它之前,
fuchsia.mem.Buffer
和 zx.handle:VMO
广泛应用于
fuchsia.git 代码库中这一决定本质上是进化
这种经过充分测试的模式。
之前已放弃的 RFC,其描述与此相似, 还使用 VMO 作为大型消息的底层传输机制。
附录 A:fuchsia.git 中 FIDL 载荷的边界
下表显示了溢出之间的有界限分布情况
(大于 64KiB)和 fuchsia.git 代码库中的标准载荷(截至
2022 年 8 月初。这些数据是通过构建
“全部”fuchsia.git 的版本。然后,通过
一系列 jq
查询。
列表 7:显示每种载荷边界的测量频率的表 并将大小和大小保存在 fuchsia.git 代码库中。
边界 / 消息种类 | 标准 | 溢出 | 总计 |
---|---|---|---|
有边界 | 3851 (76%) | 45% | 3896 位 (77%) |
半界 | 530 (10%) | 70 (1%) | 600 (11%) |
无界限 | 0 (0%) | 602 个 (12%) | 602 个 (12%) |
总计 | 4381 (86%) | 717 位 (14%) | 5098 (100%) |