| 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 的当前定义允许传达各种不同的错误情况,但目前只能传达单个成功值。随着时间的推移,Fuchsia 开发者发现了几个用例,在这些用例中,将其他非错误信息传达回调用方会很有用:
非错误警告:某些函数想要发出警告,即函数大部分成功,但存在潜在问题。例如,写入缓冲区的请求成功,但缓冲区小于可用数据量。
有关对象状态的其他信息:例如,内核 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语义的文档(Markdown 文档和源代码注释),以与此规范中提出的内容相匹配。更新
zx_debuglog_read系统调用以使用自定义(非zx_status_t)类型。更新正
zx_status_t状态范围的当前用户,以使用一种新类型,该类型可以更好地描述正在生成的其他错误。
性能
首先是此编码方案本身的性能机制。 在大多数情况下,与其他编码此信息的方案相比,性能变化应该很小且微不足道。对于具有昂贵或稀疏形参的系统调用,性能可能会有轻微的提升。
其次是系统 API 更改为使用“成功并提供更多信息”方案的性能变化。此更改的部分动机是向用户空间传达更多信息,以便用户空间做出更复杂的决策,从而实现更高的性能。
总的来说,我们预计系统调用传达其他信息的功能所带来的性能提升将超过评估状态值的代码的复杂性。
除了性能之外,此更改以及未来使用此功能的更改可能会更改许多二进制文件(尤其是生成的 FIDL 绑定)中的代码大小。
安全注意事项
将 zx_status_t 范围嵌入到其他类型中可能会导致混淆,从而导致引入软件 bug 的风险。执行此类嵌入的函数或协议应仔细评估,以确定好处是否大于混淆的风险。
将使用 zx_status_t 和应用专用错误代码的函数迁移到具有更明确类型的函数,应有助于减少混淆的可能性。
隐私注意事项
此提案不会以有意义的方式与用户数据互动,因此不会对隐私产生影响。
测试
我们将为上述几个新函数开发单元测试。
文档
树内 Markdown 文档和代码内注释将更新,以反映 zx_status_t 的新定义。
缺点、替代方案和未知因素
缺点:来自不可信来源和 FIDL 绑定的 zx_status_t 值
目前,我们无法阻止超出范围的 zx_status_t 值通过通道传输。从不可信来源接收 zx_status_t 值并要求这些值在范围内的应用需要手动验证这些值。从长远来看,可能需要更新 FIDL 绑定生成器,以检查并拒绝超出范围的 zx_status_t 值,但这将是独立于此 RFC 的工作。
替代方案:内核输出形参
内核的另一个设计空间是使用额外的输出实参。这种方法有几个缺点:
更改这些系统调用的类型是一项侵入性更强的更改,需要更长的迁移时间。
所有这些系统调用都需要具有带输出形参的变体,或者让对额外信息不感兴趣的调用方传入 null。这两种方法都会降低人体工程学。
使用输出实参来传达少量信息是对稀缺且昂贵的资源的一种低效使用,因为它会占用少量寄存器中的一个(尤其是在 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 有许多不同的可能解释,导致混淆(从而导致 bug)的风险,因此我们拒绝了此替代方案。
替代方案:划分为错误代码和成功代码
之前的提案建议将 zx_status_t 划分为错误代码范围和成功代码范围,其中每个范围进一步划分为系统定义范围和应用定义范围。
这种拆分有一些缺点:
系统定义的成功代码的实用性尚不清楚:虽然错误代码经常在调用堆栈中向上传播,但成功代码往往会立即处理或直接丢弃。 对于具有全局理解语义的一组成功代码的需求较少。
将正值限制为仅成功代码会阻止对该值的其他更高效的使用,例如返回有关对象当前状态的位字段,或返回小型载荷(例如字节数)。
将正空间重新用于成功代码需要迁移当前使用该空间来存储应用专用错误代码的现有代码。
允许每个函数使用整个非负空间可以减少迁移负担,并为开发者提供更大的灵活性。
在先技术和参考文档
Linux 内核在内部使用负范围来存储错误,并将正范围用于函数专用目的。大多数系统调用在系统调用边界处将此单个值拆分为返回代码和线程本地
errno变量。UEFI 规范将其状态空间划分为负值(错误)、零(成功)和正值(警告)。错误范围和警告范围都进一步划分为“EFI 保留”范围和 OEM 范围,使用第二高位。
版本历史记录
2021-03-12:移除了提案的一部分,该部分建议使用新的错误 代码来指示收到了超出范围的
zx_status_t值。 相反,我们将审核树内代码以移除此类用法,并更新 FIDL 绑定生成器以避免超出范围的值跨进程边界传播。2021-03-09:修改了提案,将 有效
zx_status_t值的范围重新定义为[-2^30, 0]。此缩小的范围允许更轻松地将zx_status_t嵌入为其他类型的子范围。2021-02-10:修改了提案,将
zx_status_t中的负值拆分为应用错误和系统错误,但 保留所有非负值供应用使用,无需 进一步解释。2020-10-09:最初的提案是将
zx_status_t错误 空间拆分为四个分区:应用错误和系统错误作为 负值;而系统成功代码和应用成功 代码将是正值。0 将保留为ZX_OK。