Linux内核中的Rust使用

 Linux  Kernel  Rust 󰈭 2875字

内核代码选用Linux-6.3.4, 对其中有关Rust代码进行简单的分析.

Minimal Rust

rs
 1// SPDX-License-Identifier: GPL-2.0
 2
 3//! Rust minimal sample.
 4
 5use kernel::prelude::*;
 6
 7module! {
 8    type: RustMinimal,
 9    name: "rust_minimal",
10    author: "Rust for Linux Contributors",
11    description: "Rust minimal sample",
12    license: "GPL",
13}
14
15struct RustMinimal {
16    numbers: Vec<i32>,
17}
18
19impl kernel::Module for RustMinimal {
20    fn init(_module: &'static ThisModule) -> Result<Self> {
21        pr_info!("Rust minimal sample (init)\n");
22        pr_info!("Am I built-in? {}\n", !cfg!(MODULE));
23
24        let mut numbers = Vec::new();
25        numbers.try_push(72)?;
26        numbers.try_push(108)?;
27        numbers.try_push(200)?;
28
29        Ok(RustMinimal { numbers })
30    }
31}
32
33impl Drop for RustMinimal {
34    fn drop(&mut self) {
35        pr_info!("My numbers are {:?}\n", self.numbers);
36        pr_info!("Rust minimal sample (exit)\n");
37    }
38}

参考 A first look at Rust in the 6.1 kernel [LWN.net]:

  • 引入rust/kernel/prelude.rs定义的若干声明

  • 用 C 编写的内核模块包括许多对宏的调用,如 MODULE_DESCRIPTION()MODULE_LICENSE(),它们将模块的元数据存储在单独的 ELF 部分中。module_init ()module_exit()宏分别标识模块的构造函数和析构函数。Rust 将大部分样板文件等价的放入单个宏调用中module!

  • 使用RustMinimal包装Vec类型, 使得我们可以为其实现Module以及Drop等trait. 参考 孤儿规则与绕过方案:

    Rust的孤儿规则(Orphan Rule, OR)约束了当为某类型实现某trait时, 必须保证类型或特征至少有一个在当前作用域中定义. 这个原则可以保证别人用你库的时候不会破坏你库里函数的功能(同理, 你也不能破坏标准库函数的功能), 以此来保证一致性.

    如何绕过孤儿规则? 使用newtype模式, 即创建一个新的元组结构体来封装该类型.

    好消息是, 其在运行时没有任何性能损耗, 因为在编译期间, 该类型就会被自动忽略; 同时, 还可以通过重载warpper的一些方法来避免暴露底层的所有方法, 实现隐藏的目的. …

  • init ()函数应该执行通常的模块初始化工作.

    • 在这种情况下, 它会向系统日志发出一些信息(在该过程中显示可用于在编译时查询内核配置参数的cfg!()宏).

    • 然后它分配一个可变的Vec并尝试将三个数字放入其中. try_push()的使用在这里很重要: Vec会在必要时自行调整大小. 这涉及分配内存, 这在内核环境中可能会失败. 如果该分配失败, try_push() 将返回失败状态, 这反过来又会导致init() 返回失败

    • Rust中问号的使用:

    具体来说,当使用 ? 运算符时,Rust 会自动进行如下操作: 检查当前表达式的结果是否为错误类型(例如 Result 或 Option)。 如果是错误,则立即将错误传播到当前函数的调用者,并终止当前函数的执行。 如果是正常值,则将其解包并继续执行后续的代码。

    • 最后, 如果一切顺利, 它会返回一个带有分配的Vec和成功状态的RustMinimal结构. 由于此模块没有与任何其他内核子系统交互, 因此除了耐心等待被删除外, 它实际上不会做任何事情.
  • 由于Module特征只有init方法, 见rust/kernel/lib.rs:

rust
 1/// The top level entrypoint to implementing a kernel module.
 2///
 3/// For any teardown or cleanup operations, your type may implement [`Drop`].
 4pub trait Module: Sized + Sync {
 5    /// Called at module initialization time.
 6    ///
 7    /// Use this method to perform whatever setup or registration your module
 8    /// should do.
 9    ///
10    /// Equivalent to the `module_init` macro in the C API.
11    fn init(module: &'static ThisModule) -> error::Result<Self>;
12}

因而使用了Drop特征作为RustMinimal的析构函数.

Rust支持

rust/kernel/prelude.rs

rust
 1// SPDX-License-Identifier: GPL-2.0
 2
 3//! The `kernel` prelude.
 4//!
 5//! These are the most common items used by Rust code in the kernel,
 6//! intended to be imported by all Rust code, for convenience.
 7//!
 8//! # Examples
 9//!
10//! ```
11//! use kernel::prelude::*;
12//! ```
13
14#[doc(no_inline)]
15pub use core::pin::Pin;
16
17#[doc(no_inline)]
18pub use alloc::{boxed::Box, vec::Vec};
19
20#[doc(no_inline)]
21pub use macros::{module, vtable};
22
23pub use super::build_assert;
24
25// `super::std_vendor` is hidden, which makes the macro inline for some reason.
26#[doc(no_inline)]
27pub use super::dbg;
28pub use super::{pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
29
30pub use super::static_assert;
31
32pub use super::error::{code::*, Error, Result};
33
34pub use super::{str::CStr, ThisModule};
  • core::pin: 这个命名空间应该是 Rust 标准库(std crate)的一部分,但为了避免对嵌入式设备等环境的过度依赖,Rust 也提供了一个核心库(core crate),其中包含了许多常用的类型和函数。因此,在这里使用 core::pin 而不是 std::pin 可以让代码更具有通用性。

  • alloc::{boxed, vec}: 这个命名空间中包含了 Rust 分配器 alloc crate 中的两个常见类型:Box 和 Vec。与核心库类似,alloc crate 应该也是标准库的一部分,但需要在程序中显式引入。

  • macros::{module, vtable}: 这个命名空间中包含了自定义宏 module 和 vtable。由于 Rust 的宏是编译时展开的语法扩展,因此它们本身并不占用运行时资源,也不存在库或模块的概念。在 Rust 中,宏可以在任何作用域内使用,只要它们已经被正确定义和导入。

  • super表示到处父模块下的一些公共API:

    • build_assert, static_assert, error, str:CStr 定义在rust/kernel/下对应的rust文件下

    • dbg定义在rust/kernel/std_vendor.rs

    • pr_系列的宏定义在rust/kernel/print.rs

    • ThisModule定义在rust/kernel/lib.rs

  • error::{code, Error, Result}, str::CStr, ThisModule: 这几个命名空间中包含了来自父模块的一些类型和函数,用于错误处理、字符串处理和模块元数据获取。由于这些功能较为特定,因此这些命名空间可能只存在于该 crate 中。

有关#[doc(no_inline)]:

#[doc(no_inline)] 是 Rust 中的一个属性(attribute),用于指定某个项的文档注释不应该被内联到其他模块中。具体来说,当一个项被标记为 no_inline 时,在生成 API 文档时,该项的文档注释将只出现在自己所在的模块中,而不会出现在直接或间接导入该项的其他模块中。

需要注意的是,#[doc(no_inline)] 只影响文档注释的内联情况,而不影响代码本身的内联机制。例如,即使一个函数被标记为 no_inline,如果编译器认为将其内联到调用点更高效,仍然可能会在代码中进行内联。因此,在使用 no_inline 属性时,需要同时考虑文档注释和实际代码的内联情况,以便达到最佳的代码可读性和执行效率。

Rust代码目录结构

bash
 1rqdmap in ~/tmp/linux-6.3.4/rust via  v13.1.1-gcc via  v1.72.0-nightly
 2[ 17:27:20 ]   tree
 3.
 4├── alloc
 5│   ├── alloc.rs
 6│   ├── boxed.rs
 7│   ├── collections
 8│   │   └── mod.rs
 9│   ├── lib.rs
10│   ├── raw_vec.rs
11│   ├── README.md
12│   ├── slice.rs
13│   └── vec
14│       ├── drain_filter.rs
15│       ├── drain.rs
16│       ├── into_iter.rs
17│       ├── is_zero.rs
18│       ├── mod.rs
19│       └── partial_eq.rs
20├── bindgen_parameters
21├── bindings
22│   ├── bindings_helper.h
23│   └── lib.rs
24├── build_error.rs
25├── compiler_builtins.rs
26├── exports.c
27├── helpers.c
28├── kernel
29│   ├── allocator.rs
30│   ├── build_assert.rs
31│   ├── error.rs
32│   ├── lib.rs
33│   ├── prelude.rs
34│   ├── print.rs
35│   ├── static_assert.rs
36│   ├── std_vendor.rs │   ├── str.rs │   ├── sync
37│   │   └── arc.rs
38│   ├── sync.rs
39│   └── types.rs
40├── macros
41│   ├── concat_idents.rs
42│   ├── helpers.rs
43│   ├── lib.rs
44│   ├── module.rs
45│   └── vtable.rs
46└── Makefile
  • alloc目录的README中所说:

Rust 内核开发中使用的 alloc crate 来源于 Rust 标准库,以 Apache-2.0 或 MIT 许可证发布,并针对内核使用进行了一定的修改和适配。

为什么需要在内核中使用 alloc crate 呢?原因在于,Rust 的标准库中包含了一些内存分配、字符串、向量等常用的数据结构和算法,而这些功能在内核开发中也是非常有用的。但是,由于内核的运行环境与普通用户空间程序不同,存在一些限制和特殊需求(例如无法访问堆内存),因此需要对 alloc 进行适当的修改和限制,从而使它能够在内核中安全有效地使用。

在选择是否将 alloc 引入内核时,内核开发人员和 Rust 社区之间存在一些分歧。为了平衡两者的需求,他们决定保留一个基于 alloc 的子集,只添加必要的新方法,避免与 Rust 标准库过多地分叉。随着时间的推移,内核可能需要更多的功能和改进,而这些功能和改进应该被纳入 Rust 标准库中。到那时,内核就可以放弃自己的版本,转而使用标准库中的实现,以避免分歧和维护成本。

有关alloc的官方crate则可以参考:

alloc - Rust

rust/library/alloc at master · rust-lang/rust · GitHub

嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱; 加密博客访问请求等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改日志
  • 2023-06-27 17:46:16 Linux Rust分析添加readmore放置摘要出现代码块内容
  • 2023-06-06 21:30:42 Linux内核: {Rust使用,编译期间的优化}