Zircon 的 C++

Zircon 树中使用了 C++17 语言的子集。这包括内核和用户空间代码。这两个地方都混合了 C++ 和 C(以及一些汇编代码)。避免使用或禁止使用某些 C++ 语言功能。使用 C++ 标准库功能时需要非常谨慎。

语言功能

  • 不允许
    • 异常
    • RTTI 和 dynamic_cast
    • 运算符过载
    • 虚拟继承
    • 静态构建的对象
    • 尾随返回类型语法
      • 异常:当 lambda 具有不可输出的返回类型时,必须这样做
    • 初始化程序列表
    • 内核代码中的 thread_local
  • 允许
    • 纯接口继承
    • lambda
    • constexpr
    • nullptr
    • enum class
    • template
    • 默认参数
      • 但要根据自己的判断来判断。在末尾添加一个可选 out 参数可能没什么问题。四个可选的 Bool 参数,可能没有。
    • 普通的老类
    • auto
    • 多重实现继承
      • 但请谨慎。它被广泛用于干扰性容器 mixins 等用途。
  • 需要更多规则 TODO(cpu)
    • 全局构造函数
      • 目前,我们已将这些变量用于全局数据结构。

TODO:指向样式指南的指针?

C++ 标准版

Zircon 代码使用 -std=c++17 构建而成,通常可以自由使用 C++ 17 语言和库功能(受限于上文所述的样式/功能限制以及下文所述的库使用准则)。与 C++ 14 或更低版本保持兼容没有一般问题。如果标准 C++ 17 功能是最干净的实现方式,就要这样。

所有纯 C 代码(其使用的 .c 源文件和头文件)均为 C 11。对于旨在由树外引导加载程序重复使用的代码,会有一些特殊的例外情况;对于嵌入的代码,此类引导加载程序始终采用保守的 C 89 子集。

标准库

C++ 标准库 API 具有许多特性不同的接口。根据每个特定接口的代码生成及使用机器和操作系统设施的可预测性和复杂性,我们将标准库 API 细分为以下几个类别。这些可视为 API 的同心圆,从最小的类似 C 的子集一直到完整的 C++ 17 API。

背景信息至关重要

本部分提供了相关指南,指导您如何判断使用特定标准 C++ 库 API 对整个系统的影响。除了内核(请参阅下一部分)之外,没有硬性规则,还有实现约束条件(人们总是希望这些约束条件应该只是暂时性)。

压倒性的规则是谨慎行事

  • 请考虑您对时间和空间复杂性、动态分配行为(如有)以及您使用的每个 API 的故障模式的了解程度。

  • 然后考虑使用它的具体“上下文”,以及该上下文对各种问题的敏感度。

  • 尤其要注意依赖于输入的行为,在使用重要的库设施时,这种行为可能很快就会变得难以预测。

如果您在任何类型的系统服务中编写驱动程序或热路径中的主要 I/O 逻辑,以实现延迟、吞吐量或可靠性,那么对于依赖的库工具,应该非常保守。从技术层面来说,它们在用户空间中可供您使用(尽管内核中的版本要少得多;请参阅下一部分)。但实际应该使用的并没有太多。您可能不想依赖大量在后台进行动态分配的 std 容器。它们将使您难以了解、预测和控制服务的存储空间/内存占用、分配行为、性能和可靠性。

尽管如此,即使是驱动程序也是可以启动并解析配置文件或参数等的用户空间程序。对于不属于热路径的所有非必需或启动时间函数,在简化工作的情况下,使用更复杂的库设施可能没有问题。请务必注意代码的总体指标,例如最小/总/峰值运行时内存用量、代码膨胀(会同时使用设备存储空间和运行时内存)以及应对意外故障模式的弹性。请不要仅仅为了利用出色的配置解析库,就将驱动程序的代码大小和内存占用量加倍。

内核中没有 std

C++ std 命名空间不能内核代码中使用,其中还包含引导加载程序。少数不涉及 std:: API 的 C++ 标准库头文件仍可直接使用。请参阅下一部分。

内核代码中不应使用其他 C++ 标准头文件。相反,任何值得在内核中添加的库工具(例如 std::move)都通过内核专用 API(例如 ktl::move)提供。实际上,这些 API 的内核实现可能依赖于工具链标头,这些实现是对内核 API 名称进行别名化处理的 std:: 实现。但是,只有这些 API 实现和某些库头文件中的非常特殊情况才应在内核内置的源代码中使用 std::

通用标头

这些头文件 API 在任何位置均可安全地使用,甚至在内核中也是如此。

它们在内核支持的标准 C 接口子集上包含 C++ 封装容器:

内核代码中不应使用来自这些头文件的 C 库 API 的 std 命名空间别名。

甚至在内核中也有一个纯 C++ 头文件:

保守型用户空间

这些标头 API 可在任何位置安全地使用。内核中不允许使用这类函数,因为它们都完全位于 std 命名空间中。但是,如果有在内核代码中使用此类 API 的合理情况,那么这些 API 的子集可能会获得内核内 API 别名。

这些是纯标题类型的类型和模板。它们不会自行进行任何动态分配。应通过说明清楚说明每个函数的时间和空间的复杂程度。

这涉及一些动态分配,但仅限于显式分配:

  • <any>
  • <memory>

    切勿使用 std::shared_ptrstd::weak_ptrstd::auto_ptr API。请改用 std::unique_ptrfbl::RefPtr

仅限用户空间

这些信息根本不会提供,也不会通过内核中的任何类似 API 或名称获得。但它们通常在用户空间中的各个位置都无害。它们不涉及动态分配。

厨房水槽

其中涉及到难以预测且通常无法控制的动态分配。确切的运行时行为和内存要求通常很难推断。在任何关键路径中使用这些接口以确保可靠性或性能,或者将其用于任何精简且节省空间的组件之前,请深思熟虑。

FBL

FBL 是指在内核和用户空间之间共享的 Fuchsia 基础库。 因此,FBL 具有非常严格的依赖关系。例如,FBL 不能依赖于系统调用接口,因为内核中不提供该系统调用接口。同样,FBL 不能依赖于内核中不可用的 C 库功能。

  1. system/ulib/fbl 可从内核和用户空间使用。
  2. kernel/lib/fbl:只能通过内核使用。

FBL 提供:

FBL 对内存分配有严格的控制。应该明确地分配内存,并使用 AllocChecker 让客户端从分配失败中恢复。在某些情况下,允许隐式内存分配,但隐式分配内存的函数必须 #ifdef'ed 才能在内核中不可用。

FBL 在平台源代码树之外不可用。

南非

ZX 包含 Zircon 对象系统调用的 C++ 封装容器。这些封装容器可为句柄提供类型安全和移动语义,但除了 syscalls.abigen 中的内容之外,还提供其他意见。在未来的某个时间点,我们可能会通过 syscalls.abigen 自动生成 ZX,这与我们在其他语言中自动生成系统调用封装容器的方式类似。

ZX 是 Fuchsia SDK 的一部分。

土耳其里拉

FZL 是 Fuchsia Zircon 图书馆。此库为涉及内核对象的常见操作提供了增值服务,并且可免费就如何与 Zircon 系统调用进行交互。如果一段代码不依赖于 Zircon 系统调用,则代码应改为采用 FBL。

FZL 在平台源代码树之外不可用。

封闭 C++

我们鼓励使用 C++ 而非 C 作为整个紫红色的实现语言。不过,在许多情况下,我们需要较窄的 ABI 瓶颈,以简化预防、跟踪或适应 ABI 偏移的问题。使 ABI 保持简洁的第一个关键方法是使其基于纯 C API(可以直接在 C++ 中使用,也可以通过许多其他语言中的外函数接口使用),而不是 C++ API。如果我们将一段代码关联到一个具有纯 C 外部 API 和 ABI 的模块,但在内部使用 C++ 实现其实现,我们将这种结构称为“封闭 C++”。

  • 内核本身可以说是在封闭的 C++ 中实现的。
  • vDSO 是在封闭 C++ 中实现的共享库。
  • Fuchsia 的标准 C 库虽然主要是在 C 中实现,但也在其实现中使用封闭的 C++。
  • 大多数 Fuchsia 设备驱动程序都是在封闭的 C++ 中实现的。

对于在 Fuchsia 公共 SDK 中导出的二进制文件,这条硬性要求规定了共享库必须具有纯 C API 和 ABI。此类库可以并且应该在其实现中使用 C++ 而非 C,并且可以将其他静态链接的库与 C++ API 搭配使用,前提是这些内部 C++ API 的 ABI 方面不会泄露到共享库的公共 ABI 中。

“可加载模块”(有时称为“插件”模块)与共享库非常相似。有关纯 C ABI 瓶颈的规则适用于可加载模块 ABI。Fuchsia 设备驱动程序就是此类可加载模块,必须满足驱动程序(纯 C) ABI。因此,在 C++ 中实现的每个驱动程序都必须使用封闭的 C++。

Fuchsia C++ 工具链使用 libc++ 实现提供完整的 C++17 标准库。在 C++ 可执行文件(以及具有 C++ ABI 的共享库)中,这通常是动态链接的,而且这是编译器的默认行为。该工具链还提供 libc++,以实现通过 -static-libstdc++ 开关到编译器 (clang++) 的封闭静态链接。在 Zircon GN 构建系统中,executable()test()library()(带有 shared = true)等链接目标会使用下面这行代码来请求封闭的 C++ 标准库:

    configs += [ "//zircon/public/gn/config:static-libc++" ]

在通过 sdk = "shared" 以二进制形式导出到公开 IDK 的每个 library() 中,必须提供此信息。

每个 driver() 都会自动使用封闭的 C++,因此不需要此行。(驱动程序不能依赖于自己的共享库,只能依赖于驱动程序 ABI 提供的动态链接环境。)

对于可执行文件和未导出的共享库,需要对标准 C++ 库使用静态链接还是动态链接进行判断。在 Fuchsia 的软件包部署模型中,与许多其他系统一样,使用共享库没有特定的可更新性改进。主要权衡因素是在通过许多存储软件包节省内存和存储空间,以及使用与单个软件包完全相同的共享库二进制文件和紧凑性(有时是性能)在系统上运行进程。由于系统 build 中的许多软件包都将使用相同的共享 libc++ 库,因此除非有特殊情况,否则通常都是正确的做法。它是编译器和构建系统中的默认值。