あらまし
約二年も前の話ですが、「割と適当に動作するOS「誰得OS」のCortex-M0+版であるUOS-LPC800を作りました」で 、LPC810でも動作するリアルタイム・オペレーティング・システムを作りました。LPC800シリーズの厳しい制約(ROM: 4KB、RAM: 1KB)の中でOSを動作させてみたいという欲求と、実際にこのような制約の中で何が出来るのか、単純に技術的な興味があったからです。当初のバージョンにおいて、タスク側から操作可能なAPIはuos_task_yieldとuos_task_sleepのみで、OSと呼ぶならば欲しいであろうタスク間通信すら追加しませんでした。 サンプル・プログラムは、片方のタスクでLEDを点滅させ、もう片方のタスクでシリアル通信を扱うだけのものです。まさに「誰得OS」の名前に相応しいアプリケーション。
なかなか思い切った割り切りだと当時は考えましたが、先日から実アプリケーションにUOS-LPC800を適用してみようと考え始めた時点で、タスク間通信の必要性を改めて感じる事になりました。各タスクがそれぞれの仕事をこなしつつ、他のタスクと協調して動作させようと考えた場合、タスク間通信は必須とも言える機能です。 やっぱり欲しいよね・・・。
設計
タスク間通信を追加する前のUOS-LPC800におけるタスクの状態遷移は以下です。タスクは、基本的にRunningとReadyを行ったり来たりしており、uos_task_sleepによってスリープ状態に遷移するようにしました。非常にシンプルな作りです。今回、タスク間通信を追加するにあたって、方針を「出来るだけ現状の設計には手を加えないで追加する」とし、スリープ処理に手を加えない形でタスク間通信を追加する事にしました。
今回のタスク間通信(メッセージ・パッシング)のモデルは以下のようなものです。 Task Aからuos_task_event_sendに対して「送信先タスクID番号」と「32ビットの値」を渡すと、あーら不思議、uos_task_event_recvを使って待ち状態に入っているTask Bに値が渡って実行状態に遷移するというものです。モデルは至って単純。
今回、上記の単純なモデルを実現するために、カーネル内部にブロック状態を管理するタスク・コンテキスト・ブロックのキューを新設し、送信待ちタスクと受信待ちタスクをブロック状態のキューに入れてから処理する事にしました。状態遷移は以下のようになります。
本当は、スリープ状態もブロック状態の一種として扱え、相当な理由が無い限り分離する意味はなさそうなのですが、今回は既存機能に手を加えない事にしたのでそのまま。もしかしたら次のアップデートではブロック状態をBlockedに変更して、現在のBlockとSleepと統合するかもしれません。
サンプル・アプリケーション
サンプル・アプリケーションは、従来通り二つのタスクを用意しました。タスク間通信を使用する場合は、task_ttyからtask_ledに向かってタスク間通信機能を使ってLEDの点灯指令を発行します。外から見た動作は従来のサンプルと何ら変わらないのですが、タスク間通信で指令するようになったところが従来と異なります。ちなみに、タスク間通信を使う場合、直接タスク間通信の関数を呼び出しても良いのですが、受信側タスクにサービス要求用のAPIを作って、送信側のタスクがそのサービス要求用のAPIを呼び出す方が筋が良い設計です。
巷に溢れる数多くのリアルタイム・オペレーティング・システムを使った実装例には、受信側タスクの内部事情を送信側タスクが知らなければ実装出来ないような記述が多く見られます。これではタスク間通信によって得られるはずの抽象化度を上げる事によるメリットの多くを享受出来ません。受信側タスクの事情を変更した場合(例えば、送信側に期待する送信内容を変更したとか)に、送信側タスクの実装を修正しなければならないとしたら、規模の大きな設計ではたちまちバグになります。
ダウンロード
- ソースコードはhttp://cubeatsystems.com/uos-lpc800/index.htmlからダウンロード出来ます。
- ライセンスはThe BSD 2-Clause Licenseです。
- LPC800 Mini BoardとLPCXpresso LPC812で動作します。
- 「使っているよ!」とお声がけ頂くと、作者がとても喜びます。