A kernel in Rust designed to provide a universal, multi-ABI container runtime.と銘打ってますが
その実態は思いついたことを詰め込んだキメラ

[1] Scarlet: https://github.com/petitstrawberry/Scarlet
バイナリ互換機能 (ABI Module)
ドライバ
ファイルシステム, ネットワークスタック, etc...
ハイパーバイザ機能
size的な話
ビルド時間
ドライバの変更や, ちょっとした機能追加のたびに毎回数分待つのは辛いし,
使わないドライバやカーネル機能がずっとメモリに常駐しているのもあまりよろしくない
LKM (Loadable Kernel Module)とは
Linuxなどで使われるカーネルの仕組みで, ドライバなどの機能を動的にカーネルにロードできるようにするもの
$ modprobe my_driver.ko
こんな感じに実行時にカーネル空間にドライバなどをロードできるようになる
こうすることで,
まずはLKMの仕組みを知らないと真似できない
カーネルモジュール作成フロー
カーネルモジュールのロードフロー
じゃあ、これをRust製OSであるScarletに実装してみよう、というのが今回の話
Rustで真似するだけよね, 余裕余裕
そもそも, このロード機構自体を実装するのが辛くないですか?という話もあるが, まあそれは置いておいて今回は言語的な問題に絞って話す
Scarletの依存関係はこんな感じ
とはいえ, そうは簡単に行かなかった!!
# lsm_load lsm-test
loading module: /scarlet/system/scarlet/modules/lsm-test.lsm
[LSM] unresolved symbol: _RNvNtNtNtCs5hMFX2pKYKj_7scarlet7library3std5print6__print
failed to load lsm-test: unresolved symbol (12)
#
シンボルが見つかりません!!??
実際のカーネルのシンボルとLSMの未解決シンボルを見比べてみると, どうも名前が違う
_RNvNtNtNtCs5hMFX2pKYKj_7scarlet7library3std5print6__print_RNvNtNtNtCs33NknshCXnf_7scarlet7library3std5print6__printなんか良くわからんhashみたいなのが前についてるんですが
Rustのコンパイラが関数や変数のシンボル名を特定の規則に従って変換すること
今回の例で言うと
_RNvNtNtNtCs5hMFX2pKYKj_7scarlet7library3std5print6__print
|`----+-----`||`--+--`
| | || |
| | || +------------------ crate-root identifier "scarlet"
| | |+---------------------- length 7 of "scarlet"
| | +----------------------- end of base-62-number
| +-------------------------------disambiguator for crate-root "scarlet" Cs5hMFX2pKYKj
+------------------------------------ crate-root
[2] v0 Symbol Format: The rustc-book, https://doc.rust-lang.org/rustc/symbol-mangling/v0.html
_RNvNtNtNtCs5hMFX2pKYKj_7scarlet7library3std5print6__print
|`----+-----`||`--+--`
| | || |
| | || +------------------ crate-root identifier "scarlet"
| | |+---------------------- length 7 of "scarlet"
| | +----------------------- end of base-62-number
| +-------------------------------disambiguator for crate-root "scarlet" Cs5hMFX2pKYKj
+------------------------------------ crate-root
crate disambiguator
BSP側 (カーネルバイナリを生成する側): debug buildのopt-level=3
LSM側 (カーネルモジュール側): release buildのopt-level=0
なので, rustc的には全く別のクレートと見なされている
C言語マンの発想「最適化されてても, 構造体のフィールドや関数のシグネチャが同じなら実際同じなんだから, manglingとか無視したらいいじゃん」
Rust「あかん。」
そもそもRustは...安定したABIが存在しない
これらは, 同じソースコードでもrustcのバージョンによってもコロコロ変わる。最適化オプションとかでも変わるかも
Manglingであれだけ厳密にクレートを区別してるのも, そういう理由があるから。多分
実際のところ最適化の自由度を増やすためとかそういう理由らしいが, LLVMバックエンドなのでどこまでrustc側でコントロールしてるかはしらん...
「crate-typeとしてdylibが指定可能なのは何のためにあるんだよ...結局, 独立してビルドした環境では使い物にならんはずでしょ....」
「じゃあ、普通にRustで動的にロードする共有ライブラリ作りたい場合はどうしたら?」
ただし, C ABIに縛られる
structは#[repr(C)]したものだけextern "C" + #[no_mangle]で定義する必要があるextern "C" + #[no_mangle]や#[repr(C)]をつけて, cdylibにするのは嫌そもそも, 今回はLKM的なものを作りたいという話であった
こういうのは独自のビルドツールcargo-scarletっていうので吸収することにした
結局Linuxも, カーネルバージョンへの追従のためにDKMS(Dynamic Kernel Module Support)に頼っているので, まあいいでしょう...
LSM側のコード例 (細かいメタデータ類とかは省略)
#![no_std]
use scarlet::println;
// エントリポイントだけはno_mangleで定義する (カーネルが呼び出すため)
#[unsafe(no_mangle)]
pub extern "C" fn scarlet_lsm_init() -> Result<(), &'static str> {
println!("[lsm-test] Loadable Scarlet Module loaded successfully!");
Ok(())
}
これで, LSMをビルドして, ScarletのShellからロードしてみると
# lsm_load lsm-test
loading module: /scarlet/system/scarlet/modules/lsm-test.lsm
[lsm-test] Loadable Scarlet Module loaded successfully!
module loaded successfully
#
LKM的な機能の実装は, RustのABIの不安定さやName Manglingの問題など, 色々な問題があって大変だった
次回, 「Rust製OSでDKMS的なものを実装する」
(自作OS上でrustcを動かして, セルフホストすることになります)
できる気がしません (やるとは言ってない)
ScarletのGitHubリポジトリ
https://github.com/petitstrawberry/Scarlet
pagenate: off
まあ、簡単にいうとカーネルは, 後で動的ライブラリ(カーネル)にリンクするためのオブジェクトファイルを作る感じ。普通の共有ライブラリを使うプログラムを作る時と変わらん (ただし, オブジェクトファイル止まり)