- RISC-Vアーキテクチャ上でタイムシェアリングOSのプロトタイプカーネルを実装した経験を共有
- タイムシェアリング(time-sharing)カーネルの概念と動作方式を、実践重視で説明し、Cの代わりにZigで実装して再現性を高めている
- カーネルとユーザーコードを単一バイナリにまとめるunikernelアプローチを採用し、コンソール出力とタイマー制御のためにOpenSBIを活用するレイヤリングを採っている
- スレッドはユーザーモード(U-mode)で実行され、カーネルはスーパーバイザモード(S-mode)でタイマー割り込みを通じてコンテキストスイッチを行い、システムコールで境界をまたぐ
- 核となるのは、割り込みプロローグ/エピローグが積み上げたスタックフレームを差し替え、別スレッドのレジスタ集合とCSRを復元することで実行の流れを切り替える手法
- QEMU仮想マシンと最新のOpenSBIを基盤に、誰でも再現可能な学習環境を提供し、スレッド・プロセス・コンテナなど仮想化スペクトラムを概念的に結び付けて、教育・実習向けの基礎資料として価値がある
概要
- RISC-Vアーキテクチャ上でタイムシェアリングOSカーネルを自ら実装した過程を紹介
- 主な読者は、システムソフトウェアやコンピュータアーキテクチャの初学者、学生、そして低レベル動作原理の理解に関心のあるエンジニア
- この実験では従来のC言語ではなくZig言語を用いることで、実習の再現性を高め、導入も簡単にしている
- 最終コードはpopovicu/zig-time-sharing-kernelリポジトリで公開されており、本文とは多少のずれがある場合がある
- 記事中のコード抜粋よりも、リポジトリ版を単一の信頼できる情報源とみなすことを推奨
- 実習時には、リポジトリのリンカスクリプト・ビルドオプションを基準に環境を合わせられる利便性がある
Recommended reading
- 記事は、レジスタ、メモリアドレッシング、割り込みなどのコンピュータアーキテクチャ基礎を前提としている
- 事前資料としてBare metal on RISC-V、SBIブート過程、タイマー割り込みの例を推奨
- マイクロLinuxディストリビューションの記事は、カーネル空間・ユーザー空間分離の思想を理解するうえで任意に役立つ
Unikernel
- アプリケーションとOSカーネルを単一実行ファイルとしてリンクするunikernel構成を採用
- ランタイムのローダ・リンカの複雑さを回避し、ユーザーコードをカーネルとともにメモリへ読み込む単純化を実現
- 教育および再現性の観点から、配布の単純さと環境の一貫性を確保できる利点がある
SBI layer
- RISC-VはM/S/Uモードの権限モデルを使い、この実験ではOpenSBIをM-modeに置き、カーネルをS-modeで動作させる
- コンソール出力とタイマーデバイス制御をSBIに委譲して移植性を確保
- SBIが使えない場合はUART MMIOをフォールバックとして使うが、実習では最新のOpenSBI利用を推奨
Goal for the kernel
- 単純化のため静的スレッドのみをサポートし、スレッドは終了しない関数として構成
- スレッドはU-modeで実行され、S-modeカーネルにシステムコールを送る
- タイマーティックごとに別スレッドへ切り替えられるよう、シングルコア前提のタイムシェアリングスケジューリングを実装
Virtualization and what exactly is a thread
- タイムシェアリングスレッディングは、プログラミングモデルを変えずに単一コア上で複数の処理を並行させる仮想化方式
- 協調的スケジューリングと異なり、明示的なyieldなしにタイマー割り込みで切り替えが起こる
- スレッドはそれぞれ他から触れられないレジスタ集合とスタックを持ち、それ以外のメモリは共有可能
The stack and memory virtualization
- スレッドは独立したスタックを持つ必要があり、呼び出し規約上、ローカル変数やraの保存など実行コンテキスト維持に不可欠な要素となる
- 仮想化スペクトラムはスレッド < プロセス < コンテナ < VMへと続き、分離レベルと**視界(view)**が異なる
- Linuxにおけるコンテナは、chroot, cgroupsなどのカーネル機構の組み合わせで実装される
Virtualizing a thread
- この実験における最小限の仮想化目標は、プログラミングモデル不変、レジスタ・一部CSR保護、個別スタック割り当て
- レジスタの見え方が保護されなければ意味のある計算が不可能になる理由を強調
- 初期a0などをスタックにシードしておくことで、スレッド開始時の引数受け渡しを簡潔に処理
Interrupt context
- 割り込みは、プロローグ/エピローグでレジスタをスタックに保存・復元する関数呼び出しに似たモデルとして理解できる
- 非同期のタイマー割り込みでレジスタが壊れないよう、保存規約の遵守が必須
- 例のアセンブリではx0–x31の保存に加え、sstatus, sepc, scause, stvalなどのCSRまで保存・復元する
Implementation (high-level)
Leveraging the interrupt stack convention
- 割り込みルーチン本体はプロローグとエピローグの間にあり、spを別のメモリ領域へ差し替えると、別コンテキストのレジスタ集合が復元される
- これはそのままコンテキストスイッチに相当し、この実験のタイムシェアリング実装の中核アイデア
- タイマー割り込みが周期的に介入し、メインの流れと割り込みの流れを交互に実行する
Kernel/user space separation
- S-modeカーネル / U-modeユーザーの境界を維持し、割り込みとシステムコール処理はS-modeトラップハンドラで行う
- ブートはM-modeのOpenSBI→ S-modeカーネル初期化→ U-modeスレッド開始の順で進行
- 周期的なタイマー割り込みがスケジューリング・コンテキストスイッチを可能にする
Implementation (code)
Assembly startup
startup.SではBSS初期化と初期スタックポインタ設定の後、Zigのmainへジャンプする最小シーケンスを構成
- カーネルエントリポイントはC ABIとの連携のため
export規約を使う
Main kernel file and I/O drivers
kernel.zigのmainは、まずOpenSBIコンソール機能を確認し、失敗時はUART MMIOへフォールバックする
sbi.debug_printはECALLプロトコルに合わせてa0/a1/a6/a7レジスタを設定して呼び出す
- タイマー設定後、S-mode割り込みハンドラを登録し、ティックを有効化する
S-mode handler and the context switch
- ハンドラはZigの
naked規約で書かれ、CSR保存を含む完全なプロローグ/エピローグを手動で構成
- 本体で
handle_kernel(sp)を呼び出し、返されたspに差し替えることで切り替えの有無を決定
scauseでU-mode ECALLかタイマー割り込みかを判定して分岐処理する
The user space threads
- ユーザーコードはカーネルとともに単一バイナリに含まれ、サンプルスレッドは文字列出力 → 遅延ループを繰り返す
syscall.debug_printはa7に64番のシステムコール番号、a0/a1にバッファ/長さを入れてECALLを実行
- スレッド初期化時にスタックへ復帰アドレス・初期レジスタ値をシードして、最初の復帰時にすぐ引数を使えるようにする
Running the kernel
- ビルドは
zig build、実行はQEMUでvirtマシン + nographic + OpenSBI fw_dynamicを指定して行う
- 起動時にはOpenSBIバナーの後、スレッドIDごとの周期的な出力が交互に現れる
-Ddebug-logs=trueでビルドすると、割り込みの発生源、現在のスタック、キューイング・デキューイングのログが詳細に表示される
Conclusion
- この実験はRISC-V + OpenSBI + Zigの組み合わせで教育用カーネルを現代化し、再現性と可読性を高めている
- 最低限のエラー処理や過剰確保されたスタックなどの単純化はあるが、コンテキストスイッチの本質と権限分離の学習に焦点を当てている
- 実機への移植性は、リンカ・ドライバ定数の調整とSBI可用性の確保を前提に可能
追加メモ: 仮想化スペクトラムの整理
- Threads: レジスタ・スタックの仮想化が中心で、メモリ共有の可能性が高い
- Process: アドレス空間仮想化によるメモリ分離があり、内部に複数スレッドを含めることができる
- Container: ファイルシステム・ネットワーク名前空間など環境の視界を組み合わせて構成される分離単位
- VM: ハードウェア全体の完全仮想化を指向する
主要実装ポイントの要約
- 割り込みスタックの差し替えでコンテキストスイッチを実現
- S-modeトラップハンドラでCSRを含む全状態を保存・復元
- SBI優先、UART MMIOフォールバックの出力経路二重化
- 静的スレッド・シングルコア・タイムスライス中心の単純なスケジューリング
- ECALLベースのシステムコールでU/S境界を明確化
まだコメントはありません。