- 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境界を明確化
1件のコメント
Hacker Newsのコメント
同様のことは "Operating System in 1000 Lines of Code" で「パッケージ」形式として体験できる。以前にZigで追ってみたことがあり(CのコードスニペットをZigに変換しながら進めた)、とても楽しかった。自分のコードとVODはここにある: https://github.com/kristoff-it/kristos/
記事の著者が別途投稿した内容: https://news.ycombinator.com/item?id=45236479。著者本人がTiny OS Kernelを再実装しており、特にRISC-VとOpenSBI環境で試してみたくて、従来のCの代わりにZigを使ったとのこと。実際のところCやRustでも簡単に追えるはずだと考えている。全体の過程はやや粗削りだが、OSカーネル開発とコンピュータアーキテクチャを学ぶ第一歩のための実験と入門用とのこと。週末に楽しく試してみるのによいプロジェクトだと思う。全体のウォークスルーとGitHubリンクは上で確認できる
こういうプロジェクトは本当に印象的だ。Linuxも結局はひとつのカーネルにすぎないが、その作業がオープンソースUnixを数十億台の機器にインストールできる道を開いた。本当にすごいことだと思う
さらに面白いのは、Linuxの最初の配布時にTorvaldsがメールで「ただの趣味でやっているだけで、GNUのように大きくてプロフェッショナルなものにはならないだろう」と書いていた事実だ https://groups.google.com/g/comp.os.minix/c/dlNtH7RRrGA/m/SwRavCzVE7gJ
私はこの種のプロジェクトを<i>そこまで</i>印象的だとは思わない。ミニマルなマルチタスクカーネルの作り方はすでに何十年も前に確立された道筋だ。起動して簡単な作業をするだけのカーネルを作るのは、ある程度の賢さと粘り強さがあればできる。RISC-Vでやるとx86より少し複雑ではあるが、ハードウェア初期化情報は簡単に手に入る(https://wiki.osdev.org/RISC-V_Meaty_Skeleton_with_QEMU_virt_board 参照)。このプロジェクトについても、著者自身が「OSの授業でやった演習を再度やったもの」だと明かしている。ソフトウェア工学の学位がある人なら誰でもやり遂げられるレベルだと思う。もちろんバグや未完成の部分はあるだろうが、マルチプロセスやMMUによるプロセス分離そのものは、もはや難しいことではない
Linuxと同じくらい、1984年にStallmanがCコンパイラとUnixユーティリティを作り始めたことも、数十億台のマシンにオープンソースUnixを導入する道を開いたと言える
ZigはOS開発に本当に向いているし、RISC-Vも同様だ。私も同じ課題をx86で始めたことがあるが、レガシーなボイラープレートが多すぎてすぐに疲れてしまった。RISC-V側にはそういうものがほとんどなく、開発を始めるのがずっと楽になる https://github.com/Fingel/aeros-v
x86で始めるとしても、ブートローダをうまく使えばボイラープレートはそれほど多くないと思う。マルチブートローダが通常リアルモードを引き受けてくれるし、多くの人はプロテクトモードを望むので、いくつかのテーブルを設定してジャンプするだけで済む。レガシー割り込みコントローラを切りたいなら追加で手を入れる必要はあるが、デスクトップPCで起動を試せる利点がある(コンソールインターフェースには注意が必要)。私の趣味OSではBIOSブートといくつかのVGA機能を使っていたが、互換性が悪くて苦労した。シリアルコンソールのほうがずっと簡単だが、最近のコンピュータにはシリアルポートがないことが多い
実質的にはObject PascalやModula-2の安全性を再び持ち出して、C構文で包み直したようなものだ。Cに特別な点があったわけではなく、Unixのライセンスのおかげで広まったにすぎない
私も自分でやってみたいのだが、RISC-Vカーネルをどんな環境で動かしているのか気になる。Qemuだけを使っているのか、それとも実機でおすすめがあるのか教えてほしい
RISC-V ISAは本当にアクセスしやすいと思う。文書も素晴らしいし、例もとても多く、エミュレータもかなり多い。非圧縮のマシンコードも読みやすい。私は娘のために自分で本を書きながら、Forthとタイムシェアリングを備えた小さなOSを作っている https://punkx.org/projekt0/book/part1/os.html。x86だったらそもそも試していなかったと思う。この過程でForthとRISC-Vアセンブリを同時に学んでいて、本当に楽しい。おもちゃのOSをゼロから作ってみたいなら、今はまさに絶好の時期だと言いたい(1980年代と同じくらい面白い)。安価なRISC-Vボード(rp2350など)を手に入れて、関連するマニュアルの節をClaudeのようなAIにアップロードすれば、行き詰まったときに大いに助けになる
こういう試みはいつも楽しくて興味深い。自分なりの暗号や難しいものにも挑戦してみるよう勧めたい。「独自暗号を実装するな」という助言は、実運用で検証されていないものを使うなという意味だ。実験や研究用途なら危険ではないので、思い切り試してよい。私たちにはもっと多くのOSと選択肢が必要だ
(スペインの判決文の引用)httpブロックまではまだしも、これはひどすぎる…
スペインではCloudflare(そしておそらく他も)がサッカー中継の問題で誤ってブロックされることがあると聞いた。こういう問題をどう回避しているのか気になる。VPNを使うのだろうか? 重要なIPが塞がれたら業務にも影響が出るはずだが
(判決文で)スペインプロサッカー協会とTelefónica Audiovisual Digitalが始めたとあるのを見て、こういう連中は犯罪者だと思う
……何だこれは? スペインのサッカー団体に国全体のインターネットアクセスを制限する権限があるということか?
安いRISCハードウェアをどうやって手に入れられるのか気になる
AliExpressでMilk-V Duo Sというボードが10ドルで売られている。最近おすすめ一覧によく出てくる。主な仕様はアップグレード版SG2000マスターと512MB RAM、より広いIO、一部モデルでWi-Fi 6/BT5対応(512M-Basic/eMMCモデルを除く)、USB 2.0ホストポート、PoE対応100Mbps Ethernet、デュアルMIPI CSI、RISC-VとARMのブート切り替えスイッチ対応など https://aliexpress.com/w/wholesale-Milk%2525252dV-Duo-S.html
すでにいくつもボードはあるが、私が面白そうだと思って出資したのはVisionFive 2 Liteだ https://www.kickstarter.com/projects/starfive/visionfive-2-lite-unlock-risc-v-sbc-at-199/description。初代VisionFive2は持っていないが、評判はよく、エコシステムもだんだん大きくなっているらしい。まだ未完成な部分はあるが、もうすぐ届くのを楽しみにしている。私が個人的に使っているのはPolarFire SoC Discovery Kitで、クアッドコアRISC-VにFPGAが入ったボードだ。やや高価(130ドル)で万人向けではないが、面白いのはボードのほうがチップ単体より安いことだ https://www.microchip.com/en-us/development-tool/MPFS-DISCO-KIT。Microchip関連の文書やツールチェーンは古くてあまり良くないが、慣れればbare-metal RISC-Vコードを動かすのは本当に簡単だ。Linux/bare-metalのサンプルも親切だ
今すぐ実機がなくても、x86やAppleマシン上でエミュレータを回してみるのがよいと思う。実機ボードより開発速度が速いし、QEMUのようなものを使えばすぐ試せる https://www.qemu.org/docs/master/system/target-riscv.html
Raspberry Pi Pico 2もRISC-Vをサポートしているので悪くない https://www.raspberrypi.com/products/raspberry-pi-pico-2/
いろいろ答えてくれた人たちに感謝する。質問に低評価を付けた人たちには感謝する理由はないと思う