整理 | 蘇宓
出品 | CSDN(ID:CSDNnews)
近來,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
{
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/