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

近来,Rust 爆火。

不久之前,53 岁的 Linus Torvalds 在出席 Linux 基金会主办的 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<Self> {            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.net/SubscriberLink/910762/a26f968ea086e32d/

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