Rust版TryKernel実装の経緯
Rustの扱いに習熟するため、インターフェース誌に掲載されたTryKernelのRustへの移植を
行いましたが、その時の経緯を記載します。
Rust版TryKernelのソースはここに格納してあります
-
Rustの学習
C言語の後継で、オブジェクト指向かつメモリー安全な機能を持つRustという言語がある事を
ネットニュースで読み、興味を持った。
技術評論社から出版されている[コンセプトから理解するRust]という解説書を購入し、所有権
やミュータブル、参照などの概念は何となく理解したが、ちゃんと言語を扱えるようになるには
実際にプログラムを作ってみることが必要と感じた。 -
発端
CQ出版社のインターフェース誌 2023年7月号に「ゼロから作るOS」が掲載されており、内容に
興味を惹かれたため購入。Raspberry Pi Pico上で動作するマルチタスクRTOSの概要と
コア部分であるタスクスケジュールやイベント通知、排他制御のためのセマフォなどの実装
詳細が紹介されていた。
C言語で作られているが、プログラムの規模も1500行とそれほど大きくなかったため
これをRustに移植して動作させる事が出来れば、Rustへの理解も深まるのではないかと思い
ssstoyama様がgithubで公開されている
Zig版Trykernel
の内容をベースにRustでのコーディングを開始した。 -
コーディングとデバッグ
Zigの内容をRustに置き換えていくのだが、単純なステートメントの置き換えは問題ないものの
配列の宣言の仕方や、必ず初期値の設定が必要など、RustのDocsのサンプルを見ながら一つ
ずつ実装を進めていった。
Rustは文法チェックが厳しいためエラーが大量に出力されるが、メッセージに「こう直したら
いいよ」という提案がされるため、親切な言語と感じた。
とはいえ仕事の傍ら趣味でやっているため、コードの作成とエラーメッセージのデバッグだけ
で1ヶ月以上かかった。
Rustの文法チェックは何段階かに分かれており、ある段階のエラーをクリアすると次の
チェックが行われ、また大量のエラーが表示されるの繰り返しだった。 -
モジュールビルドと配置
環境に依存しない部分のコーディングはほぼノーエラーとなったが、アセンブラとのリンクや
Pico向けのモジュールビルドなど、Raspberry Pi Pico特有の処理に着手。
それまではMacにRustをインストールしコンパイルしていたが、固有環境を整備するにはMac
よりLinuxの方が容易そうであったため、Raspberry Pi 4b と Picoを購入。
(初めはPico Wを購入したが、Pico WはGP25がLEDと繋がっていないのとデバッグ用
3pin端子が実装されていなかったため、Pico Hを買い直した)
Pi 4b+ Raspberry Pi OSにaarch64版のRustと、アセンブラが必要なためArm提供の
gcc toolchainを導入。なお、リンカは今後のRustのバージョンアップを考慮し
gcc toolchain付属のldではなく、Rust付属のrust-lldをそのまま利用した。
C言語のmakeの代わりにRustはbuild.rsというものを書けば cargo buildだけで
アセンブルやライブラリ作成、リンクが出来るらしいので、サンプルを見よう見まねで作成。
Pi 4b+ のRustに、Picoに適合するthumbv6m-none-eabiターゲットを追加、
nightly buildへの切り替えを行い、環境を準備。
最後の難関がリンカスクリプトで、書き方や組み込み特有の考慮事項などが初めは理解できず
インターフェース誌の記事やldコマンドのマニュアルを見ながら徐々に仕上げていった。 -
動作しない・・・試行錯誤
Pi 4b+に接続したPicoのフラッシュメモリにelf2uf2-rsコマンドでプログラムを書き込み
ブレッドボード上にPicoとタクトスイッチを配線しTryKernelに組み込まれているLチカアプリ
を実行させるも、全く動作せず。
組み込みは起動時に画面がないためデバッグしようがなく、ここで足踏み状態が続いた。
Picoに接続することで、gdbが使えるDebug Probeなる製品(実際の中身はPicoらしい)が
あったので購入、Debugピンを接続しPi 4bにopenocdを導入するも、そもそもopenocdが
動作せずプログラムにアタッチするところまでいかない。
色々悩みネットの情報を調べたら、rp2040-halというcortex_m_rtライブラリを使った
Lチカサンプルが見つかり、これをビルド・配置したら問題なく動作、openocdも動作したので
このプログラムの前処理部分を移植するよう見直した。具体的には
・2nd boot loaderの内容変更(Pico SDKのものとコードが微妙に違っていた)
・リセットハンドラをアセンブラに置き換え
・アセンブラのスタートアップ処理からRustで記述された前処理ルーチンを呼び出し
これらを行い、Rustの前処理ルーチンが呼び出している全ての関数をコメントアウトした
ところopenocdが動作したため、前処理ルーチンの呼び出している関数を1つずつ外して
いったところクロック設定関数(init_clock())に問題があることが判明。
正しい処理に見直すことで、ついにプログラムが動作するようになった。 -
動作確認
プログラムは動作するようになったが、タクトスイッチを押しても反応がないため再度
配線を見直したところ、タクトスイッチを押してPicoのG13,G14の端子電圧を0Vに落とす
ためのGND配線がなかったり、タクトスイッチ端子がブレッドボードときちんと接触して
いなかったなど、単なるミスであった。これらを是正した結果、無事動作する事を確認した。
振り返り
今回の実装で、Rustのモジュール構成や関数呼び出し・返り値の処理、ポインタ操作や
イテレータ実装などRust固有の言語機能を反映したプログラムを作ることができた。
組み込み環境ということで、ヒープを利用する基本型(String,Vecなど)が使えない制約は
あったものの、周辺機器を操作する小さなプログラムであればそれほど困らないと思った。
組み込みの開発は今回初めてであったが、既存のLinux開発環境に関する、ある程度の知識が
あれば何とかなると感じた。
Discussion