RFC-0010:zx_channel_iovec_t 支持 zx_channel_write 和 zx_channel_call | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 此 RFC 为 zx_channel_write 和 zx_channel_call 引入了新模式,其从多个内存区域复制输入数据,而不是从单个连续缓冲区复制输入数据。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2020-09-25 |
审核日期(年-月-日) | 2020-10-06 |
摘要
此 RFC 为 zx_channel_write
、zx_channel_write_etc
、
zx_channel_call
和 zx_channel_call_etc
,用于从
而不是从单个连续缓冲区产生。这个
通过允许
直接从多个用户空间对象复制,无需任何中间操作
分配、复制和布局步骤这是通过更新现有的
用于获取 zx_channel_iovec_t
内存区域描述符数组的系统调用
。
设计初衷
此提案的主要目的是优化性能。
对于非线性化域对象,FIDL 绑定目前需要 (1) 分配
缓冲区,以及 (2) 将对象复制到标准布局的缓冲区中。在这些之后
那么缓冲区会再次复制到内核中。zx_channel_iovec_t
允许
要直接复制到内核中的对象。其他 FIDL 消息
不再需要按标准顺序来排列数据,
zx_channel_iovec_t
数组必须反映所需的顺序。
设计
zx_channel_write 目前具有以下签名:
zx_status_t zx_channel_write(zx_handle_t handle,
uint32_t options,
const void* bytes,
uint32_t num_bytes,
const zx_handle_t* handles,
uint32_t num_handles);
输入数据是由 bytes
指向的连续字节数组。
zx_channel_write_etc
、zx_channel_call
和zx_channel_call_etc
,
是类似的数组。这些数组必须连续
开销。特别是,对于带有外联组件的 FIDL 消息,
FIDL 编码器必须分配一个缓冲区,并将数据
价格高昂。
zx_channel_iovec_t
提供了备用路径。zx_channel_write
,
zx_channel_write_etc
、zx_channel_call
和zx_channel_call_etc
而是接收对象位置和大小的列表,
复制操作在内核内部进行,以避免额外的重复内容
分配。
zx_channel_iovec_t
在 C++ 中的定义如下:
typedef struct zx_channel_iovec {
void* buffer; // User-space bytes.
uint32_t capacity; // Number of bytes.
uint32_t reserved; // Reserved.
} zx_channel_iovec_t;
每个 zx_channel_iovec_t
均指向要从中复制数据的后续 capacity
个字节。
buffer
到内核消息缓冲区。必须将 reserved
赋值为零。
仅当 capacity
为 0 时,buffer
字段才能为 NULL。buffer
分
可以在多个 zx_channel_iovec_t
中重复。
zx_channel_write
、zx_channel_write_etc
、zx_channel_call
的签名
或 zx_channel_call_etc
都保持不变。不过,如果用户指定了
这些系统调用的 ZX_CHANNEL_WRITE_USE_IOVEC
选项,即 void* bytes
参数将被解释为 zx_channel_iovec_t*
。同样,
num_bytes
参数将被解释为 zx_channel_iovec_t
的数量
。
请注意,句柄数组的类型(zx_handle_t
或
zx_handle_disposition_t
)不相关,因为只有 bytes
数组
已更改。
由 zx_channel_iovec_t
数组描述的消息,可用于发送
邮件的所有部分,否则邮件将不会通过
全部。提供给系统调用的句柄不再可供调用方使用
包括成功和失败时都在考虑的因素
错误情况
以下是 zx_channel_write
、zx_channel_write_etc
、
zx_channel_call
和 zx_channel_call_etc
,由于以下日期而更新:
ZX_ERR_OUT_OF_RANGE num_bytes
或 num_handles
大于
ZX_CHANNEL_MAX_MSG_BYTES
或 ZX_CHANNEL_MAX_MSG_HANDLES
。
如果指定了 ZX_CHANNEL_WRITE_USE_IOVEC
选项,
如果 num_bytes
大于该值,则生成 ZX_ERR_OUT_OF_RANGE
ZX_CHANNEL_MAX_MSG_IOVEC
,或者 iovec 容量的总和超过
ZX_CHANNEL_MAX_MSG_BYTES
。
ZX_ERR_INVALID_ARGS bytes
是无效指针,handles
是无效的指针,或者 options
包含无效的选项位。
如果指定了 ZX_CHANNEL_WRITE_USE_IOVEC
选项,
如果 buffer
字段包含无效的指针,则为 ZX_ERR_INVALID_ARGS。
对齐方式
在
zx_channel_iovec_t
。每个 zx_channel_iovec_t
将在没有内边距的情况下被复制。
限制
目前对每个节点的字节数 (65536
) 和句柄 (64
) 的限制
消息的内容保持不变。请注意,这些限制适用于邮件,
zx_channel_iovec_t
个条目。
每次系统调用的 zx_channel_iovec_t
数量上限为 8192
。这个
数字来自 8 字节对齐的内联对象 + 外行对象的数量
65536
字节消息的大小,其中包含每个 inline + 行外对象
可能使用 zx_channel_iovec_t
条目。
实现
系统调用
- 引入设计部分中定义的
zx_channel_iovec_t
类型。 - 添加
ZX_CHANNEL_WRITE_USE_IOVEC
- 可见的系统调用接口
zx_channel_iovec_t
数组没有更改 会传递到现有的bytes
参数中。
内核
收到 ZX_CHANNEL_WRITE_USE_IOVEC
选项后,内核将执行以下操作:
- 将
zx_channel_iovec_t
对象所指向的数据复制到消息中 缓冲区。虽然复制操作通常也按顺序zx_channel_iovec_t
输入,它并非强制性要求。不过,最后一个 消息必须按照zx_channel_iovec_t
条目的顺序排列。 - 将消息写入渠道。
FIDL
这是针对系统调用更改的建议,实施 发生在内核内部,并且 FIDL 绑定的具体变更 不在范围内。尽管如此,为了评估该提案, 对 FIDL 编码的影响至关重要。
FIDL 绑定可以选择性地利用 zx_channel_iovec_t
支持,
添加了对将 FIDL 对象编码为 zx_channel_iovec_t
数组的支持。
此编码路径与现有编码路径之间的主要区别在于,
zx_channel_iovec_t
允许内核就地复制对象。主要
复杂之处在于指针。FIDL 编码的消息
已发送到内核,并将指针替换为 PRESENT
或 ABSENT
标记
值。不过,在许多情况下,对象仍然需要
原始指针值,以便能够
调用。
这意味着利用 zx_channel_iovec_t
的绑定将
有时需要额外做一些记账工作
已正确清理。
Migration
由于此功能是作为默认停用选项实现的,因此它 不应对现有用户立即产生影响。呼叫网站可以迁移到 请根据需要使用相应选项
实际上,目的是迁移
使用 zx_channel_iovec_t
的优势。这预计
对 FIDL 用户的影响。
性能
已实现原型并进行了基准测试。
此原型在内核端实现了 zx_channel_write 选项
和有限的 FIDL 支持(仅限内嵌对象和矢量)。
邮件标头,以及各个内嵌和外行对象
具有 zx_channel_iovec_t
条目。
64 个条目的数组用于存储 zx_channel_iovec_t
进行内核和 FIDL 编码。
这些测量结果来自搭载 Intel Core i5-7300U CPU @ 2.60GHz 的机器。
字节矢量事件基准(zx_channel_write、zx_channel_wait_async 和 zx_channel_read)有显著提升:
- 4096 字节矢量:9398 ns ->4495 纳秒
- 256 字节矢量:8788 ns ->3794 纳秒
FIDL 编码的性能也有所提升。
对字节矢量示例的时间进行编码:
- 4096 字节矢量:345 纳秒 ->88 纳秒
- 256 字节矢量:251 ns ->86 纳秒
内嵌对象在编码方面也有小幅改进:
- 包含 256 个 uint8 字段的结构:67 ns ->49 纳秒
安全注意事项
由于这是对现有系统调用的重大更改, 在具体实施之前需要进行安全审核。
隐私注意事项
隐私权应该不会受到任何影响。
测试
我们将为更改的每个层添加单元测试和集成测试。
不打算添加任何设备或系统级端到端测试,不过 现有测试覆盖范围将有助于确保不会引入意外 bug 之后的数据
文档
需要更新系统调用文档以指明支持此操作 功能。
无需对架构范围内的文档进行更改。
缺点、替代方案和未知问题
这种方案的主要缺点在于
内核中的选项,以及 FIDL 绑定的实际增加复杂性
使用 ZX_CHANNEL_WRITE_USE_IOVEC
选项,且需要确保
针对就地复制更改对象后,系统会对对象进行适当清理。
限制
有一个参数要求 zx_channel_iovec_t
的数量下限
可能会比8192
更接近16
。这样,
zx_channel_iovec_t
数组,以复制到内核的堆栈中。不过,
会妨碍在实现策略中分配一个 zx_channel_iovec_t
每个外行 FIDL 对象的条目。
实际上,如果存在以下情况,在用户空间中进行线性化可能会提高性能:
大量 zx_channel_iovec_t
,或者至少避免轮班
内核中不过,为简单起见,建议设置 8192
限制,直到
将会知道是否需要进一步优化
上限较高的一个实现级结果是
zx_channel_iovec_t
数组无法完全放入内核堆栈。堆栈
缓冲区可用于常见情况,但需要将其复制到
当有足够多的条目时,会加载更大(更慢)的缓冲区。
矢量化手柄
可以为句柄添加 zx_channel_iovec_t
,
或者将它们与字节一起包含在现有 zx_channel_iovec_t
中。不过,
标识名的好处则更加有限,因为句柄数组往往
小。为简单起见,标识名仍位于专用数组中。
支持在单次写入中发送多条消息
此 RFC 的先前版本包括一个支持多种
通过单个 zx_channel_write
调用返回消息。
我们考虑了以下 3 项提案:
- 平面表示法:将
reserved
字段重新用于 包含两个uint16_t
字段的zx_channel_iovec_t
:message_seq
(zx_channel_iovec_t
所属的邮件)和handle_count
(号码buffer
中的字节所使用的句柄)。序列号为 限制在单调上,且没有间隙。借助此限制条件, 一种性能更高的内核实现,但将来可能会降低 (如果需要的话)。此方法符合此 RFC,且支持多消息 可以添加到现有结构中 - 数组数组表示法:外部存在一组消息,每个数组都有
指向每条消息的 iovecs 内部数组的指针。这类似于
Linux 系统调用
sendmmsg
中使用的一个结构,因此可能更熟悉 用户。虽然“数组的数组表示法”的性能 有证据表明,由于潜在的广告成本, (请参阅 CL)。 - 带标头前缀的表示形式:缓冲区以标头开始,
后跟 iovec 数组。标头包含 16 个消息描述符
每个字段都只包含
uint16_t
message_size
字段。此字段 决定了zx_channel_iovec_t
消息。这种表示法提供了层次结构,但消除了 需要进行额外的重定向和复制。
在设计讨论中,扁平表示方式受到青睐,因为它具有 性能属性和简单性。虽然支持多消息功能的完整方案 支持不在此 RFC 的涵盖范围内,请注意,此 RFC 与 使用平面表示。
iovec 的专用系统调用
您无需向现有系统调用添加新选项
zx_channel_write_iovec
,zx_channel_write_etc_iovec
,zx_channel_call_iovec
可以创建 zx_channel_call_etc_iovec
系统调用。不过,您可以选择
希望避免系统调用次数激增和 的认知负荷
用户。
zx_channel_read 支持 zx_channel_iovec_t
此 RFC 建议支持使用 zx_channel_iovec_t
写入频道,但不支持
通道读取。之所以这样做,是因为
iovecs(写入端),避免 FIDL 线性化步骤 - 但没有
从阅读方面获得明确、直接的好处。
Rust 绑定可能会受益于读取端 iovec 支持,具体方法为: 将缓冲区划分为多个较小的缓冲区,每个缓冲区都有自己的 所有权。这将促进类似于 LLCPP 的绑定变体 它本质上是将缓冲区转换为输出对象。不过,您无需 短期计划更改 Rust 绑定,使其以这种方式工作 推迟添加对 read-path iovec 的支持可能要付出很多代价, 也是需要的。
先验技术和参考资料
Fuchsia 已有 zx_stream_readv
和 zx_stream_writev
系统调用,
使用矢量化 io。Linux 还提供了类似的 readv
和 writev
系统
分别用于读取和写入文件描述符的调用。