第791回では基本的なカーネルモジュールの作り方とそれをDKMSに対応させる方法を紹介しました。今回はカーネルの新しい機能のひとつである
UbuntuカーネルにおけるRustの対応
Linuxカーネルでは、Kernel 6.
あらゆるケースにおいてRustに置き換えられるというわけではありませんが、今後カーネルのコードを読み書きする上でC言語やアセンブラだけでなくRustに対する理解が必要となる機会はきっと増えていくことでしょう。今回は第791回で作成したC言語版の自作カーネルモジュールのサンプルを、Rustに置き換える手順を紹介します。
Rustをカーネルで使う上でポイントなのが
かんたんにまとめると現時点でのUbuntuは、
$ grep _RUST /boot/config-$(uname -r) CONFIG_RUST_IS_AVAILABLE=y CONFIG_RUST=y CONFIG_RUSTC_VERSION_TEXT="rustc 1.68.2 (9eb3afe9e 2023-03-27) (built from a source tarball)" CONFIG_HAVE_RUST=y # CONFIG_SAMPLES_RUST is not set # CONFIG_RUST_DEBUG_ASSERTIONS is not set CONFIG_RUST_OVERFLOW_CHECKS=y # CONFIG_RUST_BUILD_ASSERT_ALLOW is not set
上記でもっとも重要なのがCONFIG_
」y
」
Rustは従来サポートしていたGCCに比べるとリリース周期が短いツールです。カーネルのRust対応においては、後方互換性の維持が保証されないCONFIG_
」
$ /usr/src/linux-headers-$(uname -r)/scripts/min-tool-version.sh rustc 1.68.2
Rust版のモジュールをビルドするには、ターゲットとなるカーネルに合わせたバージョンのRustツールチェインが必要です。ちなみにカーネルの6.
注意しなければいけないのはCONFIG_
は無効化されています。
$ lsb_release -d Description: Ubuntu 22.04.3 LTS $ uname -r 6.2.0-26-generic $ grep _RUST /boot/config-$(uname -r) CONFIG_RUST_IS_AVAILABLE=y CONFIG_HAVE_RUST=y
22.
ちなみに2023年12月現在では、nobleのカーネルは23.
Rustモジュールのビルドに必要なもの
Rustモジュールのビルドには、Rustのツールチェインの他にLLVMやclangも必要です。そこで次のようにインストールしてください。
$ sudo apt install rustc-1.68 rust-1.68-src rustfmt-1.68 bindgen-0.56 llvm clang build-essential linux-lib-rust-$(uname -r)
上記はUbuntu 23.
$ uname -r 6.5.0-9-generic $ /usr/src/linux-headers-$(uname -r)/scripts/min-tool-version.sh rustc 1.68.2 $ /usr/src/linux-headers-$(uname -r)/scripts/min-tool-version.sh bindgen 0.56.0
実はgccやbinutils、llvmなども
Rustのカーネルモジュールを作ってみる
では、実際にRust版のカーネルモジュールを作ってみましょう。やろうとしていることは第791回と同じで、カーネルモジュールをロードする時とアンロードする時にメッセージを記録するだけです。
第791回では、ひとつのMakefileでKbuildと通常のmakeの両方に対応しましたが、今回はMakefileを次のように分けることにします。
Makefile src/ Makefile hello.rs
これによりsrc/
はKbuild専用の記述にできます[1]。
まず最初にトップディレクトリのMakefileは次のように記述してください。
# SPDX-License-Identifier: CC0-1.0
KVER ?= $(shell uname -r)
KDIR ?= /usr/lib/modules/$(KVER)/build
RUSTFMT = rustfmt-1.68
RUST_FLAGS = CROSS_COMPILE=x86_64-linux-gnu-
RUST_FLAGS += HOSTRUSTC=rustc-1.68
RUST_FLAGS += RUSTC=rustc-1.68
RUST_FLAGS += BINDGEN=bindgen-0.56
RUST_FLAGS += RUSTFMT=$(RUSTFMT)
RUST_FLAGS += RUST_LIB_SRC=/usr/src/rustc-1.68.2/library
default:
$(MAKE) $(RUST_FLAGS) -C $(KDIR) M=$$PWD/src
install: default
kmodsign sha512 \
/var/lib/shim-signed/mok/MOK.priv \
/var/lib/shim-signed/mok/MOK.der \
src/hello.ko
$(MAKE) -C $(KDIR) M=$$PWD/src modules_install
depmod -A
fmt:
find . -type f -name '*.rs' | xargs $(RUSTFMT)
clean:
$(MAKE) $(RUNST_FLAGS) -C $(KDIR) M=$$PWD/src clean
基本的には第791回で説明したとおりの内容です。異なるのは次の点となります。
- Rust関連の変数が追加された
- fmtターゲットが追加された
- Kbuild用の行が削除された
- ソースコードが別ディレクトリになったため、
M=
オプションのパスが一段深くなった
Rust関連の変数については、カーネル側に明示的に特定バージョンのRustツールチェインを使わせるために指定しています。Ubuntuの場合、リリースによって複数バージョンのRustツールチェインを公式リポジトリからインストールできます。その場合、
もうひとつ追加された項目がM=
」
Kbuild用の行が削除されたのは
# SPDX-License-Identifier: CC0-1.0
obj-m := hello.o
そして本体となる
// SPDX-License-Identifier: GPL-2.0-only
//! Rust module sample
use kernel::prelude::*;
module! {
type: Hello,
name: "hello",
author: "Mitsuya Shibata",
description: "hello",
license: "GPL v2",
}
struct Hello {}
impl kernel::Module for Hello {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Hello world!\n");
Ok(Hello {})
}
}
impl Drop for Hello {
fn drop(&mut self) {
pr_info!("exit hello module\n");
}
}
Rustの書式についてはRustの入門書に譲るとして、カーネルモジュール関連の説明をしていきましょう。
まず最初にmodule
マクロ」MODULE_
マクロに近い扱いですが、必須のフィールドが増えています。最大のポイントがtype: Hello
」Hello
」Module
トレイトを実装する型を指定します。今回はHello
構造体を作成しそれを指定することにしました。
「impl kernel::Module for Hello
」init()
メソッドを定義し、その中でpr_
マクロを呼んでいるだけです。第791回で言うところのmodule_
」hello_
の実装部分ですね。
Module
トレイトにはmodule_
」Drop
トレイトを使えば、モジュールを終了する時pr_
マクロでログを出力しているだけとなります。
コードが完成したらmakeしてみましょう。
$ make make CROSS_COMPILE=x86_64-linux-gnu- HOSTRUSTC=rustc-1.68 RUSTC=rustc-1.68 BINDGEN=bindgen-0.56 RUSTFMT=rustfmt-1.68 RUST_LIB_SRC=/usr/src/rustc-1.68.2/library -C /usr/lib/modules/6.5.0-9-generic/build M=$PWD/src make[1]: Entering directory '/usr/src/linux-headers-6.5.0-9-generic' RUSTC [M] /home/ubuntu/rust/src/hello.o MODPOST /home/ubuntu/rust/src/Module.symvers CC [M] /home/ubuntu/rust/src/hello.mod.o LD [M] /home/ubuntu/rust/src/hello.ko BTF [M] /home/ubuntu/rust/src/hello.ko Skipping BTF generation for /home/ubuntu/rust/src/hello.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-9-generic'
実行されるコマンド列は変わりますが、手順自体はC言語版と同じです。生成されたものも、普通のモジュールファイルです。
$ file src/hello.ko src/hello.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=f87ad98bf2f25ac017602656c284b5b36a948a61, with debug_info, not stripped $ modinfo src/hello.ko filename: /home/ubuntu/rust/src/hello.ko author: Mitsuya Shibata description: hello license: GPL v2 srcversion: C487F60BD22E51D45FD0090 depends: retpoline: Y name: hello vermagic: 6.5.0-9-generic SMP preempt mod_unload modversions $ strings src/hello.ko | grep rustc clang LLVM (rustc version 1.68.2 (9eb3afe9e 2023-03-27) (built from a source tarball)) /usr/src/rustc-1.68.2/library/core/src/ptr
外からはRust版であることはわからず、かろうじてファイルの中の文字列にコンパイラの情報が残っているぐらいですね。
実際にモジュールを署名してインストールしてみましょう。これを実行するためにはあらかじめ第791回でも説明した、
$ sudo make install make CROSS_COMPILE=x86_64-linux-gnu- HOSTRUSTC=rustc-1.68 RUSTC=rustc-1.68 BINDGEN=bindgen-0.56 RUSTFMT=rustfmt-1.68 RUST_LIB_SRC=/usr/src/rustc-1.68.2/library -C /usr/lib/modules/6.5.0-9-generic/build M=$PWD/src make[1]: Entering directory '/usr/src/linux-headers-6.5.0-9-generic' make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-9-generic' kmodsign sha512 \ /var/lib/shim-signed/mok/MOK.priv \ /var/lib/shim-signed/mok/MOK.der \ src/hello.ko make -C /usr/lib/modules/6.5.0-14-generic/build M=$PWD/src modules_install make[1]: Entering directory '/usr/src/linux-headers-6.5.0-14-generic' INSTALL /lib/modules/6.5.0-14-generic/updates/hello.ko SIGN /lib/modules/6.5.0-14-generic/updates/hello.ko DEPMOD /lib/modules/6.5.0-14-generic Warning: modules_install: missing 'System.map' file. Skipping depmod. make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-14-generic' depmod -A $ modinfo hello filename: /lib/modules/6.5.0-14-generic/updates/hello.ko author: Mitsuya Shibata description: hello license: GPL v2 srcversion: C487F60BD22E51D45FD0090 depends: retpoline: Y name: hello vermagic: 6.5.0-9-generic SMP preempt mod_unload modversions sig_id: PKCS#7 signer: rust2 Secure Boot Module Signature key sig_key: 36:6E:89:CC:A8:24:44:14:F0:03:7A:0A:CA:DF:A9:C3:E9:29:C9:17 sig_hashalgo: sha512 signature: 42:6B:5C:E3:3A:80:B2:E1:72:87:75:A3:B8:DF:16:38:84:67:89:77: 6D:04:F3:41:BE:30:55:FB:6C:66:AD:C8:DE:F0:8F:74:99:54:CE:9A: A0:B9:A8:8B:4A:1B:38:D5:6F:BF:B9:A4:30:60:02:F8:ED:F5:45:C5: 85:05:34:28:16:94:34:99:3A:14:E8:E4:23:7A:92:50:2F:55:12:C3: 4A:F8:43:55:F2:79:20:FD:01:00:D7:19:8B:59:F0:2F:94:D7:A5:A5: F6:03:E8:39:E9:5D:04:DF:D0:BD:27:3D:2D:D8:C4:15:4A:83:08:7F: 53:6B:7B:7E:25:A6:C9:CE:91:9F:4E:24:1D:F7:42:FC:F8:32:0C:9A: 95:6A:C1:D9:F5:C6:E9:29:6B:68:31:00:9A:DC:39:50:FD:4F:A9:02: B7:EF:C1:6F:2E:BE:DA:EE:36:8A:20:F7:28:41:7E:74:87:8C:7A:6D: 47:65:40:6D:35:84:44:7C:E2:2A:72:6D:82:F1:37:25:5C:86:0B:CC: 71:E8:9D:CE:D0:F4:F2:50:6F:CE:36:D9:CD:33:80:0E:B7:3B:C1:81: E5:37:DD:AC:A7:D9:E2:81:2E:4A:1E:52:EC:85:D8:C4:DB:6D:E3:C0: 3C:96:E0:CE:B7:7D:7B:B0:4D:D6:75:E9:2B:48:C2:D3
無事に署名とインストールができました。それでは実際にロード・
$ sudo modprobe hello $ sudo modprobe -r hello $ journalctl -r -k | head -n 2 Dec 18 17:50:10 rust2 kernel: hello: exit hello module Dec 18 17:50:06 rust2 kernel: hello: Hello world!
問題なくRust版のカーネルモジュールが動作しました。
ここまで説明したように、Rustの基本的な知識があれば、Rustでシンプルなモジュールを作る程度であればそこまで難しくありません。もちろん本格的なデバイスドライバーを作るとなると、Rustとカーネルの両方においてそれなりの知識が必要ですしょう。現在はまだカーネルの開発者がRustを使ってみる準備が整ったという段階です。ただ、それでも新しいものを触ってみるのは常に楽しいものです。
年末年始に