RFC-0085:减少 zx_status_t 空间

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](即 是 -10737418240)。此范围中的所有值都将是 系统定义的错误代码或单个成功代码 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_NEXTZX_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