A subset of the C++17 language is used in the Zircon tree. This includes both the kernel and userspace code. C++ is mixed with C (and some assembly) in both places. Some C++ language features are avoided or prohibited. Use of the C++ standard library features is very circumspect.
thread_local in kernel code
TODO: pointer to style guide(s)?
Zircon code is built with
-std=c++17 and in general can use C++ 17 language and library features freely (subject to style/feature constraints described above and library use guidelines described below). There is no general concern with staying compatible with C++ 14 or earlier versions. When a standard C++ 17 feature is the cleanest way to do something, do it that way.
However any library that is published to the IDK must be compatible with IDK users building in both C++ 14 and C++ 17 modes. So, any libraries exported to the IDK must have public header files that are compatible with both
-std=c++17. If a library is exported to the IDK as source code rather than as a binary, then its source code must also be completely compatible with both
-std=c++17 (and not require other special options). TODO(mcgrathr): pointer to build-system docs about maintaining code to be exported to IDK
All pure C code (
.c source files and headers used by them) is C 11. Some special exceptions are made for code meant to be reused by out-of-tree boot loaders, which stick to a conservative C 89 subset for embedded code.
The C++ standard library API has many interfaces of widely varying characteristics. We subdivide the standard library API into several categories below, based on the predictability and complexity of each particular interface's code generation and use of machine and OS facilities. These can be thought of as widening concentric circles of the API from the most minimal C-like subset out to the full C++ 17 API.
This section gives guidelines for how to think about the impact of using a particular standard C++ library API on the system as a whole. There are no hard and fast rules, except for the kernel (see the next section)--and except for implementation constraints, which one always hopes should be temporary.
The overwhelming rule is be circumspect.
Consider how well you understand the time and space complexity, the dynamic allocation behavior (if any), and the failure modes of each API you use.
Then consider the specific context where it's being used, and how sensitive that context is to those various kinds of concerns.
Be especially wary about input-dependent behavior that can quickly become far harder to predict when using nontrivial library facilities.
If you‘re writing the main I/O logic in a driver, or anything that’s in a hot path for latency, throughput, or reliability, in any kind of system service, then you should be pretty conservative in what library facilities you rely on. They‘re all technically available to you in userspace (though far fewer in the kernel; see the next section). But there’s not so many you actually should use. You probably don't want to lean on a lot of
std containers that do fancy dynamic allocation behind the scenes. They will make it hard for you to understand, predict, and control the storage/memory footprint, allocation behavior, performance, and reliability of your service.
Nonetheless, even a driver is a userspace program that starts up and parses configuration files or arguments and so on. For all those nonessential or start-time functions that are not part of the hot path, using more complex library facilities is probably fine when that makes the work easier. Just remember to pay attention to overall metrics for your code, such as minimal/total/peak runtime memory use, code bloat (which uses both device storage and runtime memory), and resilience to unexpected failure modes. Maybe don't double the code size and memory footprint of your driver just to leverage that fancy configuration-parsing library.
std namespace cannot be used in kernel code, which also includes bootloader. The few C++ standard library headers that don't involve
std:: APIs can still be used directly. See the next section.
No other C++ standard headers should be used in kernel code. Instead, any library facilities worthwhile to have in the kernel (such as
std::move) are provided via kernel-specific APIs (such as
ktl::move). The kernel's implementations of these APIs may in fact rely on toolchain headers providing
std:: implementations that are aliased to kernel API names. But only those API implementations and very special cases in certain library headers should ever use
std:: in source code built into the kernel.
These header APIs are safe to use everywhere, even in the kernel.
They include the C++ wrappers on the subset of standard C interfaces that the kernel supports:
std namespace aliases for C library APIs from these headers should not be used in kernel code.
One pure C++ header is also available even in the kernel:
The vanilla non-placement
operator new and
operator new are not available in the kernel. Use
These header APIs are safe to use everywhere. They‘re not allowed in the kernel because they’re all entirely in the
std namespace. But subsets of these APIs are likely candidates to get an in-kernel API alias if there is a good case for using such an API in kernel code.
These are pure header-only types and templates. They don't do any dynamic allocation of their own. The time and space complexity of each function should be clear from its description.
These involve some dynamic allocation, but only what's explicit:
std::auto_ptr APIs should never be used. Use
These are not things that would ever be available at all or by any similar API or name in the kernel. But they are generally harmless everywhere in userspace. They do not involve dynamic allocation.
Floating-point is never available in kernel code, but can be used (subject to performance considerations) in all userspace code.
Full C 11 standard library, via C++ wrappers or in standard C
Synchronization and threads. These standard APIs are safe to use in all userspace code with appropriate discretion. But it may often be better to use Zircon's own library APIs for similar things, such as <lib/sync/...>.
These involve dynamic allocation that is hard to predict and is generally out of your control. The exact runtime behavior and memory requirements are often hard to reason about. Think very hard before using these interfaces in any critical path for reliability or performance or in any component that is meant to be lean and space-efficient.
The entire Containers library
<lib/fit/function.h> for a homegrown alternative.
FBL is the Fuchsia Base Library, which is shared between kernel and userspace. As a result, FBL has very strict dependencies. For example, FBL cannot depend on the syscall interface because the syscall interface is not available within the kernel. Similarly, FBL cannot depend on C library features that are not available in the kernel.
Note: Some FBL interfaces below that overlap with standard C++ library interfaces will probably be either removed entirely or made kernel-only (and perhaps renamed inside the kernel) once userspace code has migrated to using standard C++ library facilities where appropriate.
FBL has strict controls on memory allocation. Memory allocation should be explicit, using an AllocChecker to let clients recover from allocation failures. In some cases, implicit memory allocation is permitted, but functions that implicitly allocate memory must be #ifdef'ed to be unavailable in the kernel.
FBL not available outside the Platform Source Tree.
ZX contains C++ wrappers for the Zircon objects and syscalls. These wrappers provide type safety and move semantics for handles but offer no opinion beyond what's in syscalls.abigen. At some point in the future, we might autogenerate ZX from syscalls.abigen, similar to how we autogenerate the syscall wrappers in other languages.
ZX is part of the Fuchsia SDK.
FZL is the Fuchsia Zircon Library. This library provides value-add for common operations involving kernel objects and is free to have opinions about how to interact with the Zircon syscalls. If a piece of code has no dependency on Zircon syscalls, the code should go in FBL instead.
FZL not available outside the Platform Source Tree.
We encourage using C++ rather than C as the implementation language throughout Fuchsia. However, in many instances we require a narrow ABI bottleneck to simplify the problem of preventing, tracking, or adapting to ABI drift. The first key way to keep the ABI simple is to base it on a pure C API (which can be used directly from C++, and via foreign-function interfaces from many other languages) rather than a C++ API. When we link together a body of code into a module with a pure C external API and ABI but using C++ internally for its implementation, we call that hermetic C++.
It‘s a hard and fast rule for binaries exported in the Fuchsia’s public SDK that shared libraries must have a pure C API and ABI. Such libraries can and should use C++ rather than C in their implementations, and they can use other statically-linked libraries with C++ APIs as long as ABI aspects of those internal C++ APIs don‘t leak out into the shared library’s public ABI.
A “loadable module” (sometimes called a “plug-in” module) is very similar to a shared library. The same rules about pure a C ABI bottleneck apply for loadable module ABIs. Fuchsia device drivers are just such loadable modules that must meet the driver (pure C) ABI. Hence, every driver implemented in C++ must use hermetic C++.
The Fuchsia C++ toolchain provides the full C++17 standard library using the libc++ implementation. In C++ executables (and shared libraries with a C++ ABI) this is usually dynamically linked, and that's the default behavior of the compiler. The toolchain also provides
libc++ for hermetic static linking via the
-static-libstdc++ switch to the compiler (
clang++). In the Zircon GN build system, a linking target such as
shared = true), uses this line to request the hermetic C++ standard library:
configs += [ "//zircon/public/gn/config:static-libc++" ]
This is required in each
library() that is exported to the public IDK in binary form via
sdk = "shared".
driver() automatically uses hermetic C++ and so this line is not required for them. (Drivers cannot depend on their own shared libraries, only the dynamic linking environment provided by the driver ABI.)
For executables and non-exported shared libraries, it‘s a judgment call whether to use static linking or dynamic linking for the standard C++ library. In Fuchsia’s package deployment model, there is no particular updatability improvement to using shared libraries as in many other systems. The primary trade-off is between the savings in memory and storage from many stored packages and running processes on the system using exactly the same shared library binary and compactness and (sometimes performance) of the individual package. Since many packages in the system build will all use the same shared
libc++ library, that‘s usually the right thing to do unless there are special circumstances. It’s the default in the compiler and build system.