Linux kernel moduleからRustを呼ぶkernel-rouletteを解析

# kernel-roulette

RustでLinux kernel moduleが書ける…。噂の真相を突き止めるため、GitHubへと向かった。
ネタばれ。噂は本当だった。

---

まず最初に見つかったのが、[rust.ko](https://github.com/tsgates/rust.ko)です。
内容は、kernel moduleをロードすると、"Hello from Rust!++"をprintkで出力する、というものです。
ただ、3年前からメンテナンスされていません。早々に見切りをつけて次を探しました。

次に発見したのが、[kernel-roulette](https://github.com/souvik1997/kernel-roulette)でした。
character device driverを実装しており、デバイスファイルをreadすると、一定確率でkernel panicを起こす、というdriverでした。
~~なんて迷惑なdriverでしょうか~~
こういう高度な技術を使って、無駄なことするの、好きです。
最終更新日が7ヶ月前なので、トラブルがあっても、これからなんとかなりそうです。

というわけで、少々トラブルはあったのですが、ビルドして動かしすことができました。

感想から言うと、RustでLinux kernel moduleが書ける、というより、Linux kernel moduleからRustを呼び出せる、という感じでした。
RustでLinux driverを書けるからHappy！という世界ではないです。（おそらくそういう世界はずっと来ないと思います）

ただ、細かいことは置いておいて、面白いので見ていきましょう。

まず、何はともあれ、動かすとどうなるか、を紹介します。
その後、ソースコードを解析していきます。

**Linux kernel driverやRustの基礎事項については解説しません。別途参照をお願いします。**

# ビルド＆動作確認環境

| 項目 | Versionなど |
| --- | --- |
| Linux kernel | 4.4.0-137-generic |
| distribution | Ubuntu 16.04 (Virtual Box) |
| rustc | 1.31.0-nightly (77af31408 2018-10-11) |

# 動かしてみよう

## 前準備

[kernel-roulette README](https://github.com/souvik1997/kernel-roulette/blob/master/README.md)の`Dependencies`にある通り、下記の前準備が必要です。

> - Linux kernel headers and build-essential (gcc, make, etc.)
- Nightly Rust (install from https://rustup.rs)
- Xargo and rust-src  
Download the Rust library source with rustup component add rust-src  
Install xargo with cargo install xargo

## ビルド

レポジトリをcloneして、patchを当てます。

```
$ git clone https://github.com/souvik1997/kernel-roulette.git
$ cd kernel-roulette
$ wget https://raw.githubusercontent.com/tomoyuki-nakabayashi/blogs/master/Rust/kernel-roulette/0001-fix-build-error-in-the-latest-nightly-rust.patch
$ patch -p1 < 0001-fix-build-error-in-the-latest-nightly-rust.patch
```

[README](https://github.com/souvik1997/kernel-roulette/blob/master/README.md)の`Building`に従って手順を実行します。
以下、少し冗長に書きますので、手っ取り早く動かしたい方は、READMEの方をご覧下さい。

kernel moduleをビルドします。`build/roullete.ko`が生成されることを確認します。

```
$ make
$ ls build/
...  roulette.ko  ...
```

いよいよ、緊張の瞬間です。kernel moduleをロードしてみましょう。

```
$ sudo insmod build/roullete.ko
```

ロードできちゃいます！
ちゃんとロードされているか、確認してみましょう。

```
$ lsmod
Module                  Size  Used by
roulette              106496  0
```

おるやん！
dmesgにroulette driverがログを出力するので、確認します。Panicする確率は1/100のようです！ほとんど死なないですね！

```
$ dmesg | tail -n 10
...
[16837.958611] Registered kernel-roulette with major device number 246
[16837.958613] Run /bin/mknod /dev/kernel-roulette c 246 0
[16838.468004] Panic probability: 1/100
```

roulette driverの指示通り、キャラクタデバイスファイルを作成します。

```
$ sudo /bin/mknod /dev/kernel-roulette c 246 0
```

作成したデバイスファイルをreadすると、1/100の確率で死にます！
catして、その時が来るのを待ちます。

```
$ cat /dev/kernel-roulette
Survived... sampled value is 79, which is >= 1
$ cat /dev/kernel-roulette
Survived... sampled value is 1, which is >= 1
```

死ねない！
死ぬ確率は、driverロード時にランダムで決定されるので、~~面倒なので~~ロードし直します。

```
$ sudo rmmod build/roulette.ko
$ sudo insmod build/roulette.ko
```

さて。

```
$ dmseg | tail -n 10
...
[17574.935478] Registered kernel-roulette with major device number 246
[17574.935481] Run /bin/mknod /dev/kernel-roulette c 246 0
[17575.447283] Panic probability: 49/100
```

今度はすぐ死ねそうですね。

```
$ cat /dev/kernel-roulette
Survived... sampled value is 86, which is >= 49
$ cat /dev/kernel-roulette
Segmentation fault
```

お、2回目でセグメンテーションフォールトが発生しました。何が起こっているか、dmesgで確認してみましょう。

```
$ dmesg | tail -n 100
[17633.158768] Rust panic @ src/roulette.rs:33
[17633.158774] Boom!
[17633.158793] ------------[ cut here ]------------
[17633.158807] Kernel BUG at ffffffffc04bb24e [verbose debug info unavailable]
[17633.158822] invalid opcode: 0000 [#1] SMP 
# トレース省略
[17633.172695] fbcon_switch: detected unhandled fb_set_par error, error code -16
[17633.173673] fbcon_switch: detected unhandled fb_set_par error, error code -16
[17633.174673] ---[ end trace ff2742f73b763c6d ]---
```

まずRustでpanicが発生していて、その後は、おなじみのLinux kernelのトレース情報です。
KernelのBUG()が呼び出されて、invalid opcodeになっていますね。

kernel-roullete作者の恩情で、panic()ではなく、BUG()を呼ぶようにしてくれているため、Kernelがpanicしてシステムが止まるような事態は発生しません。

ちなみに、この後、roullete driverをrmmodできなくなります。
原因は解明できていないです。

```
$ sudo rmmod roulette
rmmod: ERROR: Module roulette is in use
```

# ソースコード解析

さて、Linux kernel driverの一部として、Rustを使うにはどのようなトリックが必要なのでしょうか？
ここまでで動作を確認したkernel-rouletteのソースコードを解析してみましょう。
ソースファイルやMakefileに丁寧にコメントが添えられているため、非常に解析しやすいソースコードでした。作者の技術レベルの高さが伺えます。
**紹介するソースコードは、1.31.0-nightlyでビルドできるように修正を加えたものです。**

## 構成

まずは、ディレクトリ構成から全体像を把握していきます。`shim.c`と`kbuild.mk`がある以外は、普通のRustプロジェクト、という感じでしょうか。

```
$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── kbuild.mk
├── LICENSE.txt
├── Makefile
├── README.md
├── src
│   ├── io
│   │   └── mod.rs
│   ├── lang.rs
│   ├── lib.rs
│   ├── mem
│   │   └── mod.rs
│   ├── roulette.rs
│   └── shim.c
├── x86_64-unknown-none-gnu.json
└── Xargo.toml

3 directories, 14 files
```

Cargo.tomlを見てみましょう。

```toml:Cargo.toml
[package]
name = "kernel-roulette"
version = "0.1.0"
authors = ["Souvik Banerjee <souvik@souvik.me>"]

[lib]
name = "roulette"
crate-type = ["staticlib"]

[dependencies]
lazy_static = { version = "1.0.0", features = ["spin_no_std"] }
spin = "0.4.9"
rand = { version = "0.4.2", default-features = false }
rlibc = "1.0"
```

crate-typeがstaticlibになっています。これはRustのアーカイブファイルを作って、C言語のオブジェクトファイルとリンクするアレですね、きっと。
検討がついたところで、Makefileを見ましょう。kernel module名は`roulette`です。

```makefile:Makefile
# Configuration that will be passed to sub-make

# Name of the kernel module
export KERNEL_MODULE := roulette

# Path to Linux kernel headers
export KERNEL_BUILD_PATH := /lib/modules/$(shell uname -r)/build

# Find all C and Rust source files
export C_FILES := $(shell find src/ -type f -name "*.c")
export RUST_FILES := $(shell find src/ -type f -name "*.rs") Cargo.toml Cargo.lock

# Define the architecture; this will be used for the LLVM target specification
export UTS_MACHINE = x86_64

# The Rust compiler and cross-compiler
export CARGO=cargo
export XARGO=xargo

# A JSON file specifying an LLVM target
export LLVM_TARGET_SPEC=$(UTS_MACHINE)-unknown-none-gnu.json

# Top-level project directory
export BASE_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))

# The build directory
export BUILD_DIR := build

# The Makefile that is copied to $(BUILD_DIR)
export KBUILD := kbuild.mk

all: $(BUILD_DIR)/Makefile Makefile
# The kbuild makefile has been copied to $(BUILD_DIR), so now we can invoke kbuild from
# the kernel headers.
	$(MAKE) -C "$(KERNEL_BUILD_PATH)" M="$(BASE_DIR)/$(BUILD_DIR)" modules

$(BUILD_DIR)/Makefile : $(KBUILD)
	@mkdir -p "${BUILD_DIR}/src"
	cp "$(KBUILD)" "$(BUILD_DIR)/Makefile"

clean:
# cleanup is really simple, we just blow away the $(BUILD_DIR) and call `xargo clean`
	rm -rf "$(BUILD_DIR)"
	xargo clean
```

kbuild.mkをMakefileにリネームした上で、buildディレクトリに移動して、kbuild.mkをmakeコマンドで叩いています。
kbuildで引き継ぐ環境変数も多数定義していますね。
kbuild.mkを見てみます。

```makefile:kbuild.mk
# Define the Rust target. Xargo will create an ar archive file with the
# following name in the target folder
RUST_TARGET := lib$(KERNEL_MODULE).a

# Enumerate the object files that the C files will compile to
C_OBJECTS := $(patsubst %.c,%.o,$(C_FILES))

# Tell kbuild which files to build
obj-m := $(KERNEL_MODULE).o

# Tell kbuild where the source files are
src := $(BASE_DIR)

# The kernel module will be linked from the C object files and the Rust archive
# The order is important: C objects must come first
$(KERNEL_MODULE)-objs := $(C_OBJECTS) $(RUST_TARGET)

# Strip unused symbols from the input object file
EXTRA_LDFLAGS += --gc-sections --entry=init_module --undefined=cleanup_module
EXTRA_LDFLAGS += $(if $(RELEASE),--strip-all)

# Fix file paths (since this script will be run from the kbuild's working directory)
C_FILES    := $(foreach filepath,$(C_FILES)   ,$(BASE_DIR)/$(filepath))
RUST_FILES := $(foreach filepath,$(RUST_FILES),$(BASE_DIR)/$(filepath))
LLVM_TARGET_SPEC := $(foreach filepath,$(LLVM_TARGET_SPEC),$(BASE_DIR)/$(filepath))

# Determine target directory of cargo's module build
CARGO_BUILD_DIR := $(BASE_DIR)/target/$(UTS_MACHINE)-unknown-none-gnu/$(if $(RELEASE),release,debug)

$(obj)/$(RUST_TARGET): $(RUST_FILES) $(LLVM_TARGET_SPEC) $(BASE_DIR)/$(KBUILD)
# We set RUST_TARGET_PATH because of a bug in cargo/xargo/rustc: the --target flag is relative to the current working
# directory but subsequent invokations of cargo/xargo/rustc might change their working directory. Setting
# RUST_TARGET_PATH ensures that the compiler can find the LLVM target specification.
# We also have to `cd` into $(BASE_DIR) since we are currently in the kernel headers directory.
	(cd $(BASE_DIR); env RUST_TARGET_PATH=$(BASE_DIR) $(XARGO) rustc $(if $(RELEASE),--release) $(if $(VERBOSE),--verbose) --target $(UTS_MACHINE)-unknown-none-gnu -- -C code-model=kernel -C relocation-model=static -C panic=abort)
# After the archive is compiled, copy it to the build directory
	cp "$(CARGO_BUILD_DIR)/$(RUST_TARGET)" $(obj)

$(obj)/%.c : $(BASE_DIR)/%.c $(BASE_DIR)/$(KBUILD)
# KBUILD_CFLAGS is automatically generated by kbuild
	$(CC) $(KBUILD_CFLAGS) -c $(BASE_DIR)/$*.c -o $*.o
```

そこそこボリュームがあるのですが、下を見ると、先ほどの予測が的中していることが分かります。

```makefile
# The kernel module will be linked from the C object files and the Rust archive
# The order is important: C objects must come first
$(KERNEL_MODULE)-objs := $(C_OBJECTS) $(RUST_TARGET)
```

kernel moduleを作るためのオブジェクトファイルは、C言語とRust、それぞれ個別で作られています。
C言語、普通にkernel moduleのオブジェクトファイル作っています。

```makefile
$(obj)/%.c : $(BASE_DIR)/%.c $(BASE_DIR)/$(KBUILD)
# KBUILD_CFLAGS is automatically generated by kbuild
	$(CC) $(KBUILD_CFLAGS) -c $(BASE_DIR)/$*.c -o $*.o
```

Rust、relocation-modelをstaticにする、panicをabortにする、以外は普通のクロスビルドでしょうか。

```makefile
	(cd $(BASE_DIR); env RUST_TARGET_PATH=$(BASE_DIR) $(XARGO) rustc $(if $(RELEASE),--release) $(if $(VERBOSE),--verbose) --target $(UTS_MACHINE)-unknown-none-gnu -- -C code-model=kernel -C relocation-model=static -C panic=abort)
```

RustとC言語ソースファイルの構成をざっと見てみましょう。

```
├── src
│   ├── io
│   │   └── mod.rs
│   ├── lang.rs
│   ├── lib.rs
│   ├── mem
│   │   └── mod.rs
│   ├── roulette.rs
│   └── shim.c
```

| ファイル | 説明 |
| --- | --- |
| shim.c | Linux kernel driver。init()やopen()でRust関数を呼び出す |
| io/mod.rs | printkのラッパーでprint!マクロを定義 |
| mem/mod.rs | kmallocのラッパーでヒープアロケータを実装 |
| lang.rs | panic_handlerおよびalloc_error_handlerを定義 |
| lib.rs | init(), exit()で呼ばれる関数を定義 |
| roulette.rs | 一定確率でpanicする処理を定義 |

## ソースコード

では、コードを見ていきましょう。

### 初期化/終了処理

#### driverロード

まずは、初期化を追っていきます。
kernel driverのエントリポイントなので、C言語からです。
最後の一行以外は、普通のkernel driverです。
キャラクタデバイスを登録しています。これは後ほど改めて解説します。

```c:shim.c
/*
 * The entry points in C
 */
static int _mod_init(void) {

  // Register a character device
  rl_dev_major_num = register_chrdev(0 /* allocate a major number */, rl_device_name, &rl_driver_fops);

  if (rl_dev_major_num < 0) {
    printk(KERN_ALERT "failed to register character device: got major number %d\n", rl_dev_major_num);
    return rl_dev_major_num;
  }

  printk(KERN_INFO "Registered %s with major device number %d\n", rl_device_name, rl_dev_major_num);
  printk(KERN_INFO "Run /bin/mknod /dev/%s c %d 0\n", rl_device_name, rl_dev_major_num);

  return rust_mod_init();
}
init(_mod_init);
```

`rust_mod_init()`は、Rustの関数呼び出しです。shim.c内でexternされています。

```c:shim.c
/*
 * The entry points for the kernel module in Rust. We define
 * entry points in C for the module_init and module_exit macros.
 */
extern int rust_mod_init(void);
```

Rust側の実装を見てみましょう。
`no_mangle`でマングルされないようにしている以外は、普通のRustに見えますね。

```rust:lib.rs
// Entry points
#[no_mangle]
pub extern "C" fn rust_mod_init() -> i32 {
    print!("Panic probability: {}/{}\n", CONFIG.lock().chance, MAX_RAND);
    0
}
```

ただ、裏では色々と動いています。
順番に見ていきましょう。
print!マクロ、ヒープアロケータ、`CONFIG`、の順番に見ていきます。

#### print!マクロ

まずは、`print!`マクロです。
標準ライブラリを使っていないため、printk()をラップする仕組みを自作しています。

```rust:io.rs
/// Like the `print!` macro in the standard library, but calls printk
#[allow(unused_macros)]
macro_rules! print {
    ($($arg:tt)*) => ($crate::io::print(format_args!($($arg)*)));
}
```

このcrateのioモジュールのprint()を読んでいますね。
つまり、同`io.rs`内のprint()関数です。

```rust:io.rs
pub fn print(args: fmt::Arguments) {
    use core::fmt::Write;
    let mut writer = PRINTK_WRITER.lock();
    writer.write_fmt(args).unwrap();
    writer.flush();
}
```

PRINTK_WRITERのロックを取得し、writerが実装しているWrite traitのwrite_fmt()を呼び出しています。
この`PRINTK_WRITER`は、Mutex付きの`KernelDebugWriter`です。
lazy_static!マクロを利用しているので、最初に使われるときに初期化されます。

```rust:io.rs
lazy_static! {
    /// Used by the `print!` and `println!` macros.
    pub static ref PRINTK_WRITER: Mutex<KernelDebugWriter> = Mutex::new(KernelDebugWriter::default());
}
```

`KernelDebugWriter`は、最終的に、C言語のprintk()関数を呼び出します。
printk()関数に渡す文字列は、`Vec<u8>`でバッファリングします。

```rust:io.rs
#[derive(Default)]
pub struct KernelDebugWriter {
    buffer: Vec<u8>,
}

extern "C" {
    // printk()のラッパー
    fn puts_c(len: u64, c: *const u8);
}

// `buffer`を拡張して、`buffer`に文字列を追加する。
impl fmt::Write for KernelDebugWriter {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        // Add the bytes from `s` to the buffer
        self.buffer.extend(s.bytes());
        Ok(())
    }
}

impl KernelDebugWriter {
    // 実際に文字を出力する。出力が終わると、`buffer`をクリアする。
    fn flush(&mut self) {
        // Call c_puts with the string length and a pointer to its contents
        unsafe { puts_c(self.buffer.len() as u64, self.buffer.as_ptr() as *const u8) };
        self.buffer.clear();
    }
}
```

Vecを使っているので、ヒープアロケータが必要です。これは後で説明します。
`write_str()`でbufferに文字列のデータを追加し、`flush()`でC言語の`put_c()`を呼び出します。

C言語の世界に戻ります。`put_c()`はprintk()のラッパーですね。

```c:shim.c
/*
 * A utility function to print a string.
 * NOTE: `str` is not necessarily NULL-terminated.
 */
void puts_c(u64 length, char* str) {
  printk(KERN_DEBUG "%.*s", (int)length, str);
}
```

これで、Rustからkernelログを出力できる仕組みがわかりました。

#### ヒープアロケータ

kmalloc()をラップして、Vecの利用を解禁します。

Rustのベアメタルヒープアロケータについては、こちらの記事で書きました。詳細に興味があればご覧下さい。
[Redox Slab Allocatorで学ぶRustベアメタル環境のヒープアロケータ](https://qiita.com/tomoyuki-nakabayashi/items/e0bd16e9105163cecafb)

print!マクロでは、文字列を`Vec<u8>`のbufferでバッファリングして、printk()に出力を委譲していました。
Vecを使うためには、ヒープ領域を確保する必要があります。ヒープ領域を使うためには、`GlobalAlloc` traitを実装したアロケータを#[global_allocator]アトリビュートを付けて定義します。
アロケータは次のように定義されています。

```rust:lib.rs
// Set up the global allocator
#[global_allocator]
static ALLOCATOR: mem::KernelAllocator = mem::KernelAllocator::new();
```

KernelAllocatorの内部を追っていきます。重要なのは`GlobalAlloc` traitの実装です。

```rust:mem/mod.rs
#[derive(Default)]
pub struct KernelAllocator;

impl KernelAllocator {
    pub const fn new() -> Self {
        Self {}
    }
}

// Use shim functions to avoid hardcoding the GFP_KERNEL constant
#[allow(dead_code)]
extern "C" {
    fn kmalloc_c(size: usize) -> *mut u8;
    fn kfree_c(ptr: *mut u8);
    fn krealloc_c(ptr: *mut u8, size: usize) -> *mut u8;
}

unsafe impl GlobalAlloc for KernelAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        // A side effect of the buddy allocator is that allocations are aligned to
        // the power-of-two that is larger than the allocation size. So if the
        // request needs to be aligned to something larger than the allocation size,
        // we can just pass max(size, align) to kmalloc to get something reasonable
        // at the cost of a few extra wasted bytes.
        use core::cmp::max;
        let p = kmalloc_c(max(layout.size(), layout.align()));
        if p.is_null() {
            0 as *mut u8            
        } else {
            p
        }
    }

    unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
        kfree_c(ptr);
    }
}
```

`alloc()`では、`kmalloc_c()`を呼び出しています。これはC言語のkmallocのラッパーです。

```c:shim.c
/*
 * Re-exported memory management functions
 */
void* kmalloc_c(size_t size) {
  return kmalloc(size, GFP_KERNEL);
}
```

この薄いラッパー1枚を作るだけで、VecやBoxなどの便利機能が解禁となるわけです。これは朗報ですね！

#### CONFIG

Rust側の初期化処理を再掲載します。

```rust:lib.rs
// Entry points
#[no_mangle]
pub extern "C" fn rust_mod_init() -> i32 {
    print!("Panic probability: {}/{}\n", CONFIG.lock().chance, MAX_RAND);
    0
}
```

`CONFIG.lock().chance`はkernel panicが発生する確率です。どのように初期化されているか見ていきましょう。

```rust:roullete.rs
lazy_static! {
    // CONFIGはRouletteConfigをMutexで包んでいます
    pub static ref CONFIG: Mutex<RouletteConfig> = Mutex::new(RouletteConfig::new());
}

#[derive(Default)]
pub struct RouletteConfig {
    pub chance: u8,
}

impl RouletteConfig {
    pub fn new() -> Self {
        // MIN_RAND(0)からMAX_RAND(100)の範囲で乱数を生成します
        let chance = RNG.lock().gen_range(MIN_RAND, MAX_RAND);
        RouletteConfig { chance: chance }
    }
}
```

正確には、0～99の範囲で乱数を生成します。APIの解説は次の通りです。
> Generate a random value in the range [low, high), i.e. inclusive of low and exclusive of high.

`RNG`は次のように定義されています。JitterRngという乱数生成器をMutexで包んでいます。

```rust:roullete.rs
lazy_static! {
    static ref RNG: Mutex<rand::JitterRng> = Mutex::new(rand::JitterRng::new_with_timer(|| unsafe { nanosecond_timer_c() }));
}
```

[JitterRng](https://doc.rust-lang.org/nightly/nightly-rustc/rand/jitter/struct.JitterRng.html)はCPU実行時間のジッタとメモリアクセスのジッタとを使った、乱数ジェネレータ、とのことです。
`new_with_timer()`でインスタンスを生成しています。これは、`no_std`環境でJitterRngを使う場合のAPIです。

```rust:jitter.rs
pub fn new_with_timer(timer: fn() -> u64) -> JitterRng
```

> Create a new JitterRng. A custom timer can be supplied, making it possible to use JitterRng in no_std environments.

`new_with_timer()`で渡しているクロージャでは、C言語の`nanosecond_timer_c()`を呼んでいます。
この中ではjiffiesを取得します。

```c:shim.c
/*
 * This isn't really a nanosecond timer but its close enough.
 */
u64 nanosecond_timer_c(void) {
  return get_jiffies_64();
}
```

少し整理すると、`RNG`はMutexで包まれた乱数ジェネレータです。
`RNG.gen_range(MIN_RAND, MAX_RAND)`を呼び出すと、クロージャでjiffiesを取得し、0から99の範囲で乱数を生成します。
`CONFIG`はインスタンス生成時に、`RNG.get_range()`を呼び出し、panic確率である`chance`を初期化します。

終了処理は特に難しいことをしていないので、省略します。

### ロシアンルーレット実行

それでは、肝心のロシアンルーレット実行部分を見ていきます。
記事の最初の方でやった通り、/dev/kernel-rouletteをcatしたとき(より正確にはopen()呼び出し時)に乱数を生成し、chanceより小さな値が出るとpanicします。

```
$ cat /dev/kernel-roulette
Survived... sampled value is 86, which is >= 49
$ cat /dev/kernel-roulette
Segmentation fault
```

残念なことに、デバイスファイルの操作はほとんどC言語で処理しています。
まず、キャラクタデバイスですが、`_mod_init()`で登録していました。

```c:shim.c
static int _mod_init(void) {

  // Register a character device
  rl_dev_major_num = register_chrdev(0 /* allocate a major number */, rl_device_name, &rl_driver_fops);
...
```

キャラクタデバイスドライバには、open(), release(), read()が実装されています。

```c:shim.c
static int rl_device_open(struct inode* inode, struct file* filp);
static int rl_device_release(struct inode* inode, struct file* filp);
static ssize_t rl_device_read(struct file* filp,	/* see include/linux/fs.h   */
                              char __user* buffer,	/* buffer to fill with data */
                              size_t length,	/* length of the buffer  */
                              loff_t* offset /* the file offset */);
static struct file_operations rl_driver_fops = {
  .read = rl_device_read,
  .open = rl_device_open,
  .release = rl_device_release,
};
static int rl_dev_major_num = 0;
static const char rl_device_name[] = "kernel-roulette";
```

release()とread()からはRustを呼んでいないため、open()だけ解説します。
Rustを呼んでいるのは、sample()の部分です。

```c:shim.c
static int rl_device_open(struct inode* inode, struct file* filp) {
  u8 sampled;
  struct rl_state* state;
...
  // Get a sample from Rust
  sampled = sample(); // may panic!
...
  // read()で読みだすデータをここで作成します
  // Format data and store in string
  snprintf(state->data, MAX_BUFFER_SIZE, "Survived... sampled value is %d, which is >= %d\n", sampled, get_chance());
...
  // Set private_data to the state we allocated
  filp->private_data = state;
  return SUCCESS;
}
```

Rustのsample()は次の通りです。

```rust:roulette.rs
#[no_mangle]
pub extern "C" fn sample() -> u8 {
    let sampled = RNG.lock().gen_range(MIN_RAND, MAX_RAND);
    if sampled < CONFIG.lock().chance {
        panic!("Boom!");
    } else {
        sampled
    }
}
```

新たに生成した乱数が、chanceより小さければ、"Boom"を出力して、panicします。
panic_handlerは次のようになっています。

```rust:lang.rs
#[panic_handler]
#[no_mangle]
pub fn rust_begin_panic(info: &PanicInfo) -> ! {
    // Print the file and line number
    if let Some(location) = info.location() {
        println!("Rust panic @ {}:{}",
            location.file(), location.line());
    }

    // Print the message and a newline
    if let Some(message) = info.message() {
        println!("{}", message);
    }

    unsafe {
        // In a real kernel module, we should use abort() instead of panic()
        abort() // replace with panic_c() if you want
    }
}
```

最終的にC言語の`abort()`を呼んでいます。abort()はBUG()を呼び出します。

```c:shim.c
/*
 * Define the abort() function. We use the BUG() macro, which generates a ud2 instruction.
 */
void abort(void) {
  BUG();
}
```

ちなみに、abort()の代わりに、`panic_c()`を呼び出すと…

```c:shim.c
void panic_c(void) {
  panic("Panic!");
}
```

本当にフリーズします。

以上で主要な処理の解析は完了です。
いかがでしたでしょうか。

# まとめ

- Linux kernel moduleからRustを呼べます。Rustをライブラリでビルドしておき、C言語のkernel module objectとリンクするだけです。
- kmalloc()のラッパーを用意することで、Vecなど、ヒープ領域を使う機能が使えます。
- printk()のラッパーを用意することで、print!マクロを作って、標準ライブラリのように使えます。
- 残念ながらデバイスファイルの操作はほとんどC言語でした。

# 参考

[kernel-roulette](https://github.com/souvik1997/kernel-roulette)