如何使用 rust 寫內核模塊

近年來,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<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 等功能,可以參考項目中相關的測試代碼。

更多規劃

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

作者:卜比

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章