エッジコンピューティングの逆襲 特集

Rustをフル活用したリアルタイムOS「Tock」の特異性リアルタイムOS列伝(18)(2/3 ページ)

» 2021年12月27日 10時00分 公開
[大原雄介MONOist]

問題をドラスチックな方法で解決

 それでは、Tockの解決法はどういうものだったのか。以下に挙げてみよう。

  • 内部は全てイベントドリブン構造とする。このため、ユーザープログラムはひたすらCallbackのみを記述する格好になる
  • Tock自身を記述する言語をRustとする。これによりRustの持つ型安全性やメモリ安全性、スレッド安全性を期待できるので、OSの側であれこれ用意する必要がない
  • メモリ管理に関しては、Rustが提供するアフィン型のメモリ管理の仕組み(Ownership)をそのまま利用する。

 何というかなかなかドラスチックな解決法である(図6)。現時点でのTockはシングルスレッドモデルなので、イベントは特定の1スレッドにしか通知されない。このため、疑似マルチタスクはともかく、いわゆるマルチスレッドモデルのプログラミングは書き直す必要がある。

図6 図6 スレッド安全性に関しては、そもそもTockではマルチスレッドをサポートしていないのであまり関係ないのだが。それにしても、「型安全性はRustがやってくれるからZero Cost」というのは、まぁそれはそうなのだろうが……[クリックで拡大]

 ではTockの実装はどうなっているのか。それをまとめたのがこちら(図7)。カーネルそのものはRustで記述され、Trustな環境にある。その上に仮想のデバイスドライバが載るが、これはカプセル化される形で提供され、ユーザープログラム領域とは分離される。Process Schedulerはプリエンプティブ(非協調的)な構成になってはいるが、ただいわゆるコンカレントモデルをフルに実装しているわけではない。またプロセスの数もそれほど多く実行することは考慮していない(内蔵SRAMが最大64KBの、Microchipの「SAM4L」で最大8スロット程度、としている)。このあたりは先にも書いたがメモリとのご相談である。

図7 図7 GitHubに上がっているTock Overviewより。Syscall I/Fは、それぞれのプロセスとの間に挟まる薄く描かれたライブラリ(libtock/libtock-rs/etc……)で実現される[クリックで拡大]

 そのメモリサイズに関して言えば、例えば「SOSP 2017」の際に行われたLevy氏による講演での数字はこんな感じ(図8)。

図8 図8 blinkはいわゆるLチカで、これだと1KB未満で実現する。Networked sensorの方は詳細不明だが、それほど高度なものではないと思われる(例えば温度を測定して1分ごとにBLEで飛ばすなど)[クリックで拡大]

 ROMサイズ、つまりコードサイズはそこそこであるが、RAMサイズの方はかなり低めに抑えられている(RAMサイズが小さいRTOS「TinyOS」は本連載でまだ紹介していないが、何せ開発がほぼ止まっているというか終わっているので、紹介の順序はだいぶ後になりそうだ)。アプリケーションから見たアドレス空間は図9のようになっている。

図9 図9 Tock Designより。一応マルチプロセスモデルなので、プロセスの先頭アドレスは一意には決まらない(MMUはないので)[クリックで拡大]

 フラッシュ空間はRead+Execute、SRAM(と仮にあればデータフラッシュメモリ)はRead+Writeとして扱われ、プロセスごとに分離される(図10)。

図10 図10 要するに共有メモリにあたるものはない。不正な他のプロセスのメモリ空間へのアクセスはMPUで保護される(はず)[クリックで拡大]

 ついでに割り込み(Interrupt)周りを記しておくと、こんな感じになっている(図11)。カーネルにはIRQ Dispatchと、これに連動したProcess Schedulerが実装されているが、このうちタイマーのSysCallやTimer Driver、Virtual Alarmなどは全てカプセル化されている。このカプセル化されたオブジェクトは、一切イベント(というか、割り込み)は生成しない形になっている。これはオーバーヘッド削減につながり、またメモリ削減にもつながる、というのがLevy氏の説明だ。イベントを生成すると、実態としては割り込みにならざるを得ないからISRが無駄に動くことになる。ISRが動くというのは、その前後でContext Switchingが発生するということなので、これのオーバーヘッドがしゃれにならない(Context Switchingは340サイクルほどと説明されている。48MHz動作で7μsほど、という説明なので、これもSAM4Lの数字であろう)。

図11 図11 Command(カプセルの機能を呼び出す)/Allow(カプセルに利用できるメモリアドレスを渡す)/Subscribe(処理の登録)の他、memop(ヒープサイズを増やす)やYield(Subscribeで登録した処理が完了するまで待機)などのI/FがProcess Schedulerに用意されている[クリックで拡大]

 もう1つのメモリ削減だが、オブジェクト間での通信をイベントベースでやる場合、FIFOでも何でもいいのだが、何かしらのキューが必要になる。ところがTockの場合は、キューの数をOSのビルド時に静的に決定する方式となっているので、処理が間に合わないとキューにメッセージを入れられずにドロップすることになってしまい、安定動作が難しくなる。これを防ぐために、何でもかんでもイベントにするわけではなく、同期式の呼び出しをメインにすることでオーバーヘッドを削減するという方式だ。

Copyright © ITmedia, Inc. All Rights Reserved.