如何使用 rust 寫內核模塊

語言: CN / TW / HK

作者:卜比

近年來,Rust 語言以內存安全、高可靠性、零抽象等能力獲得大量開發者關注,而這些特性恰好是內核編程中所需要的,所以我們看下如何用rust來寫Linux內核模塊。

Rust 與內核模塊

雖然 Rust 支持已經在 LinuxKernel6.1 版本合併到主線了,所以理論上來説,開發者可以使用 Rust 來為 Linux6.1 寫內核模塊。

但實際開發工作中,內核版本不是最新的,比如 Debian 11 的內核就是 5.10 版本的,那麼在這種情況下,該如何用 Rust 寫內核模塊呢?

原理

  1. Rust 如何向內核註冊回調、如何調用內核代碼。Rust 和 C 的互操作性
  2. Rust 如何編譯到目標平台上。Rust 的 target 配置
  3. Rust 如何申明內核模塊入口、並添加特殊 section。Rust 內核模塊的二進制約定

Rust 和 C 的互操作性

第一個問題基本上就是 C 和 Rust 的互操作性了。

得益於 Rust 的抽象層次,C 語言和 Rust 的互相調用都是比較容易的。rust 官方也提供了 bindgen 這樣,根據 .h 文件生成 .rs 文件的庫。

這樣一來,貌似直接使用 bindgen 將內核頭文件翻譯成 .rs 就可以了?

但還有一個問題,如何獲取內核頭文件路徑呢?

可以使用一個 dummy 內核模塊,在編譯過程中把編譯參數導出來,其中包含了頭文件路徑,編譯參數等,用於 bindgen 生成代碼。

Rust 和 target 配置

內核模塊和普通的程序相比,主要的不同在於:

  1. 內核模塊是 freestanding 的,沒有 libc、內存分配也比較原始
  2. 內核模塊對於異常處理等有特殊約定

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 { 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!

1.png

已在內核 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 等功能,可以參考項目中相關的測試代碼。

更多規劃

原始項目是 fishinabarrel/linux-kernel-module-rust,但目前提示使用 rust-for-linux,已經 archived。然而,考慮到目前舊版本內核還有很多,所以我重新修復了這個項目的一些環境,讓大家在舊版本內核上能夠用 Rust 編寫內核模塊。