Linux Kernelに入ってきそうなRustをさらに読む

先の記事でLinux kernelに対するRustのアプローチを紹介したが,最近になってまた大きな進展があったので取り上げる.

前回のパッチでは,Rustによるdriver等の開発が可能になったが,panic等のRust特有のメモリの扱いに対してはあまり受け入れられていなかった.

しかし今回の更新により,stableコンパイラへの対応やallocの改善,Android用のBinder IPCのサポートなどが追加された.

Rust-for-Linux

GitHubにRust-for-Linux/linuxというリポジトリがあり,ここでLinux kernelへのRust supportが行われている.

また,kernel/git/next/linux-nextにもマージされている

本記事は現在マージされている時点での情報を解説する.

Build Document

とりあえず,make htmldocsしてドキュメントをビルドし,rust/quick-start.hmlを読むか,rstをそのまま脳内でパースする.

以下はドキュメントのまとめ

ドキュメントは自分で読む.という読者はこちらまでスキップして良い.

Quick Start

rustc

必要なrustcのバージョンは1.57.0で,これ以外はまだサポートされていない.

rustup override set 1.57.0でインストールする.

Rust standard library source

ビルドシステムのクロスコンパイルのため,coreallocが必要になる.

rustup component add rust-src

でインストールする.

libclang

bindgen等のためにllvmのインストールが必要.

bindgen

c bindingの生成の為にbindgenをインストールする

cargo install --locked --version 0.56.0 bindgen

Developing

Rustによるkernel developingの為には以下のツールも必要となる

rustfmt

自明

rustup component add rustfmt

clippy

linter

rustup component add clippy

cargo

自明

rustdoc

自明

rust-analyzer

rust-analyzer最高

Configuration

CONFIG_RUSTをmenuのGeneral setupから有効化する必要がある.

また,Kernel hacking -> Sample kernel code -> Rust samplesから有効にすることでいくつかあるサンプルをビルドできる.

Building

LLVMを使ってビルドする事が推奨されている.

make LLVM=1

LLVMによって完全なサポートがされていないアーキテクチャではmake CC=clangでビルドする.

Hacking

samples/rust/以下のソースコードやrust/以下のRustサポート,またはKernel hacking menuのRust hackingを読むと更に詳しくわかる.

もしGDB/Binutilsを使っていて,Rustのシンボルが正しくdemangleされない場合,ツールチェインがRustの新しいv0 manglingをサポートしていない可能性がある.

そんな時は

Coding

kernelでRustのコードを書く方法

Coding style

rustfmtによってフォーマットを行う

make LLVM=1 rustfmt

CIなどで,フォーマットが行われているかのチェックができる

make LLVM=1 rustfmtcheck

Extra lints

clippyを使う

make LLVM=1 CLIPPY=1

Abstractions vs. bindings

AbstractionsはCのkernelの機能をwrapするRustのコードを指す.

bindingとは,Cの関数や型をRustで再宣言したもので,Cで定義された関数や型を利用するためにはbindingを作成する必要がある.

まだ全てのkernel APIや概念を抽象化できているわけではないが,今後もカバー範囲を広げていく予定である.

“Leaf”モジュール(例: ドライバなど)は,C言語のbindingを直接仕様するべきではなく,かわりにサブシステムは必要に応じて可能な限り安全な抽象化機能を提供すべきである.

Conditional compilation

Kernel configに基づく条件付きコンパイルが可能

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

Documentation

Docs

Docs

Rustのコードは,通常のCのコードと異なり,kernel-docを介してdocumentationされずに,rustdocが使われる.

Reading the docs

読み慣れたRustのドキュメント

localで生成する場合は

make LLVM=1 rustdoc

Writing the docs

いつものRustのDocumentation commentと基本的な文法は同じ

/// Returns the contained [`Some`] value, consuming the `self` value,
/// without checking that the value is not [`None`].
///
/// # Safety
///
/// Calling this method on [`None`] is *[undefined behavior]*.
///
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
///
/// # Examples
///
/// ```
/// let x = Some("air");
/// assert_eq!(unsafe { x.unwrap_unchecked() }, "air");
/// ```
pub unsafe fn unwrap_unchecked(self) -> T {
        match self {
                Some(val) => val,

                // SAFETY: the safety contract must be upheld by the caller.
                None => unsafe { hint::unreachable_unchecked() },
        }
}

こんな感じ.

いくつかルールがある

Arch Support

Architecture Level of support Constraints
arm Maintained armv6互換のみ,RUST_OPT_LEVEL>=2
arm64 Maintained なし
powerpc Maintained ppc64leのみ,RUST_OPT_LEVEL<2 CONFIG_THREAD_SHIFT=15
riscv Maintained riscv64のみ
x86 Maintained x86_64のみ

Code

前回と比べると,rust/以下にallocが追加されるなどの変化がある.

allocについては前から議論されており,allocに失敗した際にpanicを起こす挙動が問題視されており,その改善の為だと思われる.

samples/rust/以下にはいくつかのサンプルがある.

$ ls samples/rust/
Kconfig  Makefile  rust_chrdev.rs  rust_minimal.rs  rust_miscdev.rs  rust_module_parameters.rs  rust_print.rs  rust_random.rs  rust_semaphore.rs  rust_semaphore_c.c  rust_stack_probing.rs  rust_sync.rs

前回はdrivers/char/rust_example.rsだけだった(気がする)が,結構種類が増えているので参考にできそう.

configのRust samplesを有効にするとビルドされる.

Rust minimal

samples/rust/rust_minimal.rs

// SPDX-License-Identifier: GPL-2.0

//! Rust minimal sample

#![no_std]
#![feature(allocator_api, global_asm)]

use kernel::prelude::*;

module! {
    type: RustMinimal,
    name: b"rust_minimal",
    author: b"Rust for Linux Contributors",
    description: b"Rust minimal sample",
    license: b"GPL v2",
}

struct RustMinimal {
    message: String,
}

impl KernelModule for RustMinimal {
    fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
        pr_info!("Rust minimal sample (init)\n");
        pr_info!("Am I built-in? {}\n", !cfg!(MODULE));

        Ok(RustMinimal {
            message: "on the heap!".try_to_owned()?,
        })
    }
}

impl Drop for RustMinimal {
    fn drop(&mut self) {
        pr_info!("My message is {}\n", self.message);
        pr_info!("Rust minimal sample (exit)\n");
    }
}

前回の記事を読むとなんとなくわかるはず.

KernelModule traitは,rust/kernel/lib.rsに定義されており,moduleのentrypointとなる(ロード時に実行される)initが定義されている

/// The top level entrypoint to implementing a kernel module.
///
/// For any teardown or cleanup operations, your type may implement [`Drop`].
pub trait KernelModule: Sized + Sync {
    /// Called at module initialization time.
    ///
    /// Use this method to perform whatever setup or registration your module
    /// should do.
    ///
    /// Equivalent to the `module_init` macro in the C API.
    fn init(name: &'static str::CStr, module: &'static ThisModule) -> Result<Self>;
}

RustMinimalでは,pr_info!を行っているが,これはkernel/print.rsで定義されている.

ここには

が定義されていて,それぞれのprintk(のレベル(同名マクロ))に対応している.

Rust random

ここでは例として,random deviceのRustによる実装を紹介する.

コードはsamples/rust/rust_random.rs

// SPDX-License-Identifier: GPL-2.0

//! Rust random device
//!
//! Adapted from Alex Gaynor's original available at
//! <https://github.com/alex/just-use/blob/master/src/lib.rs>.

#![no_std]
#![feature(allocator_api, global_asm)]

use kernel::{
    file::File,
    file_operations::FileOperations,
    io_buffer::{IoBufferReader, IoBufferWriter},
    prelude::*,
};

#[derive(Default)]
struct RandomFile;

impl FileOperations for RandomFile {
    kernel::declare_file_operations!(read, write, read_iter, write_iter);

    fn read(_this: &Self, file: &File, buf: &mut impl IoBufferWriter, _: u64) -> Result<usize> {
        let total_len = buf.len();
        let mut chunkbuf = [0; 256];

        while !buf.is_empty() {
            let len = chunkbuf.len().min(buf.len());
            let chunk = &mut chunkbuf[0..len];

            if file.is_blocking() {
                kernel::random::getrandom(chunk)?;
            } else {
                kernel::random::getrandom_nonblock(chunk)?;
            }
            buf.write_slice(chunk)?;
        }
        Ok(total_len)
    }

    fn write(_this: &Self, _file: &File, buf: &mut impl IoBufferReader, _: u64) -> Result<usize> {
        let total_len = buf.len();
        let mut chunkbuf = [0; 256];
        while !buf.is_empty() {
            let len = chunkbuf.len().min(buf.len());
            let chunk = &mut chunkbuf[0..len];
            buf.read_slice(chunk)?;
            kernel::random::add_randomness(chunk);
        }
        Ok(total_len)
    }
}

module_misc_device! {
    type: RandomFile,
    name: b"rust_random",
    author: b"Rust for Linux Contributors",
    description: b"Just use /dev/urandom: Now with early-boot safety",
    license: b"GPL v2",
}

File Operation

FileOperationsについては,前回の記事でも紹介したとおり,include/linux/fs.hstruct file_operationsをTraitで表現したもの.

これはrust/kernel/file_operations.rsで定義されていて,declare_file_operationsで使用するfile operationを指定する(struct ToUse).(空だと全てがfalseになり,それぞれにnull pointerが設定される)

read

random deviceへのreadを実装している.

かなり読みやすい.重要なのはkernel::random::getrandom及びkernel::random::getrandom_nonblockで,rust/kernel/random.rsに定義されている.

それぞれがbindingを利用してdrivers/char/random.cwait_for_random_bytesget_random_bytesを呼び出している.

bindgenにより,ビルド時にrust/bindings_helpers_generated.rsが生成され,bindings::*は基本的にそこで定義されている.

write

こっちもbindingを利用してadd_device_randomnessを呼び出す.

module_misc_device

これはいい感じにmodule!を呼び出すマクロ

install

みんなも実際にビルドして使ってみてくれよな!!!(ビルド遅すぎてアドカレに間に合わんので終わったら追記します)


この記事はIPFactory Advent Calendar 2021の12/15分,

Linux Advent Calendar 2021の12/15分,

Rust Advent Calendar 2021 3の12/15分です.

IPFactoryというサークルについては,こちらをご覧下さい.