Linux Kernelに導入されそうなRustを読む

Linux KernelにRustを取り入れようという動きは大分前から観測されていたが,遂にこのcommitで大きく動いた.

まだmasterにはマージされていないものの,このcommitではRustによるkernelの開発を可能にしている.

docmentation

コミットの多くはドキュメントで,サポートの詳細が書かれている.

とりあえずそれらを抜粋,要約しておく.

doc-guide/kernel-doc

kernel-docはRustで書かれた部分のドキュメンテーションを行わない.

しかし,その代わりにrust_docsでドキュメンテーションが行われる.

kbuild/kbuild

新たにRustコンパイラへのオプション用のKRUSTCFLAGSが追加された.

process/changes

rustc(nightly), bindgen(0.56.0), rustdoc(nightly)が追加された.

この内,rustdocはドキュメンテーションに使用される.

rust/arch-support

rustcはLLVMに依存している為,ターゲットに出来るアーキテクチャが限られている.

現在はarm64, x86_64のみをサポートしている.

rust/coding

Rustで書かれたカーネル内のコードはrustfmtによって自動でフォーマットされる.(嬉しいね.マジで)

ただし,コメントどドキュメントに関してはその範囲外である.

フォーマッタの設定はデフォルトのものを用いる.例えば,インデントにはタブではなく4つのspaceを用いる.

clippyを使う事も出来る.

現時点で全てのカーネルAPIに対して抽象化が提供されている訳では無いが,今後追加されるだろうとの事.

正当な理由がない限り,Rustでコードを書くときにはCバインディングを直接扱わないようにすべきである.

まだRust向けに抽象化されていないAPIを扱うコードを記述する際は,Rust向けに抽象化する事を検討すると良い.

Rustのコード内でkernel configに基づいたコードを書く時は

#[cfg(CONFIG_X)]      // `CONFIG_X` is enabled (`y` or `m`)
#[cfg(CONFIG_X="y")]  // `CONFIG_X` is enabled as a built-in (`y`)
#[cfg(CONFIG_X="m")]  // `CONFIG_X` is enabled as a module   (`m`)
#[cfg(not(CONFIG_X))] // `CONFIG_X` is disabled

のようにする事ができる.

rust/docs

ここはrustdocの良さを語ってる.

良いよね.

markdownだからテキストベースでも読みやすいし,ドキュメント生成も簡単だよって話をしてる.

rust/quic-start

ビルド,開発に必要なツール群とそのインストール方法が書かれている.この記事では列挙に留め,インストール方法については触れない

ビルド要件

開発要件

カーネルでRustをサポートするのに必要なconfig

General setup中にあるRust support(CONFIG_RUST)を有効化する必要がある.

また,Device DriversCharacter devicesにあるRust example(CONFIG_RUST_EXAMPLE)を有効化する事でサンプルのドライバがインストールされる.

Hack

さらに詳しい内容は/rust以下のコードやメニュー中のKernel hacking以下のRust hackingを読むとわかる.

GDBやBinutilsを利用していて,Rustシンボルがデマングルされていない場合,toolchainがRustのv0 mangling schemeをサポートしていない可能性がある.

drivers/char/rust_example

実際にconfigRust exampleを有効化した際にビルドされるRustのコードを読んでみる.

Rustサポート全体を把握するには/rust以下のコードを読むのが良いが,概観を把握するには実際に使われるコードを読んだほうが早いだろう.

/rust以下のコードもめちゃくちゃ面白いので,ぜひ読んでほしい.超楽しい.

rust_example.rs

#![no_std]

なので,stdは無い.

/rust以下にalloccore等のモジュールが定義されており,これらを利用する.


module!マクロ

module! {
    type: RustExample,
    name: b"rust_example",
    author: b"Rust for Linux Contributors",
    description: b"An example kernel module written in Rust",
    license: b"GPL v2",
    params: {
        my_bool: bool {
            default: true,
            permissions: 0,
            description: b"Example of bool",
        },
        my_i32: i32 {
            default: 42,
            permissions: 0o644,
            description: b"Example of i32",
        },
        my_str: str {
            default: b"default str val",
            permissions: 0o644,
            description: b"Example of a string param",
        },
        my_usize: usize {
            default: 42,
            permissions: 0o644,
            description: b"Example of usize",
        },
    },
}

これはrust/module.rsで定義されているproc_macro

コメントを読めばわかるが,パラメタの型,定義が出来る.

ここでは

が使える.


FileOperations

linux/fs.hfile_operations

struct RustFile;

impl FileOperations for RustFile {
    type Wrapper = Box<Self>;

    kernel::declare_file_operations!();

    fn open() -> KernelResult<Self::Wrapper> {
        println!("rust file was opened!");
        Ok(Box::try_new(Self)?)
    }
}

Cでオブジェクト指向っぽくしてる部分をtraitで実装してるの,すごい良いよね.

ここではopenしか実装してないので,readとかwriteはできない.


KernelModule::init

モジュールのインストール時に実行される奴

struct RustExample {
    message: String,
    _chrdev: Pin<Box<chrdev::Registration<2>>>,
    _dev: Pin<Box<miscdev::Registration>>,
}

impl KernelModule for RustExample {
    fn init() -> KernelResult<Self> {
        println!("Rust Example (init)");
        println!("Am I built-in? {}", !cfg!(MODULE));
        {
            let lock = THIS_MODULE.kernel_param_lock();
            println!("Parameters:");
            println!("  my_bool:    {}", my_bool.read());
            println!("  my_i32:     {}", my_i32.read(&lock));
            println!(
                "  my_str:     {}",
                core::str::from_utf8(my_str.read(&lock))?
            );
            println!("  my_usize:   {}", my_usize.read(&lock));
        }

        // Test mutexes.
...

ここのprintlnマクロの中身は,普段のRustで使われるprintln!ではなくて,rust/kernel/printk.rsで定義されているprintk

色んな機能のテスト(というかサンプル)が書かれている.

たとえば

{
    let lock = THIS_MODULE.kernel_param_lock();
...

の所とか,Rustのlifetimeを活かして,ブロックの終了時(lifetimeの終了時)にlock自体もドロップされる様になっている.

非常に良い


impl Drop for RustExample {
    fn drop(&mut self) {
        println!("My message is {}", self.message);
        println!("Rust Example (exit)");
    }
}

DropKernelModuleimplしている構造体に対してimplしろって書かれているが,どこで定義されてるのかパッと見からなかった.

まあdeinitでしょう.

install

実際にパッチを当ててインストールしてみると,rust_chrdevが生えてる.

vagrant@vagrant:~$ uname -a
Linux vagrant 5.12.0-rc3+ #7 SMP Fri Mar 19 18:24:14 JST 2021 x86_64 x86_64 x86_64 GNU/Linux
vagrant@vagrant:~$ cat /proc/devices |grep rust
244 rust_chrdev
vagrant@vagrant:~$

openすると

vagrant@vagrant:~$ sudo cat /dev/rust_miscdev
cat: /dev/rust_miscdev: Invalid argument
vagrant@vagrant:~$ dmesg|tail -n1
[16159.895305] rust file was opened!

ちゃんとFileOperationsで定義したopenが実行されている.エモい

楽しいね.

後で自分でも書いてみようかな