LinuxカーネルのRustアトミック型
「え、Rust製のLinuxカーネルモジュール、すでに普及してるの?」
LinuxカーネルのRust製モジュールが普及するのはまだ先だから、厳しく精査されないだろうと安心して適当な記事を書いてきました。ところが、今年6月にリリースされたAndroid 16には、Rustで実装されたカーネルモジュールが含まれており、すでに広く普及していました。
今回は、カーネル用に独自実装されたRustのアトミック型について紹介します。
メモリモデル
Linuxカーネルは、Rustの標準ライブラリのアトミック型を使わず、独自のアトミック型を実装しています。その大きな理由は、Rustの標準のアトミック型が前提とするメモリモデルとカーネルのメモリモデルLKMM(Linux kernel Memory Model)が異なるからです。
「メモリモデル?」という方も読み進められる技術レベルのことしか書かれてないので、安心してください。メモリモデルは、複数CPU(複数スレッド)から見たときに、メモリの読み書きがどの順序で観測されうるか、そして同期操作が何を保証するかを定めたルールだと考えてください。命令はコンパイラ最適化やCPUの再順序化があるため、直感どおりに「上から実行される」とは限りません。
メモリモデルの前提が揃っていないと、例えばRustとCが同じメモリ領域に並行してアクセスする場合に、読み書きが観測される順序や可視性についての理解が食い違い、開発者が意図した同期が成立しない可能性があります。
Rust自体にLKMMサポートを加える提案もありましたが、最終的にはLinuxカーネル側で独自のアトミック型を実装する設計に落ち着きました。
汎用アトミック型
標準ライブラリのアトミック型は、AtomicI32やAtomicI64のように、プリミティブ型ごとに専用の型が用意されています(AtomicBoolやAtomicUsizeなども同様)。一方、カーネル用のアトミック型は Atomic<T>という型パラメータ付きの形で定義されています。
「お、標準ライブラリよりも使いやすい。いろいろな型をアトミックに?」と思った方もいるかもしれませんが、カーネル用APIがアプリケーション用APIよりも使いやすいなどということはありえません。Atomic<T>は「任意の型をアトミックにできる」わけではありません。Tは、内部表現としてi32もしくはi64に対応付けられる型に限られます。例えばisize(ビット幅はアーキテクチャ依存)や、#[repr(i32)]か#[repr(i64)]を付けたenumなどが対象です。
これは、カーネルのC側で長年使われてきた主要な整数向けのアトミックな型が32ビットのatomic_tと64ビットのatomic64_tであり、Rust側の実装もそれら向けに用意されたCのAPIを薄くラップして提供しているためです。
C側のアトミックAPIは性能を優先して多くが インライン関数やマクロとして提供されています。そのためRust側から利用する場合、バインディングの都合で呼び出しがラッパー経由になり、純粋な性能面では不利になる可能性があります。
それでもこの設計が選ばれているのは、速度面の最適化よりも、カーネルが長年使い続けてきた同期機構のセマンティクスにそのまま乗れることを重視しているためです。Rust側で同等の命令列を自前で実装しようとすると、アーキテクチャ固有命令や各種バリア、周辺機構との整合まで含めて、カーネル固有の前提を継続的に再現し続ける必要が出てきます。
その結果、性能よりも「Cとの一貫性を保ちやすい」こと、そしてコードを保守しやすいことを重視して、CのアトミックAPIへ1対1でマッピングする方針になっています。
独自アトミック型のAPIは、標準ライブラリのアトミックAPIと似せてあり、違和感なく使えるはずです。
i8もアトミックに
Linuxカーネルの開発でRustを使い始めた、という理由もなく困難な道を選んできた人を除けば、標準ライブラリで使っていたi8 やi16 のアトミック型も同じ感覚で使いたい、と考えるかもしれません。そのような希望(未確認)に対応すべく、筆者はこの点の改善に取り組んでいるため、ここでは課題を整理します。
上で説明したように、カーネルの Atomic<T>はCのアトミックAPIに1対1でマッピングする方針で設計されています。しかしC側には、i8 や i16に対応するアトミック型やAPIは用意されていません。
アトミック操作は専用のCPU命令に依存するものが多く、アーキテクチャごとに実装や検証のコストが増えがちです。Cと一貫性のあるRustのアトミック実装を各アーキテクチャで維持する負担を考えると、Rust側で独自に再実装するアプローチは関係者の合意を得にくく、C側の既存実装をできるだけ活用する方針のほうが現実的です。
まとめ
Linuxカーネル内のRustでは、標準ライブラリのアトミック型をそのまま使うのではなく、LKMMを前提にしたカーネル独自のアトミック型が採用されています。さらに、Arcのように内部で標準のアトミック型を使う型も、Cのアトミック型を使う独自実装へ置き換えられています。
同じ流れとして、時間関係の型もカーネル独自の実装に置き換えられたことも紹介しました。標準のRustライブラリからカーネルの前提で正しく動く独自ライブラリへの置き換えが進み、Rustでドライバを実装するための現実的な環境が整ってきています。
NTT研究所では、低レイヤの開発に興味がある仲間を募集中です。連絡お待ちしています。