この本は組込みシステムやOSのような低レイヤーシステムの開発経験がないプログラマーのような人が、自作OSを始めるための解説本です。著者自身、本職はウェブプログラマーですが、趣味で組込みOSの自作をしています。この本はそのノウハウ集のようなものです。このような低レイヤーシステムはC言語で開発されることが多いのですが、今回はRustを使います。RustはC言語と比較して、様々なモダンな機能やツール群を取り揃えている上に、C言語の長所である直接のメモリー制御ができ、パフォーマンスも高いとして、組込みシステム開発で使えると注目されています。Rustそのものの解説は控えめですが、低レイヤーシステム開発特有のテクニックは必要に応じて解説します。
ある程度はプログラミング経験があることを前提としています。また、レジスタやメモリーなど、コンピューターアーキテクチャに関する基礎用語の説明もちゃんとはしていないので、このあたりの大まかな理解もあるといいでしょう(不安があればヘネシー&パターソン「コンピュータの構成と設計」などに代表されるコンピューターアーキテクチャの教科書を併用するとよいかもしれません)。Rustに関してですが、基本的な文法事項などは説明しませんが、一通りの文法になんとなく触れている程度の経験があれば十分です。他の知識・経験、特に組込み開発に関する知識については必要ありません。初歩のマニュアルの読み方・各種ツールの使い方を解説します。本書は前述の通り、組込みRust本でも用いられているWio Terminalと呼ばれるマイコンモジュールを用いた開発を行います。組込みRust本ではRustの基本的な文法も解説されているので、併用すると役に立つでしょう。
オペレーティングシステム(OS)とはそもそもなんでしょうか。これは自作OSをつくるときに誰もがぶつかる疑問でしょう。OSの教科書として有名なModern Operating Systemの第四版を見てみましょう。
It is hard to pin down what an operating system is other than saying it is the software that runs in kernel mode–and even that is not true.
カーネルモードという通常よりも権限の強いCPUのモードで動くソフトウェアとくらいしか定義しようがない、そのうえそれすらも正しくないものかもしれない、といっています。その原因のひとつとして、OSには本質的には関係のない2つの機能を持っているからと書かれています。その2つの機能とは、プログラマーのためにきれいなインターフェースを提供するというものと、ハードウェアのリソースを管理するというものです。
順番に見ていきましょう。まず、インターフェースとしての機能です。CPUや付属するデバイスをそのままの形で直接叩くというのは、非常に複雑で難しいです。たとえば、みなさんはプログラミングの一番初めの一歩として「Hello World」などの文字列を出力したと思うのですが、そのとき、「画面に文字を出力する機能」というのは直接実装することはなかったのではないでしょうか?この「画面に文字を出力する機能」というものを提供しているのはOSです(厳密にはそのOSの機能を利用する言語の標準ライブラリーを使うことが多いでしょう)。このOSがこのような機能を提供してくれるので、たとえばモニターを別のメーカーのものや違う規格の接続方法のものに付け替えたとしても、それらの違いというのはOSが吸収してくれる、というわけです。
次に、CPUやデバイスなどを管理するというリソースマネージャーとしての機能です。パソコンで作業しているときに、作業のためのエディターを立ち上げつつ、BGMを流すための音楽プレーヤーソフトを同じパソコンで立ち上げる、といった複数のアプリケーションを同時に立ち上げることは多いと思います。しかし、実際に使えるCPUは限られていて、近年はマルチコアが当たり前になっていますが、その個数を超えてアプリケーションを実行することも珍しくないと思います。これを実現しているのが、OSのリソースマネージャーとしての機能です。それぞれのアプリケーションに対してCPUをどの程度使っていいかを制限し、CPUで動くアプリケーションをすばやく切り替えることによってあたかも複数のアプリケーションが同時に何個も動いているように見せているというわけです。
筆者はこの2つの機能のうち少なくとも片方を満たす、というのが定義にはならなくても、OSとしての必要条件になってくるのではと考えています。
OSといわれて、多くの人はWindowsやmacOS、Linuxなどの各種デスクトップPC向けのOSや、AndroidやiOSのようなスマートフォン向けのOSを思い浮かべると思います。しかし、実際には様々な家電製品やセンサーノードなどの小さな電子機器にも組込みOSといわれるOSが入っていることが多くあります。他のWindowsなどのOSは汎用OSといわれます。組込みOSは小型な機器にも使えるように汎用OSとは異なる設計になっていて、以下のような傾向があります。
・使える機能が汎用OSに比べると少ない
・リアルタイム性、つまり一定周期毎、あるいは特定のイベントに対して遅れることなくタスクを実行することが重視される
・メモリーやCPU性能を多くは使わない
もっとも汎用OSと組込みOSの厳密な区別はなく、最近は強力な組込み機器向けのCPUも多く存在するため、Linuxが組込み機器に使われるケースもあります。そのため、必ずしも上記のような性質が常に当てはまるわけではなく、汎用OSと大差がないという場合もあります。
今回、自作しようとしている組込みOSは、ArmのCortex-MというCPUを対象としたものです。Armはスマートフォンやゲーム機にも使われていますが、こちらはCortex-AというCPUがメインです。Cortex-MはAよりも低価格・省電力なのが特徴です。その分、性能面や使えるメモリーが少ないです。ネットワークにつながるセンサーやモーターなどの制御に使われます。
今回作ろうとするOSは、たとえばマウスやキーボードの操作を受け付ける、というものではありません。CPUそのものがそういう用途ではないからです。最終的に作るのは、簡単なスケジューラと割り込み制御を備えた非常に簡易なOSです。そのため、先程挙げたOSとしての2つの機能のうち、後者の側面が強いものになっています。そのため、完成形は少し物足りないものになっているかもしれません。しかしながら、自作OSでは最初の一歩が非常にハードルが高いと思うので、その最初の一歩を助けるという意識で書きました。
筆者のTwitter(id:garasubo)で各種フィードバックを受け付けています。また、本書の各章を一通り終えたあとにできあがると期待されるコードは、以下からダウンロードできます。こちらのコードに問題を見つけた場合は、Issueから報告していただけると幸いです。
筆者はUbuntu 20.04で動作確認をしていますが、基本的にはWindowsやMac環境でも構築可能です。
rustupを使う方法が公式でも全プラットフォーム共通で推薦されているので、これに従います。https://rustup.rs/の方法に従って、rustupをインストールします。LinuxやMacのようなUnixライクなOSの場合、以下のようにインストールします。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustupは、Rustコンパイラのインストール・アップデートやバージョン管理をしてくれるツールです。今回は特にnightlyと呼ばれる、いわゆるベータ版のようなRustコンパイラを使うことになるのですが、nightlyと安定版であるstableの切り替えもコマンドひとつで行えて、大変便利です。
今回はnightlyの2021-10-05のバージョンを使います。以下のようにしてnightly-2021-10-05をインストールしましょう。
rustup install nightly-2021-10-05
さらにArmのクロスコンパイル用の環境を入れるために、以下のコマンドを実行します。
rustup target add thumbv7em-none-eabihf
アセンブラをコンパイルするために、Arm用のGCCを入れる必要もあります。Ubuntuだと、以下のコマンドで入手できます。
sudo apt install gcc-arm-none-eabi
Rustのインストールが終わったら、マイコンモジュールを扱うためにいくつかのツールをインストールする必要があります。cargo-hf2というWio Terminalに書き込むためのcargoのサブコマンドツールをインストールしましょう。
cargo install cargo-hf2
このツールを使うとき、デバイスにアクセスするためにルート権限を求められることになります。これを避けるためにudevのルールを追加し、sudoなしでも実行できるようにしておくと便利です。/etc/udev/rules.d/99-seed-boards.rulesを以下の内容で作成しておきましょう。
ATTRS{idVendor}=="2886", ENV{ID_MM_DEVICE_IGNORE}="1"
SUBSYSTEM=="usb", ATTRS{idVendor}=="2886", MODE="0666"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2886", MODE="0666"
追加後は設定をリロードするために再起動、もしくは以下のコマンドでリロードを行います。
sudo udevadm control --reload-rules
Wio Terminalというマイコンモジュールを使用します。これはLCDや無線モジュールなど様々なペリフェラルを備えているもので、秋月電子通商やスイッチサイエンスといった国内の電子部品販売店の通信販売で4000円程度で購入できます。ただし、この本はペリフェラルを使いこなしたアプリケーションをつくることが主な目的ではありません。そのため、自力である程度仕様書を読み解けば、他のArm Cortex-Mマイコンボードでも十分に本書の内容を試すことができると思います。
本書ではWio Terminalをデバッガにつないで、デバッグする必要があります。Wio Terminal本体にはPCと接続できるデバッグインターフェースはないため、別途機材を用意して接続する必要があります。方法はいくつかありますが、「基礎から学ぶ組込みRust」でも紹介されているSeeeduino XIAOをつかったデバッグをここでは使います。「基礎から学ぶ組込みRust」のサポートページの方に機材の購入方法も書いてあるので、そちらも参考にしてください。
サポートページ:https://github.com/tomoyuki-nakabayashi/Embedded-Rust-from-Basics/tree/main/debug
必要な機材としては以下のものです
・Seeeduino XIAO本体
・10ピン 0.5mmピッチのフレキシブルケーブル(FPC)およびその変換基板
・SeeeduinoとFPC変換基板を接続するためのジャンパケーブル4本
デバッガをPCから使うために、OpenOCDをインストールする必要があります。自前でビルドするか、サポートページにもあるビルド済みのものを利用するのがよいでしょう。OpenOCDを介して、GDBからリモートでバッグをします。Armアーキテクチャに対応したGDBを使う必要があるので、Ubuntu 18.04以降ではaptよりgdb-multiarchを、それ以前の場合はgdb-arm-none-eabiをインストールしましょう。
sudo apt install gdb-multiarch
OpenOCDはaptでも入手できますが、バージョンが古く必要なファイルが足りていないので、自前でビルドするかビルド済みのものを利用します。https://github.com/ciniml/debug-tools-builder/releases/download/v1.1/openocd-linux.tar.gzよりダウンロードできます。
XIAOマイコンには、DAPLinkというデバッガ用のファームウェアを書き込む必要があります。こちらのGitリポジトリのものを自前でビルドするか、@cinimlさんが公開しているビルド済みのものを利用しましょう。
・リポジトリ:https://github.com/Seeed-Studio/Seeed_Arduino_DAPLink
・ビルド済みファームウェア:https://github.com/ciniml/debug-tools-builder/releases/download/v1.1/Seeeduino_XIAO_DAPLink.uf2
ファームウェアの書き込み方法ですが、XIAOをUSBケーブルでPCと接続すると、USBストレージとして認識されるはずなので、そこにビルドされた「.uf2ファイル」をルートディレクトリーにコピーすると書き込めます。PC側でストレージとして認識されない場合、XIAOのオレンジ色のLEDの状態を確認してください。チカチカと点滅している場合は「RST」と書かれたメッキ2箇所をジャンパケーブルなどを用いてすばやく何回かショートさせて、点灯の状態にする必要があります。
デバッガの接続方法はWio Terminal背面の蓋を外し、左上にあるFPCコネクタとFPCケーブルからFPC変換基板を介して、XIAOマイコンにつなぎます。背面の蓋は左上と右下のゴム足をはがし、その下にあるネジを外した後、左下と右上のツメをヘラなどで本体側を外に引っ張りながら蓋を持ち上げると外れます。詳しくはこちらの記事が参考になるでしょう。
Wio Terminal の開け方:https://qiita.com/matsujirushi/items/018d4c5588ab782fb04e
Wio TerminalのFPCコネクタからSWDCLK・SWDIO・MCU_RESET・GNDのピンをXIAOマイコンにつなぎます。回路図の5ページ目を見ると、これらのピンが1・2・4・6番目に対応していることがわかります。XIAO側ですが、ビルドしたファームウェアによってピン番号が変わります。先ほど紹介したビルド済みファームウェアをそのまま書き込んだ場合は、それぞれ8・9・0・GND番ピンに接続します。