Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,28 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/rust/arch-support.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
架构支持
========
目前,Rust编译器(``rustc``)使用LLVM进行代码生成,这限制了可以支持的目标架构。此外,对
使用LLVM/Clang构建内核的支持也有所不同(请参见 Documentation/kbuild/llvm.rst )。这
种支持对于使用 ``libclang`` 的 ``bindgen`` 来说是必需的。
下面是目前可以工作的架构的一般总结。支持程度与 ``MAINTAINERS`` 文件中的``S`` 值相对应:
============= ================ ==============================================
架构 支持水平 限制因素
============= ================ ==============================================
``arm`` Maintained 仅 ARMv7 小端序。
``arm64`` Maintained 仅小端序。
``loongarch`` Maintained \-
``riscv`` Maintained 仅 ``riscv64``,且仅限 LLVM/Clang。
``um`` Maintained \-
``x86`` Maintained 仅 ``x86_64``。
============= ================ ==============================================
@@ -0,0 +1,438 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/rust/coding-guidelines.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
编码指南
========
本文档描述了如何在内核中编写Rust代码。
风格和格式化
------------
代码应该使用 ``rustfmt`` 进行格式化。这样一来,一个不时为内核做贡献的人就不需要再去学
习和记忆一个样式指南了。更重要的是,审阅者和维护者不需要再花时间指出风格问题,这样就可以
减少补丁落地所需的邮件往返。
.. note:: ``rustfmt`` 不检查注释和文档的约定。因此,这些仍然需要照顾到。
使用 ``rustfmt`` 的默认设置。这意味着遵循Rust的习惯性风格。例如,缩进时使用4个空格而
不是制表符。
在输入、保存或提交时告知编辑器/IDE进行格式化是很方便的。然而,如果因为某些原因需要在某
个时候重新格式化整个内核Rust的源代码,可以运行以下程序::
make LLVM=1 rustfmt
也可以检查所有的东西是否都是格式化的(否则就打印一个差异),例如对于一个CI,用::
make LLVM=1 rustfmtcheck
像内核其他部分的 ``clang-format`` 一样, ``rustfmt`` 在单个文件上工作,并且不需要
内核配置。有时,它甚至可以与破碎的代码一起工作。
导入
~~~~
``rustfmt`` 默认会以一种在合并和变基时容易产生冲突的方式格式化导入,因为在某些情况下
它会将多个条目合并到同一行。例如:
.. code-block:: rust
// Do not use this style.
use crate::{
example1,
example2::{example3, example4, example5},
example6, example7,
example8::example9,
};
相反,内核使用如下所示的垂直布局:
.. code-block:: rust
use crate::{
example1,
example2::{
example3,
example4,
example5, //
},
example6,
example7,
example8::example9, //
};
也就是说,每个条目占一行,只要列表中有多个条目就使用花括号。
末尾的空注释可以保留这种格式。不仅如此, ``rustfmt`` 在添加空注释后实际上会将导入重
新格式化为垂直布局。也就是说,可以通过对如下输入运行 ``rustfmt`` 来轻松地将原始示例
重新格式化为预期的风格:
.. code-block:: rust
// Do not use this style.
use crate::{
example1,
example2::{example3, example4, example5, //
},
example6, example7,
example8::example9, //
};
末尾的空注释适用于嵌套导入(如上所示)以及单条目导入——这有助于最小化补丁系列中的差
异:
.. code-block:: rust
use crate::{
example1, //
};
末尾的空注释可以放在花括号内的任何一行中,但建议放在最后一个条目上,因为这让人联想到其
他格式化工具中的末尾逗号。有时在补丁系列中由于列表的变更,避免多次移动注释可能更简单。
在某些情况下可能需要例外处理,即以上都不是硬性规则。也有一些代码尚未迁移到这种风格,但
请不要引入其他风格的代码。
最终目标是让 ``rustfmt`` 在稳定版本中自动支持这种格式化风格(或类似的风格),而无需
末尾的空注释。因此,在某个时候,目标是移除这些注释。
注释
----
“普通”注释(即以 ``//`` 开头,而不是 ``///`` 或 ``//!`` 开头的代码文档)的写法与文
档注释相同,使用Markdown语法,尽管它们不会被渲染。这提高了一致性,简化了规则,并允许在
这两种注释之间更容易地移动内容。比如说:
.. code-block:: rust
// `object` is ready to be handled now.
f(object);
此外,就像文档一样,注释在句子的开头要大写,并以句号结束(即使是单句)。这包括 ``// SAFETY:``,
``// TODO:`` 和其他“标记”的注释,例如:
.. code-block:: rust
// FIXME: The error should be handled properly.
注释不应该被用于文档的目的:注释是为了实现细节,而不是为了用户。即使源文件的读者既是API
的实现者又是用户,这种区分也是有用的。事实上,有时同时使用注释和文档是很有用的。例如,用
于 ``TODO`` 列表或对文档本身的注释。对于后一种情况,注释可以插在中间;也就是说,离要注
释的文档行更近。对于其他情况,注释会写在文档之后,例如:
.. code-block:: rust
/// Returns a new [`Foo`].
///
/// # Examples
///
// TODO: Find a better example.
/// ```
/// let foo = f(42);
/// ```
// FIXME: Use fallible approach.
pub fn f(x: i32) -> Foo {
// ...
}
这适用于公共和私有项目。这增加了与公共项目的一致性,允许在更改可见性时减少涉及的更改,
并允许我们将来也为私有项目生成文档。换句话说,如果为私有项目编写了文档,那么仍然应该使
用 ``///`` 。例如:
.. code-block:: rust
/// My private function.
// TODO: ...
fn f() {}
一种特殊的注释是 ``// SAFETY:`` 注释。这些注释必须出现在每个 ``unsafe`` 块之前,它们
解释了为什么该块内的代码是正确/健全的,即为什么它在任何情况下都不会触发未定义行为,例如:
.. code-block:: rust
// SAFETY: `p` is valid by the safety requirements.
unsafe { *p = 0; }
``// SAFETY:`` 注释不能与代码文档中的 ``# Safety`` 部分相混淆。 ``# Safety`` 部
分指定了(函数)调用者或(特性)实现者需要遵守的契约。
``// SAFETY:`` 注释显示了为什么一个(函数)调用者或(特性)实现者实际上尊重了
``# Safety`` 部分或语言参考中的前提条件。
代码文档
--------
Rust内核代码不像C内核代码那样被记录下来(即通过kernel-doc)。取而代之的是用于记录Rust
代码的常用系统:rustdoc工具,它使用Markdown(一种轻量级的标记语言)。
要学习Markdown,外面有很多指南。例如:
https://commonmark.org/help/
一个记录良好的Rust函数可能是这样的:
.. code-block:: rust
/// Returns the contained [`Some`] value, consuming the `self` value,
/// without checking that the value is not [`None`].
///
/// # Safety
///
/// Calling this method on [`None`] is *[undefined behavior]*.
///
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
///
/// # Examples
///
/// ```
/// let x = Some("air");
/// assert_eq!(unsafe { x.unwrap_unchecked() }, "air");
/// ```
pub unsafe fn unwrap_unchecked(self) -> T {
match self {
Some(val) => val,
// SAFETY: The safety contract must be upheld by the caller.
None => unsafe { hint::unreachable_unchecked() },
}
}
这个例子展示了一些 ``rustdoc`` 的特性和内核中遵循的一些惯例:
- 第一段必须是一个简单的句子,简要地描述被记录的项目的作用。进一步的解释必须放在额
外的段落中。
- 不安全的函数必须在 ``# Safety`` 部分记录其安全前提条件。
- 虽然这里没有显示,但如果一个函数可能会恐慌,那么必须在 ``# Panics`` 部分描述发
生这种情况的条件。
请注意,恐慌应该是非常少见的,只有在有充分理由的情况下才会使用。几乎在所有的情况下,
都应该使用一个可失败的方法,通常是返回一个 ``Result``。
- 如果提供使用实例对读者有帮助的话,必须写在一个叫做``# Examples``的部分。
- Rust项目(函数、类型、常量……)必须有适当的链接(``rustdoc`` 会自动创建一个
链接)。
- 任何 ``unsafe`` 的代码块都必须在前面加上一个 ``// SAFETY:`` 的注释,描述里面
的代码为什么是正确的。
虽然有时原因可能看起来微不足道,但写这些注释不仅是记录已经考虑到的问题的好方法,
最重要的是,它提供了一种知道没有额外隐含约束的方法。
要了解更多关于如何编写Rust和拓展功能的文档,请看看 ``rustdoc`` 这本书,网址是:
https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html
此外,内核支持通过在链接目标前添加 ``srctree/`` 来创建相对于源代码树的链接。例如:
.. code-block:: rust
//! C header: [`include/linux/printk.h`](srctree/include/linux/printk.h)
或者:
.. code-block:: rust
/// [`struct mutex`]: srctree/include/linux/mutex.h
C FFI 类型
----------
Rust 内核代码使用类型别名(如 ``c_int``)来引用 C 类型(如 ``int``),这些别名可
以直接从 ``kernel`` 预导入(prelude)中获取。请不要使用 ``core::ffi`` 中的别
名——它们可能无法映射到正确的类型。
这些别名通常应该直接通过其标识符引用,即作为单段路径。例如:
.. code-block:: rust
fn f(p: *const c_char) -> c_int {
// ...
}
命名
----
Rust内核代码遵循通常的Rust命名空间:
https://rust-lang.github.io/api-guidelines/naming.html
当现有的C语言概念(如宏、函数、对象......)被包装成Rust抽象时,应该使用尽可能接近C语
言的名称,以避免混淆,并在C语言和Rust语言之间来回切换时提高可读性。例如,C语言中的
``pr_info`` 这样的宏在Rust中的命名是一样的。
说到这里,应该调整大小写以遵循Rust的命名惯例,模块和类型引入的命名间隔不应该在项目名称
中重复。例如,在包装常量时,如:
.. code-block:: c
#define GPIO_LINE_DIRECTION_IN 0
#define GPIO_LINE_DIRECTION_OUT 1
在Rust中的等价物可能是这样的(忽略文档)。:
.. code-block:: rust
pub mod gpio {
pub enum LineDirection {
In = bindings::GPIO_LINE_DIRECTION_IN as _,
Out = bindings::GPIO_LINE_DIRECTION_OUT as _,
}
}
也就是说, ``GPIO_LINE_DIRECTION_IN`` 的等价物将被称为 ``gpio::LineDirection::In`` 。
特别是,它不应该被命名为 ``gpio::gpio_line_direction::GPIO_LINE_DIRECTION_IN`` 。
代码检查提示(Lints)
---------------------
在 Rust 中,可以在局部 ``allow`` 特定的警告(诊断信息、代码检查提示(lint)),
使编译器忽略给定函数、模块、代码块等中给定警告的实例。
这类似于 C 中的 ``#pragma GCC diagnostic push`` + ``ignored`` + ``pop``
[#]_:
.. code-block:: c
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
static void f(void) {}
#pragma GCC diagnostic pop
.. [#] 在这个特定情况下,可以使用内核的 ``__{always,maybe}_unused`` 属性
(C23 的 ``[[maybe_unused]]``);然而,此示例旨在反映下文讨论的 Rust 中
的等效代码检查提示。
但要简洁得多:
.. code-block:: rust
#[allow(dead_code)]
fn f() {}
凭借这一点,可以更方便地默认启用更多诊断(即在 ``W=`` 级别之外)。特别是那些可能有
一些误报但在其他方面非常有用的诊断,保持启用可以捕获潜在的错误。
在此基础上,Rust 提供了 ``expect`` 属性,更进一步。如果警告没有产生,它会让编译器
发出警告。例如,以下代码将确保当 ``f()`` 在某处被调用时,我们必须移除该属性:
.. code-block:: rust
#[expect(dead_code)]
fn f() {}
如果我们不这样做,编译器会发出警告::
warning: this lint expectation is unfulfilled
--> x.rs:3:10
|
3 | #[expect(dead_code)]
| ^^^^^^^^^
|
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
这意味着 ``expect`` 不会在不需要时被遗忘,这可能发生在以下几种情况中:
- 开发过程中添加的临时属性。
- 编译器、Clippy 或自定义工具中代码检查提示的改进可能消除误报。
- 当代码检查提示不再需要时,因为预期它会在某个时候被移除,例如上面的
``dead_code`` 示例。
这也增加了剩余 ``allow`` 的可见性,并减少了误用的可能性。
因此,优先使用 ``expect`` 而不是 ``allow``,除非:
- 条件编译在某些情况下触发警告,在其他情况下不触发。
如果与总的相比,只有少数情况触发(或不触发)警告,那么可以考虑使用条件
``expect``(即 ``cfg_attr(..., expect(...))``)。否则,使用 ``allow`` 可
能更简单。
- 在宏内部,不同的调用可能会创建在某些情况下触发警告而在其他情况下不触发的展开代码。
- 当代码可能在某些架构上触发警告但在其他架构上不触发时,例如到 C FFI 类型的 ``as``
转换。
作为一个更详细的示例,考虑以下程序:
.. code-block:: rust
fn g() {}
fn main() {
#[cfg(CONFIG_X)]
g();
}
这里,如果 ``CONFIG_X`` 未设置,函数 ``g()`` 是死代码。我们可以在这里使用
``expect`` 吗?
.. code-block:: rust
#[expect(dead_code)]
fn g() {}
fn main() {
#[cfg(CONFIG_X)]
g();
}
如果 ``CONFIG_X`` 被设置,这将产生代码检查提示,因为在该配置中它不是死代码。因
此,在这种情况下,我们不能直接使用 ``expect``。
一个简单的可能性是使用 ``allow``:
.. code-block:: rust
#[allow(dead_code)]
fn g() {}
fn main() {
#[cfg(CONFIG_X)]
g();
}
另一种方法是使用条件 ``expect``:
.. code-block:: rust
#[cfg_attr(not(CONFIG_X), expect(dead_code))]
fn g() {}
fn main() {
#[cfg(CONFIG_X)]
g();
}
这将确保如果有人在某处引入了对 ``g()`` 的另一个调用(例如无条件的),那么将会被发现
它不再是死代码。然而, ``cfg_attr`` 比简单的 ``allow`` 更复杂。
因此,当涉及多个配置或者代码检查提示可能由于非局部更改(如 ``dead_code``)而触发
时,使用条件 ``expect`` 可能不值得。
有关 Rust 中诊断的更多信息,请参阅:
https://doc.rust-lang.org/stable/reference/attributes/diagnostics.html
错误处理
--------
有关 Rust for Linux 特定错误处理的背景和指南,请参阅:
https://rust.docs.kernel.org/kernel/error/type.Result.html#error-codes-in-c-and-rust
@@ -0,0 +1,76 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/rust/general-information.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
基本信息
========
本文档包含了在内核中使用Rust支持时需要了解的有用信息。
.. _rust_code_documentation_zh_cn:
代码文档
--------
Rust内核代码使用其内置的文档生成器 ``rustdoc`` 进行记录。
生成的HTML文档包括集成搜索、链接项(如类型、函数、常量)、源代码等。它们可以在以下地址阅读
(TODO:当在主线中时链接,与其他文档一起生成):
http://kernel.org/
这些文档也可以很容易地在本地生成和阅读。这相当快(与编译代码本身的顺序相同),而且不需要特
殊的工具或环境。这有一个额外的好处,那就是它们将根据所使用的特定内核配置进行定制。要生成它
们,请使用 ``rustdoc`` 目标,并使用编译时使用的相同调用,例如::
make LLVM=1 rustdoc
要在你的网络浏览器中本地阅读该文档,请运行如::
xdg-open Documentation/output/rust/rustdoc/kernel/index.html
要了解如何编写文档,请看 coding-guidelines.rst 。
额外的lints
-----------
虽然 ``rustc`` 是一个非常有用的编译器,但一些额外的lints和分析可以通过 ``clippy``
(一个Rust linter)来实现。要启用它,请将CLIPPY=1传递到用于编译的同一调用中,例如::
make LLVM=1 CLIPPY=1
请注意,Clippy可能会改变代码生成,因此在构建产品内核时不应该启用它。
抽象和绑定
----------
抽象是用Rust代码包装来自C端的内核功能。
为了使用来自C端的函数和类型,需要创建绑定。绑定是Rust对那些来自C端的函数和类型的声明。
例如,人们可以在Rust中写一个 ``Mutex`` 抽象,它从C端包装一个 ``Mutex结构体`` ,并
通过绑定调用其函数。
抽象并不能用于所有的内核内部API和概念,但随着时间的推移,我们打算扩大覆盖范围。“Leaf”
模块(例如,驱动程序)不应该直接使用C语言的绑定。相反,子系统应该根据需要提供尽可能安
全的抽象。
有条件的编译
------------
Rust代码可以访问基于内核配置的条件性编译:
.. code-block:: rust
#[cfg(CONFIG_X)] // Enabled (`y` or `m`)
#[cfg(CONFIG_X="y")] // Enabled as a built-in (`y`)
#[cfg(CONFIG_X="m")] // Enabled as a module (`m`)
#[cfg(not(CONFIG_X))] // Disabled
@@ -0,0 +1,42 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/rust/index.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
Rust
====
与内核中的Rust有关的文档。若要开始在内核中使用Rust,请阅读 quick-start.rst 指南。
代码文档
--------
给定一个内核配置,内核可能会生成 Rust 代码文档,即由 ``rustdoc`` 工具呈现的 HTML。
.. only:: rustdoc and html
该内核文档使用 `Rust 代码文档 <rustdoc/kernel/index.html>`_ 构建。
.. only:: not rustdoc and html
该内核文档不使用 Rust 代码文档构建。
预生成版本提供在:https://rust.docs.kernel.org。
请参阅 :ref:`代码文档 <rust_code_documentation_zh_cn>` 部分以获取更多详细信息。
.. toctree::
:maxdepth: 1
quick-start
general-information
coding-guidelines
arch-support
testing
你还可以在 :doc:`../../../process/kernel-docs` 中找到 Rust 的学习材料。
@@ -0,0 +1,335 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/rust/quick-start.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
快速入门
========
本文介绍了如何开始使用Rust进行内核开发。
安装内核开发所需的 Rust 工具链有几种方式。一种简单的方式是使用 Linux 发行版的软件包
(如果它们合适的话)——下面的第一节解释了这种方法。这种方法的一个优势是,通常发行版会
匹配 Rust 和 Clang 所使用的 LLVM。
另一种方式是使用 `kernel.org <https://kernel.org/pub/tools/llvm/rust/>`_ 上提
供的预构建稳定版本的 LLVM+Rust。这些与 :ref:`获取 LLVM <zh_cn_getting_llvm>` 中的精
简快速 LLVM 工具链相同,并添加了 Rust for Linux 支持的 Rust 版本。提供了两套工具
链:"最新 LLVM" 和 "匹配 LLVM"(请参阅链接了解更多信息)。
或者,接下来的两个 "依赖" 章节将解释每个组件以及如何通过 ``rustup``、Rust 的独立
安装程序或从源码构建来安装它们。
本文档的其余部分解释了有关如何入门的其他方面。
发行版
------
Arch Linux
**********
Arch Linux 提供较新的 Rust 版本,因此通常开箱即用,例如::
pacman -S rust rust-src rust-bindgen
Debian
******
Debian 13(Trixie)以及 Testing 和 Debian Unstable(Sid)提供较新的 Rust 版
本,因此通常开箱即用,例如::
apt install rustc rust-src bindgen rustfmt rust-clippy
Fedora Linux
************
Fedora Linux 提供较新的 Rust 版本,因此通常开箱即用,例如::
dnf install rust rust-src bindgen-cli rustfmt clippy
Gentoo Linux
************
Gentoo Linux(尤其是 testing 分支)提供较新的 Rust 版本,因此通常开箱即用,
例如::
USE='rust-src rustfmt clippy' emerge dev-lang/rust dev-util/bindgen
可能需要设置 ``LIBCLANG_PATH``。
Nix
***
Nix(unstable 频道)提供较新的 Rust 版本,因此通常开箱即用,例如::
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = with pkgs; [ rustc rust-bindgen rustfmt clippy ];
RUST_LIB_SRC = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}
openSUSE
********
openSUSE Slowroll 和 openSUSE Tumbleweed 提供较新的 Rust 版本,因此通常开箱
即用,例如::
zypper install rust rust1.79-src rust-bindgen clang
Ubuntu
******
25.04
~~~~~
最新的 Ubuntu 版本提供较新的 Rust 版本,因此通常开箱即用,例如::
apt install rustc rust-src bindgen rustfmt rust-clippy
此外,需要设置 ``RUST_LIB_SRC``,例如::
RUST_LIB_SRC=/usr/src/rustc-$(rustc --version | cut -d' ' -f2)/library
为方便起见,可以将 ``RUST_LIB_SRC`` 导出到全局环境中。
24.04 LTS 及更早版本
~~~~~~~~~~~~~~~~~~~~
虽然 Ubuntu 24.04 LTS 及更早版本仍然提供较新的 Rust 版本,但它们需要一些额外的配
置,使用带版本号的软件包,例如::
apt install rustc-1.80 rust-1.80-src bindgen-0.65 rustfmt-1.80 \
rust-1.80-clippy
ln -s /usr/lib/rust-1.80/bin/rustfmt /usr/bin/rustfmt-1.80
ln -s /usr/lib/rust-1.80/bin/clippy-driver /usr/bin/clippy-driver-1.80
这些软件包都不会将其工具设置为默认值;因此应该显式指定它们,例如::
make LLVM=1 RUSTC=rustc-1.80 RUSTDOC=rustdoc-1.80 RUSTFMT=rustfmt-1.80 \
CLIPPY_DRIVER=clippy-driver-1.80 BINDGEN=bindgen-0.65
或者,修改 ``PATH`` 变量将 Rust 1.80 的二进制文件放在前面,并将 ``bindgen`` 设
置为默认值,例如::
PATH=/usr/lib/rust-1.80/bin:$PATH
update-alternatives --install /usr/bin/bindgen bindgen \
/usr/bin/bindgen-0.65 100
update-alternatives --set bindgen /usr/bin/bindgen-0.65
使用带版本号的软件包时需要设置 ``RUST_LIB_SRC``,例如::
RUST_LIB_SRC=/usr/src/rustc-$(rustc-1.80 --version | cut -d' ' -f2)/library
为方便起见,可以将 ``RUST_LIB_SRC`` 导出到全局环境中。
此外, ``bindgen-0.65`` 在较新的版本(24.04 LTS 和 24.10)中可用,但在更早的版
本(20.04 LTS 和 22.04 LTS)中可能不可用,因此可能需要手动构建 ``bindgen``
(请参见下文)。
构建依赖
--------
本节描述了如何获取构建所需的工具。
为了方便检查是否满足要求,可以使用以下目标::
make LLVM=1 rustavailable
这会触发与Kconfig用来确定是否应该启用 ``RUST_IS_AVAILABLE`` 相同的逻辑;不过,如
果Kconfig认为不该启用,它会列出未满足的条件。
rustc
*****
需要一个较新版本的Rust编译器。
如果使用的是 ``rustup`` ,请进入内核编译目录(或者用 ``--path=<build-dir>`` 参数
来 ``设置`` sub-command),例如运行::
rustup override set stable
这将配置你的工作目录使用给定版本的 ``rustc``,而不影响你的默认工具链。
请注意覆盖应用当前的工作目录(和它的子目录)。
如果你使用 ``rustup``, 可以从下面的链接拉取一个单独的安装程序:
https://forge.rust-lang.org/infra/other-installation-methods.html#standalone
Rust标准库源代码
****************
Rust标准库的源代码是必需的,因为构建系统会交叉编译 ``core`` 。
如果正在使用 ``rustup`` ,请运行::
rustup component add rust-src
这些组件是按工具链安装的,因此以后升级Rust编译器版本需要重新添加组件。
否则,如果使用独立的安装程序,可以将Rust源码树下载到安装工具链的文件夹中::
curl -L "https://static.rust-lang.org/dist/rust-src-$(rustc --version | cut -d' ' -f2).tar.gz" |
tar -xzf - -C "$(rustc --print sysroot)/lib" \
"rust-src-$(rustc --version | cut -d' ' -f2)/rust-src/lib/" \
--strip-components=3
在这种情况下,以后升级Rust编译器版本需要手动更新这个源代码树(这可以通过移除
``$(rustc --print sysroot)/lib/rustlib/src/rust`` ,然后重新执行上
面的命令做到)。
libclang
********
``bindgen`` 使用 ``libclang`` (LLVM的一部分)来理解内核中的C代码,这意味着需要安
装LLVM;同在开启``LLVM=1`` 时编译内核一样。
Linux发行版中可能会有合适的包,所以最好先检查一下。
适用于部分系统和架构的二进制文件也可到以下网址下载:
https://releases.llvm.org/download.html
或者自行构建LLVM,这需要相当长的时间,但并不是一个复杂的过程:
https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm
请参阅 Documentation/kbuild/llvm.rst 了解更多信息,以及获取预构建版本和发行包
的进一步方法。
bindgen
*******
内核的C端绑定是在构建时使用 ``bindgen`` 工具生成的。
例如,通过以下方式安装它(注意,这将从源码下载并构建该工具)::
cargo install --locked bindgen-cli
``bindgen`` 使用 ``clang-sys`` crate 来查找合适的 ``libclang`` (可以静态链
接、动态链接或在运行时加载)。默认情况下,上面的 ``cargo`` 命令会生成一个在运行时
加载 ``libclang`` 的 ``bindgen`` 二进制文件。如果没有找到(或者应该使用与找到的
不同的 ``libclang``),可以调整该过程,例如使用 ``LIBCLANG_PATH`` 环境变量。详
情请参阅 ``clang-sys`` 的文档:
https://github.com/KyleMayes/clang-sys#linking
https://github.com/KyleMayes/clang-sys#environment-variables
开发依赖
--------
本节解释了如何获取开发所需的工具。也就是说,在构建内核时不需要这些工具。
rustfmt
*******
``rustfmt`` 工具被用来自动格式化所有的Rust内核代码,包括生成的C绑定(详情请见
coding-guidelines.rst )。
如果使用的是 ``rustup`` ,它的 ``默认`` 配置文件已经安装了这个工具,因此不需要做什么。
如果使用的是其他配置文件,可以手动安装该组件::
rustup component add rustfmt
独立的安装程序也带有 ``rustfmt`` 。
clippy
******
``clippy`` 是一个Rust linter。运行它可以为Rust代码提供额外的警告。它可以通过向 ``make``
传递 ``CLIPPY=1`` 来运行(关于细节,详见 general-information.rst )。
如果正在使用 ``rustup`` ,它的 ``默认`` 配置文件已经安装了这个工具,因此不需要做什么。
如果使用的是另一个配置文件,该组件可以被手动安装::
rustup component add clippy
独立的安装程序也带有 ``clippy`` 。
rustdoc
*******
``rustdoc`` 是Rust的文档工具。它为Rust代码生成漂亮的HTML文档(详情请见 general-information.rst )。
``rustdoc`` 也被用来测试文档化的Rust代码中提供的例子(称为doctests或文档测试)。
``rusttest`` 是本功能的Make目标。
如果使用的是 ``rustup`` ,所有的配置文件都已经安装了这个工具,因此不需要做什么。
独立的安装程序也带有 ``rustdoc`` 。
rust-analyzer
*************
`rust-analyzer <https://rust-analyzer.github.io/>`_ 语言服务器可以和许多编辑器
一起使用,以实现语法高亮、补全、转到定义和其他功能。
``rust-analyzer`` 需要一个配置文件, ``rust-project.json``, 它可以由 ``rust-analyzer``
Make 目标生成::
make LLVM=1 rust-analyzer
配置
----
Rust支持(CONFIG_RUST)需要在 ``General setup`` 菜单中启用。在其他要求得到满足的情
况下,该选项只有在找到合适的Rust工具链时才会显示(见上文)。相应的,这将使依赖Rust的其
他选项可见。
之后,进入::
Kernel hacking
-> Sample kernel code
-> Rust samples
并启用一些内置或可加载的样例模块。
构建
----
用完整的LLVM工具链构建内核是目前支持的最佳设置。即::
make LLVM=1
使用GCC对某些配置也是可行的,但目前它是非常试验性的。
折腾
----
要想深入了解,请看 ``samples/rust/`` 下的样例源代码、 ``rust/`` 下的Rust支持代码和
``Kernel hacking`` 下的 ``Rust hacking`` 菜单。
如果使用的是GDB/Binutils,而Rust符号没有被demangled,原因是工具链还不支持Rust的新v0
mangling方案。有几个办法可以解决:
- 安装一个较新的版本(GDB >= 10.2, Binutils >= 2.36)。
- 一些版本的GDB(例如vanilla GDB 10.1)能够使用嵌入在调试信息(``CONFIG_DEBUG_INFO``)
中的pre-demangled的名字。
@@ -0,0 +1,215 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/rust/testing.rst
:翻译:
郭杰 Ben Guo <benx.guo@gmail.com>
测试
====
本文介绍了如何在内核中测试 Rust 代码。
有三种测试类型:
- KUnit 测试
- ``#[test]`` 测试
- Kselftests
KUnit 测试
----------
这些测试来自 Rust 文档中的示例。它们会被转换为 KUnit 测试。
使用
****
这些测试可以通过 KUnit 运行。例如,在命令行中使用 ``kunit_tool`` ( ``kunit.py`` )::
./tools/testing/kunit/kunit.py run --make_options LLVM=1 --arch x86_64 --kconfig_add CONFIG_RUST=y
或者,KUnit 也可以在内核启动时以内置方式运行。获取更多 KUnit 信息,请参阅
Documentation/dev-tools/kunit/index.rst。
关于内核内置与命令行测试的详细信息,请参阅 Documentation/dev-tools/kunit/architecture.rst。
要使用这些 KUnit 文档测试,需要在内核配置中启用以下选项::
CONFIG_KUNIT
Kernel hacking -> Kernel Testing and Coverage -> KUnit - Enable support for unit tests
CONFIG_RUST_KERNEL_DOCTESTS
Kernel hacking -> Rust hacking -> Doctests for the `kernel` crate
KUnit 测试即文档测试
********************
文档测试( *doctests* )一般用于展示函数、结构体或模块等的使用方法。
它们非常方便,因为它们就写在文档旁边。例如:
.. code-block:: rust
/// 求和两个数字。
///
/// ```
/// assert_eq!(mymod::f(10, 20), 30);
/// ```
pub fn f(a: i32, b: i32) -> i32 {
a + b
}
在用户空间中,这些测试由 ``rustdoc`` 负责收集并运行。单独使用这个工具已经很有价值,
因为它可以验证示例能否成功编译(确保和代码保持同步),
同时还可以运行那些不依赖内核 API 的示例。
然而,在内核中,这些测试会转换成 KUnit 测试套件。
这意味着文档测试会被编译成 Rust 内核对象,从而可以在构建的内核环境中运行。
通过与 KUnit 集成,Rust 的文档测试可以复用内核现有的测试设施。
例如,内核日志会显示::
KTAP version 1
1..1
KTAP version 1
# Subtest: rust_doctests_kernel
1..59
# rust_doctest_kernel_build_assert_rs_0.location: rust/kernel/build_assert.rs:13
ok 1 rust_doctest_kernel_build_assert_rs_0
# rust_doctest_kernel_build_assert_rs_1.location: rust/kernel/build_assert.rs:56
ok 2 rust_doctest_kernel_build_assert_rs_1
# rust_doctest_kernel_init_rs_0.location: rust/kernel/init.rs:122
ok 3 rust_doctest_kernel_init_rs_0
...
# rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150
ok 59 rust_doctest_kernel_types_rs_2
# rust_doctests_kernel: pass:59 fail:0 skip:0 total:59
# Totals: pass:59 fail:0 skip:0 total:59
ok 1 rust_doctests_kernel
文档测试中,也可以正常使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符,例如:
.. code-block:: rust
/// ```
/// # use kernel::{spawn_work_item, workqueue};
/// spawn_work_item!(workqueue::system(), || pr_info!("x\n"))?;
/// # Ok::<(), Error>(())
/// ```
这些测试和普通代码一样,也可以在 ``CLIPPY=1`` 条件下通过 Clippy 进行编译,
因此可以从额外的 lint 检查中获益。
为了便于开发者定位文档测试出错的具体行号,日志会输出一条 KTAP 诊断信息。
其中标明了原始测试的文件和行号(不是 ``rustdoc`` 生成的临时 Rust 文件位置)::
# rust_doctest_kernel_types_rs_2.location: rust/kernel/types.rs:150
Rust 测试中常用的断言宏是来自 Rust 标准库( ``core`` )中的 ``assert!`` 和 ``assert_eq!`` 宏。
内核提供了一个定制版本,这些宏的调用会被转发到 KUnit。
和 KUnit 测试不同的是,这些宏不需要传递上下文参数( ``struct kunit *`` )。
这使得它们更易于使用,同时文档的读者无需关心底层用的是什么测试框架。
此外,这种方式未来也许可以让我们更容易测试第三方代码。
当前有一个限制:KUnit 不支持在其他任务中执行断言。
因此,如果断言真的失败了,我们只是简单地把错误打印到内核日志里。
另外,文档测试不适用于非公开的函数。
作为文档中的测试示例,应当像 “实际代码” 一样编写。
例如:不要使用 ``unwrap()`` 或 ``expect()``,请使用 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符。
更多背景信息,请参阅:
https://rust.docs.kernel.org/kernel/error/type.Result.html#error-codes-in-c-and-rust
``#[test]`` 测试
----------------
此外,还有 ``#[test]`` 测试。与文档测试类似,这些测试与用户空间中的测试方式也非常相近,并且同样会映射到 KUnit。
这些测试通过 ``kunit_tests`` 过程宏引入,该宏将测试套件的名称作为参数。
例如,假设想要测试前面文档测试示例中的函数 ``f``,我们可以在定义该函数的同一文件中编写:
.. code-block:: rust
#[kunit_tests(rust_kernel_mymod)]
mod tests {
use super::*;
#[test]
fn test_f() {
assert_eq!(f(10, 20), 30);
}
}
如果我们执行这段代码,内核日志会显示::
KTAP version 1
# Subtest: rust_kernel_mymod
# speed: normal
1..1
# test_f.speed: normal
ok 1 test_f
ok 1 rust_kernel_mymod
与文档测试类似, ``assert!`` 和 ``assert_eq!`` 宏被映射回 KUnit 并且不会发生 panic。
同样,支持 `? <https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator>`_ 运算符,
测试函数可以什么都不返回(单元类型 ``()``)或 ``Result`` (任何 ``Result<T, E>``)。例如:
.. code-block:: rust
#[kunit_tests(rust_kernel_mymod)]
mod tests {
use super::*;
#[test]
fn test_g() -> Result {
let x = g()?;
assert_eq!(x, 30);
Ok(())
}
}
如果我们运行测试并且调用 ``g`` 失败,那么内核日志会显示::
KTAP version 1
# Subtest: rust_kernel_mymod
# speed: normal
1..1
# test_g: ASSERTION FAILED at rust/kernel/lib.rs:335
Expected is_test_result_ok(test_g()) to be true, but is false
# test_g.speed: normal
not ok 1 test_g
not ok 1 rust_kernel_mymod
如果 ``#[test]`` 测试可以对用户起到示例作用,那就应该改用文档测试。
即使是 API 的边界情况,例如错误或边界问题,放在示例中展示也同样有价值。
``rusttest`` 宿主机测试
-----------------------
这类测试运行在用户空间,可以通过 ``rusttest`` 目标在构建内核的宿主机中编译并运行::
make LLVM=1 rusttest
当前操作需要内核 ``.config``。
目前,它们主要用于测试 ``macros`` crate 的示例。
Kselftests
----------
Kselftests 可以在 ``tools/testing/selftests/rust`` 文件夹中找到。
测试所需的内核配置选项列在 ``tools/testing/selftests/rust/config`` 文件中,
可以借助 ``merge_config.sh`` 脚本合并到现有配置中::
./scripts/kconfig/merge_config.sh .config tools/testing/selftests/rust/config
Kselftests 会在内核源码树中构建,以便在运行相同版本内核的系统上执行测试。
一旦安装并启动了与源码树匹配的内核,测试即可通过以下命令编译并执行::
make TARGETS="rust" kselftest
请参阅 Documentation/dev-tools/kselftest.rst 文档以获取更多信息。