トロンフォーラム

第9回 タスク例外

今回は、T-Kernelのタスク例外処理機能について説明します。

本連載の第2回から第5回にかけて、T-Kernelのタスク管理機能とタスク間での同期・通信に関する機能(イベントフラグやセマフォなど)を扱ってきました。今回扱うタスク例外処理機能の観点から重要な点は、これらのAPIの多くが実行時の状況に依存しているということです。

たとえば、第3回で扱ったセマフォを用いて、以下のようにセマフォのセマフォ資源の取得を実行したとしましょう。

このとき、上記の処理をどのタスクから実行したかは極めて重要な情報です。資源がすぐに確保できない場合、上記APIを実行したタスクは資源が取得できるまで待ち状態となりますが、タスクAから実行した場合はタスクAが、タスクBから実行した場合はタスクBが待ち状態に遷移します。このように、T-KernelのAPIを実行する際の状況はAPIの動作を大きく左右する情報であり、一般にこれを「コンテキスト」と呼びます。

今回説明するタスク例外処理機能を一言で説明すると、あるタスクAから別のタスクBのコンテキストに入って操作を行うことができる、例外処理のために用意された特殊な機能です。先ほどの例で説明するならば、タスクBからtk_wai_sem()を実行し、タスクAを待ち状態に遷移させることができるような機能であるといえます。

タスク例外処理の手順

T-Kernelのタスク例外処理機能は、以下のAPIから成り立っています。

  • tk_def_tex
    タスク例外ハンドラの定義
  • tk_ras_tex
    タスク例外を発生
  • tk_dis_tex
    タスク例外を禁止
  • tk_ena_tex
    タスク例外を許可
  • tk_end_tex
    タスク例外ハンドラの終了
  • tk_ref_tex
    タスク例外の状態参照

タスク例外を発生し、対象となるタスクのコンテキストで目的のタスク例外処理を 実行させるための基本的な利用手順は以下の通りとなります。

  1. tk_def_tex(タスク例外ハンドラの定義)により、対象タスクに対する タスク例外ハンドラを定義します。
    • タスク例外ハンドラは、対象となるタスクのコンテキストにおいて実行したい処理であり、その関連付けをここで行います。
  2. tk_ena_tex(タスク例外を許可)を用いて、そのタスクへのタスク例外の発生を許可します。
    • タスク例外が発生した時に、そのタスクがタスク例外を受け付けることができるように、このAPIを用いてタスク例外を許可します。
  3. tk_ras_tex(タスク例外を発生)を実行し、タスク例外を発生させます。
    • これにより、対象となるタスクのコンテクストでタスク例外ハンドラの 内容が実行されます。

なお、T-Kernelのタスク例外は「タスク例外コード」という0〜31の番号による識別が可能になっており、タスク例外ハンドラはそれぞれの例外コード毎に独立に定義できます。

タスク例外処理機能を用いた例外処理

今回は、タスク例外コード0のタスク例外の利用例を示します。例外コードが0以外のタスク例外については longjmp() などを用いて復帰処理なども行うことができ、これをあわせて利用することで柔軟な例外処理が可能となりますが、複雑なので本記事では省略します。

例として、ミューテックスによる排他制御を行なっているタスクの異常を検出し、安全な状態まで戻してからタスクを終了させる処理をタスク例外で実現することとしましょう。この例では、タスク間の排他制御に用いているミューテックスのオブジェクトIDをmtxid であるものと仮定して話を進めます。

まずは、タスク例外ハンドラを定義します。タスク例外を定義するコード例は以下の通りです。

なお、上記で参照しているタスク例外ハンドラ本体の定義は以下の通りとします。

このコードは、「あるタスクが例えばプログラム中のデッドロックを引き起こすバグにより正しく機能しなくなった状況下において、他のタスクからその状況を検出し、安全にタスクを終了させてから再起動を行い、正しい状態へと復帰させたい」というような処理を想定しています。ミューテックスのロックを解除は、取得したタスクでしか実行できませんから、タスク例外ハンドラの利用が必要となります。なお、ミューテックスのロックを解除する前に、排他制御の観点から問題ない状況への移行処理が必要となるでしょう。その内容は行いたい処理によりますが、上記のコード例ではcancel_operation()関数でその処理を行うこととしています。

このタスク例外ハンドラを用いて、別のタスクでデッドロックを検出し、復帰処理を行うこととしましょう。コード例は以下のとおりです。

上記の例では、ミューテックスによる最大ロック時間がMAXIMUM_LOCK_TIMEであるものとし、タイムアウト時間にその値を指定しています。ここでE_TMOUTのエラーとなった場合は、正常動作時に想定される時間を超えてミューテックスのロックが解除されない状態が続いていることになるため、これはデッドロック等によりシステムが想定通り動作していない状況であると考えられます。このため、ここではtk_ras_tex()を実行することでタスク例外を発生させ、先ほど定義したタスク例外処理を実行させます。タスク例外処理実行後の復帰処理としてrestart_task()関数が実行されますが、その内容については行いたい処理に応じたものとなりますのでここでは省略します。

注意

前節の例からも分かるように、タスク例外処理機能というのはかなり特殊な 機能です。プログラム中の任意の場所でタスク例外ハンドラが起動される可能性を 考える必要があり、厳重な注意が必要となります。

例えば、対象タスク内で以下のような処理を行なっていたとしましょう。 これは、foo() がバイナリセマフォを用いて排他制御されているという、 一般的に見られるコードの形です。

ここで、タスク例外ハンドラを以下のように定義した場合どうなる でしょうか?

このタスク例外を単純に許可した場合、以下のようにタイミング上の問題が 生じる可能性が出てきます。

  • (A), (D) のタイミングでタスク例外ハンドラが起動された場合、特に 問題なく動作します。
  • (B), (C) のタイミングでタスク例外ハンドラが起動された場合、 バイナリセマフォを同じタスクから二重に取得要求するような状況となり、 永久に待ちに入ってしまいます。

このように、タスク例外は利用に注意を必要とする機能であり、どうしてもその機能が必要なときに正しい使い方で利用しなければいけません。

サブシステムとの関係

ここまでの話で分かるように、タスク例外管理機能を正しく使うのはそれほど簡単ではありませんが、サブシステムと組み合わせることにより、ミドルウェア開発の負担を大幅に減らすことが可能です。

詳細はサブシステム管理機能の範疇となりますので略しますが、T-Kernelにはサブシステム管理機能を用いて作られたソフトウェアモジュールを、正しく安全に例外処理するための仕組みが備わっています。具体的には、今回ご紹介したタスク例外に対する処理を、サブシステムごとのブレーク関数で割り込み、各ソフトウェアモジュールごとに独立して例外処理を行える仕組みがあります。

実際この仕組みを用いて例外処理を正しく行なっているミドルウェアの例としては、本連載でも簡単にご紹介したT-Kernel 2.0 Extension (T2EX)とT-Kernel Standard Extension (TKSE)があります。

興味がありましたら、T-Kernel 2.0仕様書のサブシステム管理機能におけるブレーク関数についての説明をご確認いただければと思います。

第8回 メモリ管理機能第10回 割込み管理機能


Return Top