近年來,Rust 語言以內存安全、高可靠性、零抽象等能力獲得大量開發者關注,而這些特性恰好是內核編程中所需要的,所以我們看下如何用rust來寫Linux內核模塊。
Rust 與內核模塊
雖然 Rust 支持已經在 LinuxKernel6.1 版本合併到主線了,所以理論上來說,開發者可以使用 Rust 來爲 Linux6.1 寫內核模塊。
但實際開發工作中,內核版本不是最新的,比如 Debian 11 的內核就是 5.10 版本的,那麼在這種情況下,該如何用 Rust 寫內核模塊呢?
原理
- Rust 如何向內核註冊回調、如何調用內核代碼。Rust 和 C 的互操作性
- Rust 如何編譯到目標平臺上。Rust 的 target 配置
- Rust 如何申明內核模塊入口、並添加特殊 section。Rust 內核模塊的二進制約定
Rust 和 C 的互操作性
第一個問題基本上就是 C 和 Rust 的互操作性了。
得益於 Rust 的抽象層次,C 語言和 Rust 的互相調用都是比較容易的。rust 官方也提供了 bindgen 這樣,根據 .h 文件生成 .rs 文件的庫。
這樣一來,貌似直接使用 bindgen 將內核頭文件翻譯成 .rs 就可以了?
但還有一個問題,如何獲取內核頭文件路徑呢?
可以使用一個 dummy 內核模塊,在編譯過程中把編譯參數導出來,其中包含了頭文件路徑,編譯參數等,用於 bindgen 生成代碼。
Rust 和 target 配置
內核模塊和普通的程序相比,主要的不同在於:
- 內核模塊是 freestanding 的,沒有 libc、內存分配也比較原始
- 內核模塊對於異常處理等有特殊約定
Rust 提供了 no_std 機制,可以讓 rust 代碼編譯成 freestanding 二進制;Rust 也提供了自定義 target 的方式,可以自定義聲明生成二進制的規範。
內核模塊的二進制約定
內核對內核模塊有一些約定:
- 通過 .modinfo 等 section 來聲明模塊信息
- 提供 init_module、cleanup_module 來提供內核模塊的安裝和卸載功能
在這一塊,Rust 提供了 link_section 來自定義 section,也支持 extern "C"來導出函數。
此外,這些底層的操作,可以由內核提供一些 C 語言宏來簡化代碼,Rust 也提供了宏,可以用來做類似的事情。
一個小例子
說了這麼多,我們來看一個帶註釋的例子:
#![no_std]
// no_std用於表示沒有std庫,即freestanding環境
extern crate alloc;
use alloc::borrow::ToOwned;
use alloc::string::String;
// 我們以printk爲底層,提供了println
use linux_kernel_module::println;
// 這個struct代表內核模塊
struct HelloWorldModule {
message: String,
}
// 實現內核模塊初始化方法
impl linux_kernel_module::KernelModule for HelloWorldModule {
fn init() -> linux_kernel_module::KernelResult<Self> {
println!("Hello kernel module from rust!");
Ok(HelloWorldModule {
message: "on the heap!".to_owned(),
})
}
}
// 提供內核模塊卸載方法
impl Drop for HelloWorldModule {
fn drop(&mut self) {
println!("My message is {}", self.message);
println!("Goodbye kernel module from rust!");
}
}
// 通過kernel_module宏,export了內核模塊的相關信息
linux_kernel_module::kernel_module!(
HelloWorldModule,
author: b"Fish in a Barrel Contributors",
description: b"An extremely simple kernel module",
license: b"GPL"
);
具體的構建和運行:
$ cd linux-kernel-module-rust/hello-world
$ RUST_TARGET_PATH=$(pwd)/.. cargo +nightly xbuild --target x86_64-linux-kernel-module
$ make
$ insmod helloworld.ko
$ rmmod helloworld
$ dmesg | tail -n 3
[521088.916091] Hello kernel module from rust!
[521174.204889] My message is on the heap!
[521174.204891] Goodbye kernel module from rust!
已在內核 5.10.0-17-amd64 上測試。
具體的代碼以及相關配置,可以參考 GitHub 倉庫:https://github.com/robberphex/linux-kernel-module-rust
一些小細節
- VSCode 支持
由於 rust-analyzer 對於自定義 target,多模塊的支持不夠,所以我們暫時需要手動配置下 settings.json 才能正常開發:
{
"rust-analyzer.cargo.extraEnv": {
"RUST_TARGET_PATH": "/root/linux-kernel-module-rust"
},
"rust-analyzer.cargo.target": "x86_64-linux-kernel-module",
"rust-analyzer.server.extraEnv": {
"RA_LOG": "lsp_server=debug",
"RUST_TARGET_PATH": "/root/linux-kernel-module-rust"
},
"rust-analyzer.trace.server": "verbose",
"rust-analyzer.linkedProjects": [
"hello-world/Cargo.toml",
"Cargo.toml"
],
}
- 其他高級功能
比如字符設備、sysctl 等功能,可以參考項目中相關的測試代碼。
更多規劃
- 和 Rust-for-Linux 保持 API 一致。(Rust-for-Linux 例子:https://github.com/Rust-for-Linux/linux/blob/d9b2e84c0700782f26c9558a3eaacbe1f78c01e8/samples/rust/rust_chrdev.rs)
- Rust 提供的內存安全性、零抽象等能力,恰好是內核領域亟需的特性和能力。比如內核態如果出現內存泄漏、野指針,一會造成很大影響、二來也很難調試。在這個領域,我們可以藉助Rust的能力來構造更加安全、更大的項目。
原始項目是 fishinabarrel/linux-kernel-module-rust,但目前提示使用 rust-for-linux,已經 archived。然而,考慮到目前舊版本內核還有很多,所以我重新修復了這個項目的一些環境,讓大家在舊版本內核上能夠用 Rust 編寫內核模塊。
作者:卜比
本文爲阿里雲原創內容,未經允許不得轉載。