Rust In Linux Kernel 2
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
ビルドシステムのクロスコンパイルのため,coreとallocが必要になる.
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
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をサポートしていない可能性がある.
そんな時は
- 新しいバージョンをインストールする(GDB >= 10.2, Binutils >= 2.36)
- 一部のバージョンのGDB(ex: GDB 10.1)は
CONFIG_DEBUG_INFOに埋め込まれた事前にdemangleされたシンボル名を使用できる
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
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() },
}
}
こんな感じ.
いくつかルールがある
- 最初の段落は項目が何をするかを完結に説明する一文で,詳しい説明は追加の段落に入れる.
unsafeな関数は,安全に使う方法を# Safetyに書く必要がある.- 関数が
panicする可能性がある場合,# Panicsにその条件を書く必要がある.通常はpanicを発生させないようにResultを返すべきである. - 使用例は
# Examplesに書く. - Rustの関数や型,定数は適切にリンクされるべきである.
- 任意の
unsafeブロックには,先行してコードの内容が安全である事を示す// SAFETY:で始まるコメントをつける必要がある.
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で定義されている.
ここには
pr_emerg(KERN_EMERG)pr_alert(KERN_ALERT)pr_crit(KERN_CRIT)pr_err(KERN_ERR)pr_warn(KERN_WARNING)pr_notice(KERN_NOTICE)pr_info(KERN_INFO)pr_debug(KERN_DEBUG)pr_cont(KERN_CONT)
が定義されていて,それぞれの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.hのstruct 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.cのwait_for_random_bytesやget_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というサークルについては,こちらをご覧下さい.
Comments