How to write Rust in the kernel: part 1
The Linux kernel is seeing a steady accumulation of Rust code. As it becomes more prevalent, maintainers may want to know how to read, review, and test the Rust code that relates to their areas of expertise. Just as kernel C code is different from user-space C code, so too is kernel Rust code somewhat different from user-space Rust code. That fact makes Rust's extensive documentation of less use than it otherwise would be, and means that potential contributors with user-space experience will need some additional instruction. This article is the first in a multi-part series aimed at helping existing kernel contributors become familiar with Rust, and helping existing Rust programmers become familiar with what the kernel does differently from the typical Rust project.
Linux 内核中正在逐步积累越来越多的 Rust 代码。随着这一趋势的持续发展,维护者可能希望了解如何阅读、审查和测试与其专业领域相关的 Rust 代码。正如内核中的 C 代码与用户空间中的 C 代码不同,内核中的 Rust 代码也与用户空间中的 Rust 代码存在一定差异。这一事实导致 Rust 丰富的文档资料在内核开发中的适用性受限,同时也意味着具备用户空间经验的潜在贡献者仍需额外的学习指导。本文是一个多篇系列的第一篇,旨在帮助已有的内核贡献者熟悉 Rust,也帮助现有的 Rust 程序员理解内核在使用 Rust 时与常规项目之间的不同。
In order to lay the groundwork for the rest of the articles in this series, this first article gives a high-level overview of installing and configuring Rust tooling, as well as an explanation of how Rust fits into the kernel's existing build system. Future articles will cover how Rust fits into the kernel's maintainership model, what goes into writing a driver in Rust, the design of the Rust interfaces to the rest of the kernel, and hints about specific things to look for when reviewing Rust code.
为了为本系列的其余文章打好基础,本文将首先概述如何安装和配置 Rust 开发工具链,并解释 Rust 如何融入内核现有的构建系统。后续文章将介绍 Rust 如何融入内核的维护模型、如何用 Rust 编写驱动、Rust 与内核其他部分的接口设计,以及在审查 Rust 代码时需要注意的具体细节。
Prerequisites
While support for Rust on GCC is catching up, and the rustc_codegen_gcc code generation backend is now capable of compiling the Rust components of the kernel, the Rust for Linux project currently only supports building with plain rustc. Since rustc uses LLVM, the project also recommends building the kernel as a whole with Clang while working on Rust code (although mixing GCC on the C side and LLVM on the Rust side does work). The build also requires bindgen to build the C/Rust API bindings, and a copy of the Rust standard library so that it can be built with the flags that the kernel requires. Building the kernel in the recommended way therefore requires Clang, lld, LLVM, the Rust compiler, the source form of the Rust standard library, and bindgen, at a minimum.
先决条件
尽管对 GCC 的 Rust 支持正在逐步完善,并且 rustc_codegen_gcc 后端已经能够编译内核中的 Rust 组件,但 Rust for Linux 项目当前仍仅支持使用原生 rustc 进行构建。由于 rustc 基于 LLVM,该项目也建议在开发 Rust 代码时使用 Clang 构建整个内核(尽管将 C 部分用 GCC、Rust 部分用 LLVM 混合编译也是可行的)。构建过程还需要 bindgen 来生成 C 与 Rust 的 API 绑定,以及 Rust 标准库的源码,以便使用内核所需的编译选项进行构建。因此,推荐的构建方式至少需要安装 Clang、lld、LLVM、Rust 编译器、Rust 标准库源码,以及 bindgen。
Many Linux distributions package sufficiently current versions of all of these; the Rust quick start documentation gives distribution-specific installation instructions. The minimum version of rustc required is 1.78.0, released in May 2024. The Rust for Linux project has committed to not raising the minimum required version unnecessarily. According to Miguel Ojeda, the current informal plan is to stick with the version included in Debian stable, once that catches up with the current minimum (likely later this year).
许多 Linux 发行版都已经打包了上述工具的足够新版本;Rust 的快速入门文档提供了按发行版分类的安装指导。目前最低要求的 rustc 版本为 1.78.0(发布于 2024 年 5 月)。Rust for Linux 项目承诺不会无故提升最低版本要求。根据 Miguel Ojeda 的说法,目前非正式的计划是,一旦 Debian stable 提供了当前所需的最低版本(很可能在今年稍晚),项目将跟随其版本。
Developers working on Rust should probably also install Clippy (Rust's linter), rustdoc (Rust's documentation building tool), and rust-analyzer (the Rust language server), but these are not strictly required. The Rust for Linux project tries to keep the code free of linter warnings, so patches that introduce new warnings may be frowned upon by maintainers. Invoking make rustavailable in the root of the kernel source will check that the necessary tools have compatible versions installed. Indeed, all the commands discussed here should be run from the root of the repository. The make rust-analyzer command will set up configuration files for rust-analyzer that should allow it to work seamlessly with an editor that has language server support, such as Emacs or Vim.
参与 Rust 开发的人员还应安装 Clippy(Rust 的 linter 工具)、rustdoc(文档生成工具)和 rust-analyzer(语言服务器),尽管这些工具并非严格必需。Rust for Linux 项目力图保持代码不产生 linter 警告,因此引入新警告的补丁可能不会受到维护者的欢迎。在内核源码根目录下执行 make rustavailable 可检查所需工具是否安装且版本兼容。实际上,这里提到的所有命令都应在内核仓库根目录下执行。 make rust-analyzer 命令会为 rust-analyzer 设置配置文件,使其可以在支持语言服务器的编辑器(如 Emacs 或 Vim)中无缝工作。
Rust code is controlled by two separate kernel configuration values. CONFIG_RUST_IS_AVAILABLE is automatically set when compatible tooling is available; CONFIG_RUST (available under "General Setup → Rust support") controls whether any Rust code is actually built, and depends on the first option. Unlike the vast majority of user-space Rust projects, the kernel does not use Cargo, Rust's package manager and build tool. Instead, the kernel's makefiles directly invoke the Rust compiler in the same way they would a C compiler. So adding an object to the correct make target is all that is needed to build a Rust module:
obj-$(CONFIG_RUST) += object_name.o
Rust 代码由两个不同的内核配置选项控制。当系统检测到可兼容的工具链时,会自动设置 CONFIG_RUST_IS_AVAILABLE;而是否实际构建 Rust 代码,则由 CONFIG_RUST 控制(可在 “General Setup → Rust support” 中启用),它依赖于前一个选项。与绝大多数用户空间的 Rust 项目不同,内核并不使用 Rust 的包管理与构建工具 Cargo,而是通过内核的 Makefile 直接调用 Rust 编译器,方式与调用 C 编译器相同。因此,只需将目标对象添加到相应的 Makefile 目标中,即可构建 Rust 模块:
obj-$(CONFIG_RUST) += object_name.o
The code directly enabled by CONFIG_RUST is largely the support code and bindings between C and Rust, and is therefore not a representative sample of what most Rust driver code actually looks like. Enabling the Rust sample code (under "Kernel hacking → Sample kernel code → Rust samples") may provide a more representative sample.
被 CONFIG_RUST 启用的代码主要是 C 与 Rust 之间的支持代码和绑定接口,因此并不能代表大多数 Rust 驱动代码的样貌。启用 Rust 示例代码(位于 “Kernel hacking → Sample kernel code → Rust samples”)可能更能反映实际驱动代码的风格。
Testing
Rust's testing and linting tools have also been integrated into the kernel's existing build system. To run Clippy, add CLIPPY=1 to the make invocation; this performs a special build of the kernel with debugging options enabled that make it unsuitable for production use, and so should be done with care. make rustdoc will build a local copy of the Rust documentation, which also checks for some documentation warnings, such as missing documentation comments or malformed intra-documentation links. The tests can be run with kunit.py, the kernel's white-box unit-testing tool. The tool does need additional arguments to set the necessary configuration variables for a Rust build:
./tools/testing/kunit/kunit.py run --make_options LLVM=1 \
--kconfig_add CONFIG_RUST=y --arch=<host architecture>
测试
Rust 的测试和静态分析工具也已集成到内核的现有构建系统中。要 Clippy,只需在 make 命令中添加 CLIPPY=1;该命令将进行一次带有调试选项的特殊构建,因此不适合用于生产环境,需谨慎操作。执行 make rustdoc 将构建一份本地 Rust 文档副本,同时检查文档警告,例如缺失的注释或格式错误的文档链接。可以使用内核的白盒单元测试工具 kunit.py 测试。该工具需要额外的参数来设置 Rust 构建所需的配置变量:
./tools/testing/kunit/kunit.py run --make_options LLVM=1 \
--kconfig_add CONFIG_RUST=y --arch=<host architecture>
Actually locating a failing test case could trip up people familiar with KUnit tests, though. Unlike the kernel's C code, which typically has KUnit tests written in separate files, Rust code tends to have tests in the same file as the code that it is testing. The convention is to use a separate Rust module to keep the test code out of the main namespace (and enable conditional compilation, so it's not included in release kernels). This module is often (imaginatively) called "test", and must be annotated with the #[kunit_tests(test_name)] macro. That macro is implemented in rust/macros/kunit.rs; it looks through the annotated module for functions marked with #[test] and sets up the needed C declarations for KUnit to automatically recognize the test cases.
不过,找到失败的测试用例可能会让熟悉 KUnit 测试的人感到困惑。与通常将 KUnit 测试写在单独文件中的 C 代码不同,Rust 通常会将测试代码与被测试代码写在同一文件中。约定做法是使用一个独立的 Rust 模块将测试代码从主命名空间中隔离开(并支持条件编译,使其不会出现在发布版内核中)。这个模块通常会被命名为 “test”,并需要使用 #[kunit_tests(test_name)] 宏进行标注。该宏定义在 rust/macros/kunit.rs 中,会扫描所标注的模块中带有 #[test] 的函数,并生成必要的 C 声明,使 KUnit 能自动识别测试用例。
Rust does have another kind of test that doesn't correspond directly to a C unit test, however. A "doctest" is a test embedded in the documentation of a function, typically showing how the function can be used. Because it is a real test, a doctest can be relied upon to remain current in a way that a mere example may not. Additionally, doctests are rendered as part of the automatically generated Rust API documentation. Doctests run as part of the KUnit test suite as well, but must be specifically enabled (under "Kernel hacking → Rust hacking → Doctests for the kernel crate").
Rust 还有一种不直接对应 C 单元测试的测试方式,称为 “doctest”(文档测试),它嵌入在函数的文档注释中,通常用于展示函数的使用方法。由于它是真实可执行的测试,因此比仅作为示例代码更能保证与当前实现保持同步。此外,doctest 也会被渲染为自动生成的 Rust API 文档的一部分。doctest 也是 KUnit 测试套件的一部分,但需要在配置中显式启用(路径为 “Kernel hacking → Rust hacking → Doctests for the kernel crate”)。
An example of a function with a doctest (lightly reformatted from the Rust string helper functions) looks like this:
下面是一个带有 doctest 的函数示例(源自 Rust 的字符串工具函数,并略作格式整理):
/// Strip a prefix from `self`. Delegates to [`slice::strip_prefix`].
///
/// # Examples
///
/// ```
/// # use kernel::b_str;
/// assert_eq!(
/// Some(b_str!("bar")),
/// b_str!("foobar").strip_prefix(b_str!("foo"))
/// );
/// assert_eq!(
/// None,
/// b_str!("foobar").strip_prefix(b_str!("bar"))
/// );
/// assert_eq!(
/// Some(b_str!("foobar")),
/// b_str!("foobar").strip_prefix(b_str!(""))
/// );
/// assert_eq!(
/// Some(b_str!("")),
/// b_str!("foobar").strip_prefix(b_str!("foobar"))
/// );
/// ```
pub fn strip_prefix(&self, pattern: impl AsRef<Self>) -> Option<&BStr> {
self.deref()
.strip_prefix(pattern.as_ref().deref())
.map(Self::from_bytes)
}

Normal comments in Rust code begin with //. Documentation comments, which are processed by various tools, start with /// (to comment on the following item) or //! (to comment on the containing item). These are equivalent:
Rust 中的普通注释以 // 开头,而文档注释(可被工具处理)以 ///(用于注释紧随其后的项)或 //!(用于注释包含该项的模块或结构)开头。以下两种写法是等价的:
/// Documentation
struct Name {
...
}
struct Name {
//! Documentation
...
}
Documentation comments are analogous to the specially formatted /** comments used in the kernel's C code. In this doctest, the assert_eq!() macro (an example of the other kind of macro invocation in Rust) is used to compare the return value of the .strip_prefix() method to what it should be.
文档注释与内核 C 代码中使用的特殊格式注释 /** ... */ 类似。在上面的 doctest 中,使用了 assert_eq!() 宏(这是 Rust 中另一类宏调用的例子)来比较 .strip_prefix() 方法的返回值是否符合预期。
Quick reference
Check Rust tools are installed
make rustavailable
Build kernel with Rust enabled
(After customizing .config)
make LLVM=1
Run tests
./tools/testing/kunit/kunit.py \
run \
--make_options LLVM=1 \
--kconfig_add CONFIG_RUST=y \
--arch=
Run linter
make LLVM=1 CLIPPY=1
Check documentation
make rustdoc
Format code
make rustfmt
速查手册
检查是否已安装 Rust 工具
make rustavailable
构建启用 Rust 的内核
(需先配置 .config)
make LLVM=1
测试
./tools/testing/kunit/kunit.py \
run \
--make_options LLVM=1 \
--kconfig_add CONFIG_RUST=y \
--arch=<主机架构>
linter
make LLVM=1 CLIPPY=1
检查文档
make rustdoc
格式化代码
make rustfmt
Finally, Rust code can also include kernel selftests, the kernel's third way to write tests. These need to be configured on an individual basis, using the kernel-configuration snippets in the tools/testing/selftests/rust directory. Kselftests are intended to be run on a machine booted with the corresponding kernel, and can be run with make TARGETS="rust" kselftest.
此外,Rust 代码也可以包含内核自测试(selftests),这是一种在内核中编写测试的第三种方式。这些测试需要单独配置,使用位于 tools/testing/selftests/rust 目录下的配置片段进行设置。Kselftests 设计为在目标内核的机器上执行,可通过以下命令:
make TARGETS="rust" kselftest
Formatting
Rust's syntax is complex. This has been one of several sticking points in adoption of the language, since people often feel that it makes the language difficult to read. That problem cannot wholly be solved with formatting tools, but they do help. Rust's canonical formatting tool is called rustfmt, and if it is installed, it can be run with make rustfmt to reformat all the Rust code in the kernel.
代码格式化
Rust 的语法相对复杂,这是该语言推广过程中的一个障碍,因为不少人认为这使得代码可读性降低。虽然格式化工具无法彻底解决这个问题,但它们确实有所帮助。Rust 官方的格式化工具是 rustfmt,如果已安装,可通过执行 make rustfmt 命令对内核中的所有 Rust 代码重新格式化。
Next
Building and testing Rust code is necessary, but not sufficient, to review Rust code. It may be enough to get one started experimenting with the existing Rust code in the kernel, however. Next up, we will do an in-depth comparison between a simple driver module and its Rust equivalent, as an introduction to the kernel's Rust driver abstractions.
接下来
构建和测试 Rust 代码是审查代码的前提,但还远远不够。不过,这些工作足以帮助开发者开始探索内核中已有的 Rust 代码。在下一篇文章中,我们将深入对比一个简单的驱动模块及其 Rust 实现,作为对内核 Rust 驱动抽象的入门介绍。

