| RFC-0218:IOBuffer:一种用于高效 IO 的对等共享内存对象 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 引入了新的共享内存对象,旨在改进分布式 IO 应用场景。 |
| 问题 |
|
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2022-09-29 |
| 审核日期(年-月-日) | 2023-05-09 |
摘要
我们提出了一种新的 IOBuffer 内核原语,旨在通过共享内存和可选的内核介导操作,提高通信和分布式 IO 使用情形的安全性、便利性和性能。
设计初衷
Fuchsia 操作系统的绝大部分是在用户空间中实现的,分布在多个相互通信的进程和组件中。这种有意为之的设计选择通常需要比单体系统中的等效实现更多的进程和线程,尤其是在单体内核中直接实现等效功能时。虽然这种分布式方法在安全性、开发和可更新性方面具有显著优势,但一个重要影响是,在 Fuchsia 中,许多操作都需要 IPC,而在单体内核中,只需要系统调用和状态读/写。此外,使用现有 Fuchsia 渠道和套接字原语的 IPC 涉及多次系统调用、模式切换、线程跳转和数据复制,才能完成端到端操作。这种开销对通信和 IO 工作负载(尤其是涉及大量数据、高频和/或低延迟操作的工作负载)的影响可能非常大。
原则上,共享内存可用于避免部分或全部此类开销,同时保留 Fuchsia 用户空间隔离模型的优势。不过,原始共享内存存在一些限制,在许多情况下(尤其是涉及多个客户端、信任度或可靠性各不相同的情况,或者涉及共享资源的情况),很难实现安全、高效且可靠的通信。
其中一些挑战包括:
- 安全性和正确性:直接访问共享内存通信模式很难在不产生大量开销或复杂性的情况下,有效防范滥用或恶意使用。重要的是,与任意内存访问相比,系统调用的验证更简单。
- 同步:除了架构内存模型之外,直接内存访问提供的同步机制有限。
- 生命周期和会话:使用现有原语很难推理和强制执行内存访问范围。
此提案是一种务实的方法,旨在解决这些挑战和其他挑战,利用 Zircon 对等对象模型来更好地管理会话,并利用内核作为可信的中介来强制执行义务。
利益相关方
辅导员:
- cpu@google.com
审核者:
- adanis@google.com
- rashaeqbal@google.com
- fmeawad@google.com
- gmtr@google.com
- maniscalco@google.com
- johngro@google.com
- abarth@google.com
- cpu@google.com
已咨询:
- miguelfrde@google.com
- puneetha@google.com
- mseaborn@google.com
- mcgrathr@google.com
- quiche@google.com
- mvanotti@google.com
- brunodalbo@google.com
共同化:
该 RFC 经过了 Fuchsia 性能、诊断和 Zircon 性能工具工作组的设计审核和迭代。
设计
IOBuffer (IOB) 设计引入了一种新型内存对象,可实现高效的通信和分布式 IO。新对象将具有不同属性和角色的多个内存区域封装到一个连贯的实体中,该实体具有实用的生命周期管理、访问权限控制和内核介导的操作。
IOB 是具有两个端点的对等对象对。这些端点提供的生命周期管理和信号传递功能与其他对等对象(例如通道、套接字、FIFO 和事件对)类似。除了常规端点句柄权限提供的访问权限控制之外,还可以针对每个端点单独配置对每个内存区域的访问权限,以支持精细的安全属性。
可配置的内核介导操作支持更强大的安全属性,其中内存访问通过内核系统调用代表用户执行,内核充当可信的介导者,始终遵循所请求的访问规则。中介操作会牺牲一些开销来换取稳健性和安全性,同时保持比其他通信基元更低的端到端开销。
这种灵活的组合包含生命周期管理、内存区域封装、可配置的访问控制和内核介导的操作,可支持各种共享内存通信和 IO 模式,与由其他离散对象组成的类似结构相比,具有更高的安全性和便利性。
目标
此提案的总体目标如下:
- 通过减少通信和 IO 的内存分配、系统调用和调度开销,缩小与单体系统的性能差距。
- 简化了共享内存生命周期管理和同步。
- 在需要时强制执行严格的安全不变量。
- 为未来的内核和用户空间接口提供基础。
端点
每个 IOB 都有两个端点,即 Ep0 和 Ep1。IOB 端点句柄可能会重复并由多个进程共享。只要至少存在一个端点句柄,IOB 就会保留底层内存对象,从而提供与其他对等对象类似的生命周期语义。
内存区域
IOB 封装了一个或多个具有独立属性和访问权限控制的内存区域,以支持每个区域在通信协议中的预期角色。例如,不同的区域可用于多种用途,包括键值存储、客户端协调、状态管理和数据载荷转移。
访问权限控制
在创建 IOB 期间,内存访问权限是按端点和区域组合配置的。每个端点可被授予对每个区域的不同访问权限。 在验证操作时,区域访问控制会与端点的句柄权限相结合。操作的有效权限可能比句柄权限更严格,但不能超过句柄权限允许的范围。
内存访问也可能通过内核进行中介,而无需映射。
下表列出了与地区相关的三种类型的访问权限控制。可以为每个区域单独设置映射(用户)和中介(内核)访问控制,而端点权限来自启动操作的句柄。
| 类型 | Ep0 | Ep1 |
|---|---|---|
| 映射(用户) | uR0、uW0 | uR1、uW1 |
| 中介(内核) | kR0、kW0 | kR1、kW1 |
| 端点(句柄) | hR0、hW0 | hR1、hW1 |
端点 Epn 的映射或中介操作的有效访问权限(eRn 和 eWn)的计算方式如下:
| 操作 | 读取权限 (eRn) | 写入权限 (eWn) |
|---|---|---|
| 地图 | uRn 和 hRn | uWn 和 hWn |
| 中介操作 | kRn 和 hRn | kWn 和 hWn |
中介访问权限控制和方向性
用户访问控制和内核访问控制之间的一个细微区别在于,前者从绝对意义上控制读写访问权限,而后者从逻辑或方向意义上进行操作。例如,内核介导的读取操作可能涉及更新区域中的簿记数据结构,以指示从缓冲区读取了多少数据。从这个意义上讲,只读访问权限不会阻止内核更新簿记,而是表示只允许进行逻辑读取操作和相关的簿记更新。
示例
下图示意性地描绘了一个具有三个内存区域(包含逻辑功能)的 IOB。端点 Ep0 和 Ep1 分别在逻辑上分配给服务器角色和客户端角色。
- 区域 0“控制”:
- Ep0:RW 映射访问权限。
- Ep1:RO 映射访问权限。
- 用途:服务器将此区域中的原子变量写入到客户端,以发布状态并协调客户端的活动。
- 地区 1“字符串映射”:
- Ep0:RO 映射访问权限。
- Ep1:中介 WO 访问。
- 用法:客户端使用内核介导的操作来内部化字符串,服务器使用映射以无锁方式解释这些字符串。由于客户端访问权限仅为中介权限,因此客户端不得篡改字符串映射(添加新条目除外),从而消除了服务器潜在的检查时、使用时风险。
- 区域 2“数据环形缓冲区”:
- Ep0:RW 映射访问权限。
- Ep1:RW 映射访问权限。
- 使用场景:客户端和服务器通过映射使用约定的协议直接访问此区域,并接受潜在的完整性问题。

创建
通过使用一组选项调用 zx_iob_create 来创建 IOB。
zx_status_t zx_iob_create(uint64_t options,
const zx_iob_region_t* regions,
uint32_t region_count,
zx_handle_t* ep0_out,
zx_handle_t* ep1_out);
参数
options 保留供将来的扩展程序使用。
regions 是一个指针,指向包含
zx_iob_region_t区域说明的数组。region_count 用于指定 regions 数组中的元素数量。
ep0_out、ep1_out 是 IOB 端点的初始句柄的 out 参数。
地区说明
区域的几何图形和配置由 zx_iob_region_t 区域描述结构指定。基本结构包含所有区域类型共有的字段。
struct zx_iob_region_t {
uint32_t type;
uint32_t access;
uint64_t size;
zx_iob_discipline_t discipline;
union {
zx_iob_region_private_t private_region;
uint8_t max_extension[4 * 8];
};
};
type 指定区域的类型以及支持该区域的内存对象。
- 类型 0:专用区域。
- 类型 1 及更高类型:预留以供日后使用。
访问权限用于指定每个端点的访问权限控制修饰符。内存访问规范可以指定哪些访问位组合有效,以支持安全性或正确性属性。
- 位 0:针对 Ep0 的 uR0 映射。
- 位 1:针对 Ep0 的 uW0 映射。在某些系统上可能表示 uR0。
- 位 2:针对 Ep0 的 kR0 中介访问。
- 位 3:针对 Ep0 的 kW0 中介访问。
- 位 4:Ep1 的 uR1 映射。
- 第 5 位:针对 Ep1 的 uW1 映射。在某些系统上可能表示 uR1。
- 位 6:针对 Ep1 的 kR1 中介访问。
- 位 7:针对 Ep1 的 kW1 中介访问权限。
- [8..31]:预留以供日后使用。
size 是所请求的区域大小(以字节为单位)。大小将向上舍入到系统页面大小边界。将
zx_object_get_info与主题ZX_INFO_IOB_REGIONS搭配使用,以确定区域的实际大小。discipline 指定要用于内核介导的操作的内存访问规范。
区域类型 0:专用
指定由 IOB 独有的私有内存对象支持的区域。 此内存对象只能通过对所属 IOB 的操作和映射进行访问。
struct zx_iob_region_private_t {
uint64_t options;
};
- 预留以供日后使用的选项。
区域类型 1+:预留以供日后使用
未来的扩展功能可能包括共享区域类型,其中多个 IOB 共享同一内存对象,以支持高效的多代理协调。
映射
IOB 的内存区域通过调用 zx_vmar_map_iob 进行映射。此调用的语义与 zx_vmar_map 类似。
zx_status_t zx_vmar_map_iob(zx_handle_t vmar,
zx_vm_option_t options,
size_t vmar_offset,
zx_handle_t ep,
uint32_t region_index,
uint64_t region_offset,
size_t region_len,
zx_vaddr_t* addr_out);
参数
- vmar 是要将区域映射到的 VMAR 的句柄。
- options 等同于
zx_vmar_map的 options 参数。仅支持以下列出的选项。 - vmar_offset 等同于
zx_vmar_map的 vmar_offset 参数。 - ep 是包含要映射的区域的端点。
- region_index 是要映射的内存区域的索引。
- region_offset 相当于
zx_vmar_map的 vmo_offset 参数。 - region_len 等效于
zx_vmar_map的 len 参数。 - addr_out 是一个输出形参,用于返回映射的虚拟地址。
支持的选项:
ZX_VM_SPECIFICZX_VM_SPECIFIC_OVERWRITEZX_VM_OFFSET_IS_UPPER_LIMITZX_VM_PERM_READZX_VM_PERM_WRITEZX_VM_MAP_RANGE
预留以供将来使用的其他选项,如果指定,则返回 ZX_ERR_INVALID_ARGS。
映射专用区域在功能上与映射不可调整大小的 VMO 相同。如果映射超出内存对象末尾且未设置 ZX_VM_ALLOW_FAULTS,则返回 ZX_ERR_BUFFER_TOO_SMALL。
region_offset 和 region_len 参数作为映射 API 的最佳实践包含在内。此最佳实践的动机包括:
- 支持拦截映射调用的清理器。使用显式范围可简化对有效映射区域的跟踪。
- 支持明确管理地址空间放置和区域覆盖的沙盒。
- 更一般地说,避免在使用
ZX_VM_SPECIFIC_OVERWRITE时发生意外冲突。
VMAR 操作
映射的区域通常支持 VMAR 操作,例如 zx_vmar_protect、zx_vmar_op_range、zx_vmar_destroy。区域的访问权限规范可能会修改或限制这些操作,如规范中所述。
对象信息查询
IOB 支持通过 zx_object_get_info 查询对象属性和内存区域。返回的详细信息包括支持每个区域的内存对象的关键属性,以用于验证和安全目的。
主题“ZX_INFO_IOB”
返回有关整体 IOB 实例的信息。
struct zx_iob_info_t {
uint64_t options;
uint32_t region_count;
uint8_t padding1[4];
};
成员
- options 是传递给
zx_iob_create的 options 参数的值。 - region_count 是 IOB 中的区域数量。
主题“ZX_INFO_IOB_REGIONS”
以 zx_iob_region_info_t 区域说明元素的数组形式返回有关 IOB 各区域的信息。
struct zx_iob_region_info_t {
zx_iob_region_t region;
zx_koid_t koid;
};
- region 是区域说明,可能包含交换的访问位。
- koid 是底层内存对象的 koid。
访问权限修饰符位被交换,使得 Ep0 访问权限位反映发出查询的端点的访问权限,而 Ep1 位反映另一端点的访问权限,从而可以在不知道哪些句柄在创建时是 Ep0 和 Ep1 的情况下确定本地和远程句柄的访问权限。
主题“ZX_INFO_PROCESS_VMOS”
为了与现有的内存归因机制保持一致,本主题将支持 IOB 区域的内存对象视为常规 VMO,包括通过句柄和映射的可达性。
对于私有区域类型,后备内存对象会被分配不同的 KOID,并且默认情况下与所属 IOB 共享相同的名称。访问规范可以替换或修改专用区域的默认名称。
中介访问
IOB 支持对内存区域进行可配置的内核介导访问。与直接访问相比,中介访问通过使用内核作为可信的中介,可提供更严格的数据完整性和其他不变量。内核保证会遵循访问控制机制为内存区域指定的访问规则。
中介读取、写入等...
中介操作通过 IOB 读取、写入和辅助系统调用来执行,这些系统调用将在未来的扩展程序中指定。访问控制机制可以定义新的 IOB 系统调用或重载之前定义的系统调用,前提是使用在语义上保持一致。一个总体目标是定义一些可重用的系统调用,以适应许多使用场景。
权限
IOB 默认拥有以下权利:
ZX_RIGHTS_BASIC= (ZX_RIGHT_TRANSFER|ZX_RIGHT_DUPLICATE|ZX_RIGHT_WAIT|ZX_RIGHT_INSPECT)ZX_RIGHTS_IO= (ZX_RIGHT_READ|ZX_RIGHT_WRITE)ZX_RIGHTS_PROPERTYZX_RIGHTS_MAPZX_RIGHTS_SIGNALZX_RIGHTS_SIGNAL_PEER
属性
IOB 支持 ZX_PROP_NAME 以提供诊断和归因信息。
IOB 可以支持由区域访问权限规范定义的其他属性。
信号
IOB 支持同类群组信号和用户信号,可提醒用户注意重要情况。未来的扩展程序可能会定义可配置的信号。
ZX_IOB_PEER_CLOSED
当对另一端点的最后一个引用被释放时,会在端点上引发 ZX_IOB_PEER_CLOSED。端点句柄和根据端点数量创建的映射,用作此用途的参考。
ZX_USER_SIGNAL_*
用户可以引发 ZX_USER_SIGNAL_* 来指示重要情况。
内核介导的访问控制
为了方便中介操作,内核必须知道如何正确访问内存区域。对某个区域的内存访问规则和配置称为访问规范,可能包括:
- 账簿的位置和格式。
- 覆盖/失败政策。
- 水印和超时设置。
- 内存顺序和栅栏要求。
纪律说明
访问权限规范使用可扩展的说明结构,该结构包含一个 32 位标头,用于指定说明的类型和格式。
#define ZX_IOB_DISCIPLINE_TYPE_NONE (0)
struct zx_iob_discipline_t {
uint32_t type;
union {
uint8_t max_extension[4 * 15];
};
};
未来 RFC 中可能会定义的学科示例包括:
- 生产方 / 消费方环形缓冲区
- ID / Blob 映射
- 键 / 值存储区
- 分散 / 集中内存复制
- 目录条目缓存
实现
初始实现是支持跟踪和日志记录客户的基础步骤。以下说明是案例研究,旨在说明定义 IOB 功能的动机,以及为满足使用情形要求而进行的潜在扩展。
系统事件跟踪
IOB 旨在通过支持以下功能,为更安全、更高效的系统事件跟踪奠定基础:
- 通过将会话协调、元数据和事件收集完全移至 IOB 区域,减少运行时开销,从而在初始设置后无需调度线程和通道 IPC。
- 通过消除除 IOB 内核 API 之外的依赖项,实现对 FDIO 和 FIDL 等低级设施的插桩。
- 通过允许内核安全地回收轨迹事件缓冲区来提高鲁棒性,以避免与 RFC 0181:无锁可丢弃 VMO 结合使用时出现严重的 OOM 情况。
系统轨迹 IOB 可以使用如下配置:
- 角色:
- Ep0:Trace Manager
- 第 1 集:Trace 提供程序
- 区域 0:轨迹类别位向量
- 类型:私密
- 权限:
- Ep0:uR0、uW0(读/写)
- Ep1:uR1 (RO)
- 纪律:无
- 用途:一个
uint64_t位向量数组,表示已启用的跟踪类别。
- 区域 1:轨迹类别
- 类型:私密
- 权限:
- Ep0:uR0、uW0(读/写)
- Ep1:kW1(仅限中介广告条目)
- 纪律:未来的 ID/blob 分配器纪律。
- 用途:一个顺序 ID/字符串映射,表示轨迹提供程序使用的每个轨迹类别的类别位。
- 区域 2:内部化字符串
- 类型:私密
- 权限:
- Ep0:uR0、uW0(读/写)
- Ep1:kW1(仅限中介广告条目)
- 纪律:未来的 ID/blob 分配器纪律。
- 使用场景:一个顺序 ID/字符串映射,用于表示跟踪事件名称和其他经常引用的名称的内部化字符串。
- 区域 3:低速率 / 静态元数据
- 类型:私密
- 权限:
- Ep0:uR0、uW0(读/写)
- Ep1:uR1、uW1(读/写)
- 纪律:未来的多生产者/单消费者环形缓冲区纪律。
- 用途:用于低速率 / 静态跟踪事件(例如线程名称)的无锁环形缓冲区。通常需要这些事件才能正确解读高速率缓冲区中的其他轨迹事件。
- 区域 4:高频跟踪事件
- 类型:私密
- 权限:
- Ep0:uR0、uW0(读/写)
- Ep1:uR1、uW1(读/写)
- 纪律:未来的多生产者/单消费者环形缓冲区纪律。
- 用途:用于高频跟踪事件的无锁环形缓冲区。在循环缓冲区模式下,可容忍数据丢失。
系统日志记录
IOB 旨在通过支持以下功能,为更安全、更高效的系统日志记录奠定基础:
- 隔离来自不同组件和/或严重程度级别的日志,防止跨组件和跨严重程度的日志滚动。
- 避免处理未被主动观察的日志。
- 不再使用调度线程来处理严重程度变化。
- 基于组件(内存)的资源管理和问责。
涉及的参与者如下:
- 日志生成方:发出日志的组件。
- 日志管理器 (Archivist):将所有提供方的日志路由到所有使用方。
- 日志使用者:连接到日志管理器并读取合并的日志流。
系统日志记录 IOB 可以使用如下配置:
- 角色:
- 第 0 集:日志管理器(归档员)
- 第 1 集:日志生成器(某个组件)
- 地区 0:对照组
- 类型:私密
- 权限:
- Ep0:uR0、uW0(读/写)
- Ep1:uR1 (RO)
- 纪律:无
- 用途:存储运行时配置,例如最低严重程度。还可能会存储有关日志循环缓冲区最大大小的信息,具体取决于内存管理要求。
- 区域 1:字符串表
- 类型:私密
- 权限:
- Ep0:uR0 (RO)
- Ep1:kW1(WO)
- 纪律:未来的 ID/blob 分配器纪律。
- 用法:一个顺序 ID/字符串映射,用于表示经常引用的日志字符串、标记、键、元数据的内部化字符串。
- 区域 2:日志循环缓冲区
- 类型:私密
- 权限:
- Ep0:uR0、uW0(读/写)(用于写入 R/W/C 指针和写入正在进行的计数器)
- Ep1:kW1(WO)
- 纪律:未来的多生产者/单消费者环形缓冲区纪律。
- 用途:用于日志记录的无锁环形缓冲区。在循环缓冲区模式下,可容忍数据丢失。该计划是从生产者中的内核介导写入和管理器中的直接读取开始。如果性能需要,可能会改为由生产者直接访问。
实施步骤
实现过程将遵循一系列开发步骤:
- 在 @next 属性下引入 IOB 接口:
- 实现基本 IOB 调度程序。
- 实现系统调用和常见验证。
- 在分支中开发基于 IOB 的跟踪和日志记录原型,以及必要的 IOB 扩展程序。
- 为必要的扩展程序撰写并批准 RFC。
- 实现扩展功能并移除 @next 属性。
- 基于 IOB 重新设置了跟踪和日志记录,将现有客户端软过渡到新接口。
性能
在弃用并移除当前的跟踪/日志记录实现之前,可以通过比较类似操作的端到端延迟时间和开销来验证性能改进。持续基准比较可能包括缓冲区内存来源(虚拟机开销)、关键操作的延迟时间/并发测试,以及用于验证稳态内存消耗属性的分配测试。
工效学设计
从设计上讲,IOB 比实现类似功能所需的 VMO、事件和 IPC 对象的组合更易于推断。与 VMO 相比,IOB 端点的生命周期也更易于一致地管理,并且内存访问规则也更易于强制执行。
向后兼容性
IOB 只会通过向语言中引入新的句柄类型来影响 FIDL 文件源兼容性和 ABI 有线格式兼容性,而不会影响向后兼容性。
安全注意事项
共享内存使用场景始终存在一些潜在的安全漏洞。大多数 IOB 用例不会引入共享 VMO 不会遇到的新危害。不过,由于对权限和内存访问生命周期有更严格的规定,IOB 确实降低了某些错误的风险。
复制省略和检查时到使用时危害
在共享内存使用情形中出现的一类重要软件 bug 是所谓的“检查时到使用时”危害 (TOCTOU)。TOCTOU 风险是由检查某个状态与使用检查结果之间的竞态条件引起的,因此状态可能会在检查与使用结果之间变为无效。
在开放式共享内存使用情形中避免 TOCTOU 风险可能具有挑战性,通常需要将数据复制出共享内存区域,以避免在验证期间或之后进行修改。即使在采用复制的情况下,也必须注意防止复制省略,因为除非使用特定的干预措施,否则编译器可能会优化掉复制操作,并直接在共享内存区域中运行。
内核介导的操作可以保证内核遵循元数据更新、载荷传输和内存访问顺序的规则,从而简化了 TOCTOU 风险的缓解。例如,在使用环形缓冲区规范时,内核保证不会覆盖记录的内容,直到消费者通过更新环形缓冲区簿记确认可以安全地这样做为止,从而避免了在验证之前复制载荷的需要。
尽管如此,用户仍需负责正确应用内存顺序和原子操作或特定于语言的等效项,以防止因优化而导致正确性风险。为了减轻用户的负担,Fuchsia SDK 将包含适用于受支持语言的库,这些库可为每个已定义的学科实现正确的访问例程。学科规范还将包含足够的详细信息,以确保安全正确地应用访问规则。
隐私注意事项
除了已适用于 IPC 和共享内存用例的隐私注意事项之外,IOB 不会引入新的隐私注意事项。系统事件跟踪和日志记录的初始使用情形将继续承担相同的隐私保护义务。
测试
测试包括以下内容:
- 针对 IOB 的核心测试。
- 针对新类型的句柄类型验证的 FIDL 一致性测试。
- 扩展了 Fuchsia 跟踪系统测试,以纳入 IOB 特有的功能。
- 扩展了系统日志记录测试,以纳入 IOB 特有的功能。
不依赖于底层实现细节的现有跟踪和日志记录测试应继续正常运行并通过。
文档
文档更新包括:
- 系统调用参考。
- 内核概念。
- FIDL 句柄类型。
缺点、替代方案和未知因素
实现此提案的成本相对较低,因为该功能基于现有的虚拟内存和对等调度程序机制。其独特的功能围绕非对称访问权限、内核介导的区域访问和关闭时取消映射功能展开。