RFC-0085:减少 zx_status_t 空间 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 缩小有效 zx_status_t 值的范围,让相应类型更容易嵌入到其他类型中。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2020-10-09 |
审核日期(年-月-日) | 2021-04-08 |
摘要
本文档建议缩小有效 zx_status_t
值的范围
从所有 32 位有符号整数到较小的范围 [-2^30, 0]
,以及
弃用由应用定义的错误代码。这样,
zx_status_t
,可作为其他类型的子范围轻松嵌入。
设计初衷
zx_status_t
是一个简单的错误类型,用于表明
特定操作是否成功。它是指
一个带符号的 32 位整数。值 ZX_OK
(0) 表示操作
成功。所有其他值表示某种形式的错误
包含系统定义的错误代码(负值)和
支持应用程序定义的错误代码(正值)。
zx_status_t
类型在整个 Fuchsia 中被广泛使用。例如:
在 Zircon 内核中使用(包括内部使用以及作为返回代码使用)
系统调用的值);用在许多 FIDL 协议中来指示错误;
用于报告错误;并且经常用于
作为一种简便方法,
来报告函数之间的错误。
虽然 zx_status_t
的当前定义允许各种不同的
需要传达的错误情况,但目前只能
传递一个成功值。随着时间的推移,Fucsia 开发者们
已经发现了几个应用场景,
将其他非错误信息传达给调用方:
无错误警告:某些函数希望发出警告,其中 一个函数大多执行成功,但存在潜在问题。对于 例如,写入缓冲区的请求成功,但该缓冲区 少于可用数据量。
有关对象状态的其他信息: 例如,内核 IPC 基元可能希望表明 在读取完成后待处理,也可以提供更精细的 以及套接字等类型的阈值信息。
流控制:内核和系统库中的通用模式 表示回调返回错误,值
ZX_ERR_STOP
指示不应再调用回调,或ZX_ERR_NEXT
以 指示应继续调用该回调函数。这些特殊的 虽然值本身本身不是错误,但 存储在系统定义的错误空间里。通信小载荷:函数可能需要 会传递少量数据作为其结果的一部分, 调用成功。例如,当前实施的
zx_debuglog_read
系统调用不经意地使用zx_status_t
,用于返回读取到缓冲区的字节数 成功。
虽然从理论上讲,所有这些应用场景都可以通过 某些额外的输出参数或更复杂的化合类型, 对性能要求较高的应用场景,如果使用函数, 可以使用一个简单的整数/寄存器返回值, 上述用例。
在本文档中,我们使用术语“函数”和“返回代码”, 这些概念适用于 FIDL 调用、系统调用等。同样, 虽然这种设计给出了使用 C++ 编写的示例,但同样的思路也适用于其他 包括 C、Rust 和 Go 等语言。
设计
在此方案中,zx_status_t
仍为有符号的 32 位整数,以及
将继续定义为具有单个成功代码 ZX_OK
(0)。
所有其他值应继续视为错误。
不过,我们会更新 zx_status_t
的定义,以便:
有效
zx_status_t
值的范围为[-2^30, 0]
(即 是-1073741824
到0
)。此范围中的所有值都将是 系统定义的错误代码或单个成功代码ZX_OK
。应用定义的错误代码(目前定义为所有正值
zx_status_t
值)已被弃用,如下文所述 “向后兼容性”。
通过限制 zx_status_t
可能采用的值的范围,用户可以
能够将 zx_status_t
值的范围嵌入到另一种类型中,而无需
担心错误代码值与非错误返回值重叠。对于
例如,函数可以定义一个类型 result_or_count_t
,其中负数
值对应于 zx_status_t
错误代码,而
非负值对应于处理的元素数量。
我们要求函数实现者定义“新”类型,而不仅仅是定义
在 zx_status_t
空间的未使用部分发出值。这样可以确保
确保函数的用户清楚函数返回的内容以及
应该进行解释:如果函数的返回值类型
zx_status_t
,保证用户 ZX_OK
是唯一有效的
成功值。
示例
以下部分展示了上述不同应用场景
处理方式是假定 zx_status_t
的范围有限。
其他状态信息
目前,zx_channel_read
的用户只能确定
通过对通道执行失败读取而等待的消息。使用此
zx_channel_read
可能会引入一种新的返回值类型,
提供“更多消息正在等待”状态作为返回值的一部分,
避免额外的系统调用:
/// Keep reading messages until none remain on the channel.
do {
// Read from the channel.
zx_channel_read_result_t result =
zx_channel_read(channel, buffer, /*options=*/ZX_GET_CHANNEL_STATE);
// `zx_channel_read_result_t` defines negative values to correspond
// to `zx_status_t` error codes.
if (result < 0) {
return static_cast<zx_status_t>(result);
}
// Otherwise, the result is defined to be a bitmap indicating
// the state of the channel.
} while ((result & ZX_CHANNEL_MORE_MESSAGES_WAITING) != 0);
流控制
不要依赖于错误代码 ZX_ERR_NEXT
和 ZX_ERR_STOP
(并参考文档告知调用者需要哪些代码
可以引入一种使用非负空间
来指示流控制:
// Negative values are zx_status_t error codes, while non-negative
// values must be one of the constants below.
using zx_iteration_status_t = int32_t;
constexpr int32_t ZX_ITERATION_CONTINUE = 0;
constexpr int32_t ZX_ITERATION_DONE = 1;
// ...
while (true) {
zx_iteration_status_t result = Next(thing);
if (result < 0) {
return result; // error
}
if (result == ZX_ITERATION_DONE) {
break;
}
// ...
}
将载荷混合到响应中
zx_debuglog_read
已使用非负空间返回一小段
有效负载(读取的字节数),目前违反了
zx_status_t
的定义。此方案允许
zx_debuglog_read
,用于定义明确返回方式的新类型
进行解读:
// Read the debug log. Returns a negative value on error, otherwise
// the number of bytes read from the debug log.
zx_debuglog_read_result_t result = zx_debuglog_read(buffer);
if (result < 0) {
return result; // error
}
print_log(/*buffer=*/buffer, /*size=*/result);
应用定义的错误代码
希望定义自己的错误代码的应用程序可以继续 但应定义一个类型,以明确说明 可解释为:
enum ApplicationError {
INVALID_AUTHORIZATION = 1,
TOO_MANY_OUTSTANDING_REQUESTS = 2,
// ...
}
// Zero indicates success. Negative values map to `zx_status_t` error
// codes. Positive values map to `ApplicationError` error codes.
using app_status_t = int32_t;
由于 zx_status_t
只占据 [-2^30, 0]
范围,因此应用
可将范围[-2^31, -2^30)
用于
或应用定义的错误代码
根据需要为其他返回代码留出空间。
向后兼容性
此提案会将 zx_status_t
的有效范围更新为以下值:
[-2^30, 0]
,所有这些都是系统定义的。不过,有
目前有一小部分应用正在使用正空间,
应用程序专用代码。
作为此 RFC 实现的一部分,我们将迁移
正状态代码转换为新的(非 zx_status_t
)类型。
实现
实现此 RFC 的步骤如下:
更新描述 语义
zx_status_t
匹配,以匹配本规范中提议的内容。更新
zx_debuglog_read
系统调用以使用自定义 (非zx_status_t
)类型。更新要使用的正
zx_status_t
状态范围的现有用户 一种新类型,可以更好地描述产生的其他错误。
性能
首先是此编码方案的性能机制, se。在大多数情况下,效果变化应该较小 与编码此信息的其他方案相比是边际的。对于 其参数昂贵或稀缺, 可能会带来轻微的正面效果提升。
其次,系统 API 的性能变化更改为使用 通过更多信息取得成功。这样做的部分原因 目的是向用户空间传达更多信息 从而做出更明智的决策,从而取得更理想的成效。
总的来说,我们预计此功能可提升 与传递额外信息的系统调用相比, 评估状态值稍微复杂一些的代码。
除了性能以外,本次更改以及今后使用 功能可能会改变许多二进制文件中的代码大小,尤其是在 生成的 FIDL 绑定。
安全注意事项
将 zx_status_t
范围嵌入其他类型有可能会
造成混淆,从而带来引入软件 bug 的风险。函数或
执行此类嵌入的协议应仔细评估
带来的益处大于造成混淆的风险。
迁移函数使用不符合规范的 zx_status_t
和
特定于应用的错误代码,具有更明确的类型,
从而减少混淆
隐私注意事项
此方案未以有意义的方式与用户数据交互, 隐私权应该不会受到任何影响
测试
我们将针对上述几个新函数开发单元测试。
文档
在树 Markdown 文档中,代码内的注释将更新为
反映了 zx_status_t
的新定义。
缺点、替代方案和未知问题
缺点:zx_status_t
值来自不可信来源和 FIDL 绑定
目前,我们无法防止发生超出范围的 zx_status_t
值,
都是通过信道传输的接收 zx_status_t
值的应用
并要求它们在范围内,则需要手动
验证它们。从长远来看,可能需要更新 FIDL 绑定
生成器检查并拒绝超出范围的 zx_status_t
值,
这将独立于此 RFC 而运行。
替代方案:内核输出参数
内核的另一个设计空间是使用额外的输出 参数。这种方法有几个缺点:
更改这些系统调用的类型的影响性要大得多 更改,并且需要更长时间的迁移
所有这些系统调用都需要具有 out 参数,或者让调用方对额外信息不感兴趣 传入 null。两者都是人体工程学降级。
使用 out 参数传达少量信息
稀缺而昂贵的资源的利用效率低下,
使用少量寄存器中的一个(尤其是在 x86_64 上),或
是开销非常大的 user_copy
。
替代方案:您可以随意使用 zx_status_t
的非负值
此提案建议限制有效 zx_status_t
值的范围
来协助将范围嵌入其他类型的中,但不允许使用
让应用直接使用未使用的范围。
先前的提案拆分了“zx_status_t
”空间,并保留了负值
并允许函数根据需要使用非负值。
如果允许将 zx_status_t
类型重复用于特定于函数的目的,
这样做的好处是,开发者可以
来自函数的载荷(无需创建其他类型),以及
事实证明,这便于内核系统调用开始返回
将数据传输到某些调用方,而不会破坏现有调用方的 ABI。
但这样做的缺点是,您不清楚应该选择 只看类型就可能返回函数。此外,广泛使用 例如以下习语:
zx_status status = CallFunction();
if (status != ZX_OK) {
return status;
}
都无法保证正确,因为 CallFunction
使用正数
返回代码空间。
由于 zx_status_t
具有很多
不同的可能解读导致我们拒绝了这个备选方案。
替代方案:分为错误代码和成功代码
以前的提案建议将“zx_status_t
”划分为
错误代码和成功代码的范围,每个范围
进一步分为系统定义的范围和应用定义的范围。
这种拆分有一些缺点:
系统定义的成功代码的用途不明确: 而错误代码会经常传播到调用堆栈, 代码要么立即得到处理,要么直接舍弃。 因此不太需要使用一组全球通用的成功代码 语义信息。
将正值限制为仅成功代码可防止出现其他错误, 更有效地利用值,如返回位字段 对象的当前状态,或者返回少量载荷, 例如字节数
重新利用成功代码的空间需要进行迁移 目前将其用于针对特定应用的 错误代码。
允许每个人使用整个非负空间 功能降低迁移负担,并为开发者提供更多 灵活性。
先验技术和参考资料
Linux 内核在内部使用负范围表示错误, 保留正范围以用于函数专用目的。大多数人 系统调用将此单个值拆分为返回代码和线程局部
errno
变量。UEFI 规范将其状态空间划分为负值 (错误)、零(成功)和正值(警告)。 错误和警告范围进一步拆分为“EFI 预留” 范围和 OEM 范围,使用第二有效位。
版本历史记录
2021 年 3 月 12 日:移除了提案中提出新错误的部分 表示收到超出范围的
zx_status_t
值的代码。 而是将审核树内代码以移除此类用法和 FIDL 绑定 生成器将进行更新,以免超出范围的值传播 进行跨进程边界工作2021 年 3 月 9 日:修改了提案,以重新定义 有效的
zx_status_t
值为[-2^30, 0]
。如此缩小的范围zx_status_t
,以便更容易地作为其他类型的子范围嵌入。2021 年 2 月 10 日:修改了提案,将负值拆分到
zx_status_t
在应用错误与系统错误之间分配,但 未将任何非负值 进一步解释。2020-10-09:拆分
zx_status_t
错误的初始提案 分为四个分区:应用错误和系统错误 负值;而系统成功代码和应用成功 将是正值。0 将仍然是ZX_OK
。