在 Linux 新版内核中的 Rust 初探,原来是这样的!

整理 | 苏宓

出品 | CSDN(ID:CSDNnews)

近来,Rust 爆火。

不久之前,53 岁的 Linus Torvalds 在出席 会主办的 2022 开源峰会时表示,下一个版本的 Linux 内核主线,可能就会合并 Rust 语言提交的 PR 分支。然而,在五天前有开发者询问 Linus 是否在 Linux 6.1 进行补丁合并时错过了一个 Git Pull 请求时,对方称他的电脑有问题,合并速度很慢,或将导致 Linux 6.1 补丁合并推迟。

正当众人怀疑他买了一个二手的翻新 ECC 时,10 月 13 日,Linux 内核开发者 Jonathan Corbet 惊喜地分享了一则关于“Linux 6.1 中 Rust 初探”的好消息。接下来,我们将与大家一下看看 Linux 和 Rust 遇到一起,将带来哪些“火花”?

 

最新进展

根据 Jonathan Corbet 的介绍,在 Linux 6.1 版本中,有很多重要的变化被合并到主线中,而引入对 Rust 的支持也只是其中一个最受关注的方面。虽然 Rust 的到来,为内核开发者的创新带来了一些不同,但是他也发现当前 Linux 内核中的 Rust 还不能做很多有趣的事情。

为 Linux 内核开发 Rust 的工作其实早在几年前就已经开始了,它已经产生了许多支持代码和一些有趣的驱动程序,包括在 Linux 内核中用 Rust 语言编写一个苹果图形驱动。

不过,在最初并入主线内核时,Linus Torvalds 明确表示,应该尽可能少地包含一些功能。因此,这些驱动程序和它们的支持代码被去掉了,必须等待未来的内核发布时候才会被加进来。现在有的只是建立一个可以载入内核的模块所需的支持,以及一个小的示例模块。

 

构建 Rust 支持

为了让对此感兴趣的开发者更加清楚明白 Linux 内核中 Rust 的支持功能,Jonathan Corbet 分享了开发者可能会遇到的一些问题。

譬如,内核配置过程会在构建系统上寻找先决条件,如果不存在,就会默默地禁用 Rust 选项,这样它们甚至不会显示。

因此,构建 Rust 支持需要特定版本的 Rust 编译器和 bindgen (一个能自动为 C(或 C++)库生成 Rust 绑定的辅助库和命令行工具)工具。具体来说,就是 Rust 1.62.0 和 bindgen 0.56.0 版本。

如果目标系统有更新的版本,配置过程会发出警告,但无论如何还是会继续。对于那些试图用分销商提供的 Rust 工具链进行构建的人来说,更尴尬的是,构建过程还需要 Rust 标准库的源代码,这样它就可以构建自己的核心和 alloc crates 的版本。在分销商开始提供 "Rust for the kernel "包之前,把这些代码放到构建过程可以找到的地方是有点困难的。

获得这种依赖项的方法是放弃分销商的工具链,而从 Rust 存储库中安装所有的东西。其中,Rust 官方的使用指南上详细地描述了 Rust 的上手过程(https://www.rust-lang.org/learn/get-started)。

 

示例模块

在安装完成后,内核配置系统将设置 CONFIG_RUST 选项,这个选项将可以用于构建示例模块。该模块(samples/rust/rust_minimal.rs)确实很小,但它足以让我们了解 Rust 中的内核代码会是什么样子。首先,写下类似于 #include 的代码行:

use kernel::prelude::*;

在 rust/kernel/prelude.rs 中找到声明的拉取,使得一些类型、函数和宏可用。

用 C 语言编写的内核模块包括对 MODULE_DESCRIPTION() 和 MODULE_LICENSE() 等宏的一些调用,这些宏将有关模块的元数据存放在一个单独的 ELF 部分。module_init() 和 module_exit() 宏分别标识模块的构造函数和析构函数。Rust 等同于将大部分的模板放在一个宏调用中。

module! {

type: RustMinimal,

name: b"rust_minimal",

author: b"Rust for Linux Contributors",

description: b"Rust minimal sample",

license: b"GPL",

}

这个宏对各个字段的顺序很挑剔,如果开发者弄错了就会抱怨。除了把所有这些信息放到一个调用中,“moudule!”宏还包括一个 type: ,它将指向实际模块代码的指针。是实际模块代码的指针。开发者系统可以有一个能做有趣事情的模型,在示例模块中,该类型可以帮助开发者实现愿望。

struct RustMinimal {

numbers: Vec

,

}

它是一个包含 32 位整数值的 Vec。本身 Rust 允许为结构类型添加接口("trait")实现。因此,这个示例模块为 RustMinimal 类型实现了 kernel::Module trait。

impl kernel::Module for RustMinimal {

fn init(_module: &'static ThisModule) -> Result

{

pr_info!("Rust minimal sample (init)n");

pr_info!("Am I built-in? {}n", !cfg!(MODULE));

let mut numbers = Vec::new();

numbers.try_push(72)?;

numbers.try_push(108)?;

numbers.try_push(200)?;

Ok(RustMinimal { numbers })

}

}

这个 init() 函数常常被认为是做常规模块初始化的工作。但在这种情况下,被期望做常规的模块初始化工作。在这种情况下,它向系统日志做了一些反馈(在这个过程中,它通过 cfg!()宏,可以在编译时用于查询内核配置参数)。然后,它分配了一个可变的 Vec,并试图将三个数字放入其中。try_push() 的使用在这里很重要:一个 Vec 会在必要时调整自己的大小。这涉及到分配内存,这在内核环境中可能会失败。

如果分配失败,try_push() 将返回一个失败的状态,这反过来将导致 init() 返回失败(这就是代码行最后"?"的作用)。

那么,如果一切顺利,它会返回一个 RustMinimal 结构,其中包含分配的 Vec 和一个成功状态。由于这个模块没有与任何其他内核子系统交互,实际上它不会做任何事情,只是耐心地等待被删除。在 Kernel::Module trait 中没有移除模块的函数;相反,这里使用一个简单的 RustMinimal 类型的析构器。

impl Drop for RustMinimal {

fn drop(&mut self) {

pr_info!("My numbers are {:?}n", self.numbers);

pr_info!("Rust minimal sample (exit)n");

}

}

这个函数打印出初始化时存储在 Vec 中的数字(从而确认数据在此期间存活)并返回,之后,该模块将被删除,其内存被释放。似乎没有办法让模块删除失败。

 

 

最后

在最后,Jonathan Corbet 说道,这就是在 Linux 6.1 中可以对 Rust 内核模块所做的事情的大致范围。这是一个可以玩的东西,但它目前还不能用于任何形式的真正的内核。

他表示,“希望这种情况在不久的将来会有所改变。如果幸运的话,Linux 6.2 版内核中的 Rust 将大大增强能力。”

来源:https://lwn/SubscriberLink/910762/a26f968ea086e32d/

胜象大百科