実験4 マルチタスクでJ-Botを動かす


ここで行うこと。

マルチタスクのオペレーティングシステムは多くの組み込みコンピュータで用いられており、ロボットもその例外ではありません。また、多くのデスクトップあるいはサーバーのオペレーティングシステムは同様にマルチタスクが行われています。これは同時に異なったプログラムを実行できるようにしています。一つのCPUのシステムを見てみると一瞬には一つの仕事をしていないことになります。多くのタスクはCPUを共有してなされます。各々のタスクは最後まで実行されずに短い時間だけ走ります。次の実行期間が来るまでは仕事は止まっています。これらの期間をタイムスライスと呼びます。

次の図は仕事がどのようになされるかを示しています。


task1の次はtask2、task3と続き、またtask1に再び戻り同じことを繰り返します。各々のタスクがどのくらい長く実行するか、いくつのタスクが実行されるかはマルチタスキングスケジューラーが行います。この実験ではマルチタスクがどのように作られているか、そしてそれがいかにJ-Botに組み込まれるかを学びます。従って、高度なマルチタスクは行わずにJavelinで動作させるサービスのみになります。

まず始めに皆さんが用いているパソコンのように複雑なシステムから説明を始めましょう。これらのOSでは複数のスタックを用いていて、一つのアクティブなタスクに対して一つが用いられています。Javaのシステムはプログラマーに対してスタックが見えないように隠していますが、メソッドを呼ぶときと同様な方法で局所変数の履歴を保存するのに用いています。例えば、メソッドAがメソッドBを呼び、メソッドBがメソッドCを呼ぶ場合には、スタックはメソッドA、B、Cに対する局所変数が入っています。勿論、各々のメソッドが呼ばれた元のメソッドに戻るための情報も入っています。


図はスタックの中に戻りポイントとメソッドに対するパラメータがいかに入れられるかを示しています。スタック上に同様にあるオブジェクトポインターは示されていないません。この例のスタックは下に伸びていきます。スタックの底のメモリーは他の目的やメソッドが呼ばれたときに上書きされたりしてはいけません。多くのOSの実装ではヒープはスタックの下にあり、それらは各々別々の方向に伸びていきます。

ここでの要点は各々のタスクはそれ自身のスタックを持っていることです。スタックがなければタスクはメソッドが実行を終了したときに呼んだメソッドはどこかを保存しておくことが出来ません。

Windowsのようにさらに複雑なシステムの場合、タスクはさらに複雑になります。一つあるいは複数のスレッド/タスクを走らせるプロセスという概念があります。 各々のスレッド/タスクのプロセスは各自のスタックを持っています。プロセスはファイルやメモリーなどの資源も一緒に利用出来るようにします。そのプロセスが終了したときにはそれらの資源はOSに戻されます。プロセス/スレッドのアーキテクチャーはプロセス中のマルチタスクを許します。タスクマネージャーの点から見て、走らせるための巨大なタスクの収集になります。

タスク間のスイッチは多くの異なったメソッドを用いているときに起こります。これにはプリエンプティブ(preemptive)とノンプリエンプティブなタスクスイッチングがあります。プリエンプティブなタスクスイッチングは一つのタスクから他のタスクにスイッチする原因となる外部タスクからのイベントを許します。前に述べたマルチタスクシステムのタイムスライスはタスクが中断すべきかを決定するのにタイマーを用いています。タスク自身は何時これが起こるか知らないし、中断(interrupt)した点から再開されるので普通は何もしない。

タイマーのイベントは通常ハードウェアの割り込みによって起こり、現在実行されているタスクの状態を保存し、次に実行するタスクを選ぶタスクマネージャーを実行開始します。新しいタスクは次のタイマー割り込みまで実行が開始されます。

他の割り込みも同様にタスクスイッチの原因になります。例えば、シリアルポートが全ての文字を受け取った時に起こります。多くの組み込みシステムではハードウェアやタスクマネージャーによって操作される多くのイベントや割り込みがあります。

タスクスイッチを同様に行うノンプリエンプティブな方法もあります。例えば、他のタスクにメッセージを送るOSのサービスを用いるタスクがあったとします。この例ではメッセージ送信が終了するまで元のタスクが停止するという意味以外の詳細については述べません。この停止中に他のタスクが実行でき、タスクマネージャーはタスクスイッチを実施します。タイムスライスをサポートしているシステムならば元のタスクは効果的にタイムスライスを短くすることが出来ます。

排他的な操作をする協調的でノンプリエンプティブなモードを用いるマルチタスキングシステムをシステムを構築することが可能です。タスクが周期的に制御を他のタスクの実行に与えるようにするのはプログラマーの責任になります。

タスクの優先順位は多くのマルチタスキングシステムによって支持されているもう一つの要素です。この場合、各々のタスクは優先順位をもっています。高い優先順位のタスクは低い優先順位のタスクより実行が優位になります。このタイプの特徴は成されなければならないが即刻ではないかどうか、プログラマーにタスクを設定させることです。低い優先順位のタスクはすべての高い優先順位のタスクが待ち状態あるいは終了した時に実行します。

優先順位が補助する例として通信を基礎とした組み込みシステムがあります。この場合、通信タスクは高い優先順位を持ち、入力情報は即時に処理されます。中位の優先順位のタスクは低い優先順位のタスクがステータスの報告作成のような重要でないような作業をしている間に通信システムによって受け取った要求を実行します。

これらの素晴らしい特徴の問題点はコストです。性能のコスト、メモリーのコストそしてプログラミングのコストです。複数のタスクは実行する事にタスクマネージャーが関連しているオーバーヘッドを加算した十分な性能が要求されます。メモリーのコストは各々のタスクをサポートするために必要な複数のスタックとメモリーのサポートを含んでいます。マルチタスキングシステムは必ずしもメモリー利用が効率的ではありません。プログラミングは通常マルチタスキングシステムでは容易ですが、タスク間の相互関連が高くなるとさらに複雑になります。マルチタスクのアプリケーションをデバッグすることは一つのスレッドをデバッグするよりはるかに困難です。

Javelinはマルチタスクをサポートしている多くのCPUに比べてメモリーや性能は限られていますが、十分です。Javelinよりも性能の低いCPUでマルチタスキングシステムを実行することは可能ですが、これは通常ハードウェア、オペレーティングシステム、アプリケーションの詳細な知識が要求される低レベルの開発システムを用います。

いかにJ-Botでマルチタスクを行うか

ここで用いるマルチタスキングシステムは商業的な世界で見られるようなものとは異なっています。これはJavelinの制限内で動作させなければいけないからです。最大の制限は一つのスタックアーキテクチャーですが、マルチタスキングシステムはそれでも構築可能です。

J-Botのマルチタスキングシステムは一つのスタック、一つの優先順位、協調的ノンプリエンプティブ、状態遷移マシンのマルチタスキングシステムとして記述することが出来ます。一つのスタックはJavelinの制限です。一つの優先順位は実装とプログラミングを非常に簡単にしています。協調的ノンプリエンプティブなアーキテクチャーは処理時間を独占しないように譲渡制御を周期的に行うプログラムが必要になります。 ノンプリエンプティブな見方はJavelinの制限でもあります。プリエンプティブな考え方である仮想周辺装置は動作しますが、仮想周辺装置はJavelinに組み込むことは制限されています。

最後にマルチタスキングシステムの状態遷移マシンの側面があります。ロボットのアプリケーションは度々状態遷移マシンとして述べられていますのでとても有用な例であることが分かります。例えば次の図は前進や左右の旋回をするロボットの単純な状態遷移マシンを示しています。障害物が前左右ににあるかどうかを検出するセンサーを持っています。障害物は前面にあり、左右の障害物より簡単に検出できるとします。


円で示されるように3つの状態があります。前進、左旋回、右旋回です。各々に関連付けられている条件によりいくつかの遷移の矢印が出ています。

プログラムは前進のような特定の状態から開始します。現在の状況に基づいて新しい状態に遷移します。同じ状態に戻ってくる矢印で同じ状態に留まることが出来ます。

このプログラムは比較的単純です。ロボットは障害物が検出されるまで直進し、検出時に障害物から離れます。再び前進することが出来るまで回転を続けます。これはこれから先の章で紹介されるセンサーが導入されたときに実際にこのプログラムが作られます。

状態遷移図はさらに複雑にすることが出来ます。例えば迷路のプログラムは壁が検出されたときには通路を引き返すように壁に沿っていくでしょう。バックトラックのプログラムは後のためにその先がどうなっているかを探索していない入り口を保存するでしょう。これらの場所に戻るにはここで示したよりももっと複雑な状態遷移マシンが必要になります。

状態遷移図をプログラムコードに変換するプログラムは存在しますがここでは用いません。その代わりに、状態遷移図に相当するプログラムを書きます。

状態遷移マシンの典型的な実装は現在の状態に対する行動を実行し次の状態に行くメソッド(method)です。

RobotStateMachine
class RobotStateMachine { 
    static final int forward = 1;
    static final int pivotLeft = 2;
    static final int pivotRight = 3;

    int state = forward ; 

    public void nextState ( int state ) {
        this.state = state;
    }

    public void execute () {
        switch ( state ) {
            case forward: 
                moveForward ();
                if ( obstacleToLeft ())
                    nextState ( pivotRight );
                else if ( obstacleToRight ())
                    nextState ( pivotLeft );
                break ; 
            case pivotLeft:
                pivotLeft ();
                if ( obstacleToRight ())
                    nextState ( pivotRight );
                else if ( ! obstacleToLeft ())
                        nextState ( forward );
                break ; 

            case pivotRight:
                pivotRight ();
                if ( obstacleToLeft ())
                    nextState ( pivotRight );
                else if ( ! obstacleToRight ())
                    nextState ( forward ); 
                break;
        }
        return state;
    } 

// 検出と動きに対する他のメソッドがここから始まる。
}

nextSteteとexecuteのメソッドのみが示されています。用いられる他のメソッドはここには示されていません。
executeメソッドは引数を持っていませんが、状態変数はオブジェクトの一部でそれはexecuteのようにオブジェクトメソッドでいつでも使えます。メソッドは行動を実行し状態を変化すべきかどうかをチェックします。nextStateメソッドがこの例では使われています。理論的にはnextStateメソッドは省略可能ですが、デバッグするときに便利です。一つのタスクからもう一つのタスクの状態へ制御する方法でもあります。

私たちのマルチタスキングシステムで用いているタスクはこの構造に似ています。タスクマネージャーは各々のタスクに対してラウンドロビン方式(round robin)でexecuteメソッドを呼び出します。

executeメソッドが比較的大きくなる一方、各々個々の状態はそれが用いる時間量は最小になるでしょう。
ラウンドロビン
一つの資源を順番に利用する手法。
 ネットワークの負荷分散の場合、同様の構成にした系を複数用意して処理要求を順番に割り振ることをいう。ホスト名とIPアドレスの対応関係を利用して複数のサーバで負荷分散を行なう「DNSラウンドロビン」が有名。

 コンピュータの並列処理の場合、各プロセスを一定時間ずつ順番に実行することをいう。「巡回的並列処理」とも呼ばれる。持ち時間を使い果たしたプロセスは一旦中断され、待ち行列の最後に回される。各プロセスに割り当てられるCPU時間の断片をタイムクォンタム(time quantum)もしくはタイムスライス(time slice)という。ラウンドロビン方式ではすべてのプロセスが平等に扱われる。

実験4-1 マルチタスクを付け加える

Event
Semaphore
Task
InterruptTask
TimerTask
TaskToneGenerator
CallableTask
WatchHeapTask
ShowStatus
TaskStatus
SemaphoreStatus

マルチタスクのクラス階層


次のソースプログラムはEventクラスの定義です。これは上のクラス階層図にあるようにタスクとセマフォ(semaphore)の基礎です。

semaphore セマフォ
 複数のプロセス(プログラム)が同時実行されるマルチタスクOSにおいて、複数プログラム間での同期をとるためのしくみ。セマフォは一種の共有フラグで、同期をとるプログラム同士がこのフラグに注目し、フラグの変化に応じて処理を行なうようにすることで、同期を実現する。

協調的マルチタスク状態遷移マシンオペレーティングシステム
/*
 * 協調的マルチタスク状態遷移マシンオペレーティングシステム
 * タスクはEventのサブクラスでタスクはEventによって再開される
 */

package stamp.util.os;

import stamp.util.*;
import stamp.core.*;

/**
 * イベントが生じたことを示すためのインターフェース
 */

 public class Event {
  static final public Event nullEvent = new Event () ;

  /**
   * null eventを作る。Event.nullEventを用いること。
   */
  protected Event () {
  }

  /**
   * イベント参照がnullかどうかチェックする。
   *
   * event:  チェックするevent
   *
   * returns:  もしnullだったらnullEventを返し、それ以外はeventを返す。
   */
  static public Event checkEvent ( Event event ) {
    return ( event == null ) ? nullEvent : event ;
  }

  /**
   * イベントを生じさせる。
   */
  public void notify() {
    notify(null);
  }

  /**
   * イベントを生じさせる。
   */
  public void notify( Object object ) {
  }

  /**
   * イベントの名前を得る。
   *
   * return: イベント名("Event")
   */
  public String name () {
    return "Event" ;
  }
}

Eventクラスは二つのみメソッドを持っている: nodify()とnotify(object)である。一つのイベントを通知するための簡単ななインターフェースです。後で出てくるTaskSemaphoreのスーパークラスになっています。Taskオブジェクトのnotifyメソッドを呼ぶことは実行を再開します。これはタスクにイベントを仕込ませ、それ自身を停止状態にする事が出来ます。Semaphoreオブジェクトに対してはnotifyメソッドはセマフォを解放します。この単純なEventインターフェースはJ-Botを制御するときに用いられます。

そのEventオブジェクトは名前を持つことが出来、デフォールトでは"Event"という名前で、Taskのような多くのサブクラスは異なった名前を持ちます。名前はデバック中に有用です。一度アプリケーションがデバックされたならばメモリを節約するために省略することが出来ます。

Taskクラスの定義から開始しましょう。Taskクラスは全てのタスクオブジェクトのスーパークラスとして用いられます。各々のタスクオブジェクトはすでにお話した状態遷移マシンアーキテクチャーに基づいて、実行の論理的なスレッドを持っています。
次のソースプログラムはTaskクラスの定義です。タスクマネージャーを実装するクラスメソッドと同様にTaskオブジェクトの定義を含んでいます。

multi-thread
 「thread」は「糸」という意味で、コンピュータ関連では、
プログラムの実行単位を表わす言葉として利用される。

 一般に複数のプログラムが同時に実行可能なマルチタスクシステムでは、
それらの各プログラムに見掛け上、独立したメモリ空間やI/O空間などを
割り当てることで、それぞれのプログラムに対し、あたかもそれだけが
動いているように見せ掛ける。これにより各プログラムは、同時に実行
される他のプログラムとの相互作用を意識しなくてすむ。
マルチタスクシステムでは、このように、メモリ資源やディスク資源などを
独立して所有するプログラムの実行単位をプロセス(process。
「過程」の意)と呼んでいる。

 しかしマルチタスクシステムが、あるプロセスから別のプロセスに実行を
切り替えるには、現在のCPUレジスタの内容をすべて保存し、これから制御を
切り替えるプロセスのためのレジスタ値をロードするなど、負荷が非常に
大きい(こうした処理により、プロセスの独立性が保証される)。
このような負荷の大きなプロセスの切り替え処理を必要とせず、同一
プロセス内でのマルチタスク処理を可能にしたものがマルチスレッド・
システムであり、この場合のタスクの実行の単位をスレッドと呼ぶ。

 同一プロセス内のスレッド間では、処理の切り替えにかかる負荷が
小さく、またメモリやI/O資源などを共有するため、負荷の大きな
プロセス間通信を伴わずに、スレッド間での通信が行なえるという
メリットがある。

 一般にマルチスレッドシステムでは、実行単位はすべてスレッドで
管理される。このためプロセスが生成されると、最低でも1つのスレッドが
同時に生成される。このようにプロセスを代表するスレッドを、
プライマリスレッド(primary thread)と呼んでいる。


協調的マルチタスク状態遷移マシンオペレーティングシステム
/*
 * 協調的マルチタスク状態遷移マシンオペレーティングシステム
 * これはラウンドロビンタスクスケジューリングを用いた非常に
 * 基本的なOSです。
 * タスクリストは効率性の理由から循環的にリンクしています。
 *
 * 注意: もしTaskStatusクラスが用いないで、数バイトを付け加えれば
 *     コンストラクタの参照でtaskStatusListとnextTaskStatus
 *    を削除可能になる。
 */

package stamp.util.os;

import stamp.util.*;
import stamp.core.*;

/**
 * フリーサイクルのときはいつでもタスクが走ります。
 * 注意: 外部タスクを終了させる良い方法はtaskToAbort.nextState(abort)
 * を用いることです。代替手段はtaskToAbort.stop()ですが、これはメモリ
 * の掃除をしません。
 */

 public abstract class Task extends Event {
  /**
   * taskState() result
   */
  final public static int taskRunning       = 0 ;

  /**
   * taskState() result
   */
  final public static int taskSuspended      = 1 ;

  /**
   * taskState() result
   */
  final public static int taskSemaphoreWait = 2 ;

  /**
   * Initial value of state in execute()
   */
  final public static int initialState = 0 ;
  final public static int checkTimer   = -1 ; // sleep()のサポート用
  final public static int interrupt    = -2 ; // InterruptTaskを見よ
  final public static int terminate    = -3 ;
  final public static int stopped      = -4 ;

  /**
   * sleep()要求によるタスクを扱うためのTimerオブジェクト
   */
  public    Timer timer    = new Timer () ;

  /**
   * 次のアクティブタスクは循環リストの中
   */
  protected static Task taskStatusList  = null ;

  /**
   * taskStatusListに対するリンクされたリスト
   */
  protected Task nextTaskStatus  = null ;

  /**
   * taskState()によって戻される値
   */
  protected int taskStatus ;

  /**
   * 循環リスト上の次のアクティブリスト
   */
  protected Task nextTask  = null ;

  /**
   * execute()の中で用いられる状態変数
   */
  protected int state      = initialState ;

  /**
   * タイムアウトが起こった後の次の状態
   */
  protected int sleepState = checkTimer ;

  /**
   * Timout値
   */
  protected int hi, lo ;

  /**
   * タスクのセットアップ(設定)と開始
   */
  public Task() {
    addTask ( this ) ;

    // タスクをステータスリストに加える
    nextTaskStatus = taskStatusList ;
    taskStatusList = this ;
  }

  /**
   * タスク名を得る。
   *
   * 戻り: String の"Task"
   */
  public String name () {
    return "Task" ;
  }

  /**
   * タスクのステータスを得る
   *
   * 戻り: タスクのステータス
   *        (taskRunning, taskSuspended, taskSemaphoreWait)
   */
  public int taskStatus() {
    return taskStatus;
  }

  /**
   * 現在のタスクの状態を得る
   *
   * 戻り: int タスクの状態
   */
  public int getState () {
    return state ;
  }

  /**
   * もし、タスクが実行中ならばタスクマネージャーのタスクリスト
   * からタスクを削除する。
   * 任意のタスクから呼ぶことが出来る。
   *
   * 戻り: boolean タスクがリストから削除されたときにtrue
   */
  public boolean suspend () {
    if ( taskStatus == taskRunning ) {
      // 注意: nextTaskがnullでなかったらマネージャーはnull
      // であってはいけない
      taskStatus = taskSuspended ;
      return removeTask ( this ) ;
    }
    return false ;
  }

  /**
   * 停止するためにタスクを削除しstopの状態に設定する
   * 戻り: boolean  停止に成功したらtrue
   */
  public boolean stop() {
    if ( suspend ()) {
      state = stopped ;
      return true ;
    }

    return false ;
  }

  /**
   * もしタスクが実行していなかったならば、タスクマネージャーの
   * リストにタスクを戻して加える。
   * 任意のタスクから呼ぶことができるが、そのタスクはマネージャーを
   * 持っていなければならない
   *
   */
  public boolean resume () {
    if ( taskStatus == taskSuspended ) {
      // 注意: マネージャーはnextTaskがnullでなかったならばnullで
      //       であってはいけない
      addTask ( this ) ;
      return true;
    }
    return false ;
  }

  /**
   * resume()と同じ
   */
  public boolean start() {
    return resume () ;
  }

  /**
   * resume()と同じ
  */
  public void notify( Object object ) {
    resume () ;
  }

  /**
   * 次のexecute()呼び出しに対して状態を設定する
   *
   */
  public void nextState(int state) {
    this.state = state ;
  }

  /**
   * 次のexecute()呼び出しに対して状態を設定する
   * タスクをSuspendする。他のタスクによってresumeされる
   */
  public void sleep(int state) {
    this.state = state ;
    suspend();
  }

  /**
   * poll timerを仕掛けて設定単位時間を経過後に次の状態を続ける
   *
   * 入力: int nextState タイムアウトが起こったときに入る状態
   * 入力: int hi タイムアウト値。詳細はTimer.timeout(hi,lo)を見よ     * 入力: int lo  タイムアウト値
   */
  public void sleep ( int nextState, int hi, int lo ) {
    this.hi = hi ;
    this.lo = lo ;
    timer.mark () ;
    sleepState = nextState ;
    state = checkTimer ;
  }

  /**
   * poll timerを仕掛けて設定単位時間を経過後に次の状態を続ける
   * 入力: int nextState  タイムアウトが起こったときに入る状態
   * 入力: int msec ミリ秒単位でのタイムアウト値
   */
  public void sleep ( int nextState, int timeMS ) {
    sleep ( nextState, timeMS/569, (timeMS%569)*119) ;
  }

  /**
   * poll timerを仕掛けて設定単位時間を経過後に次の状態を続ける
   * 入力: int nextState  タイムアウトが起こったときに入る状態
   * 入力: int timeS 秒単位でのタイムアウト値
   */
  public void sleepSec ( int nextState, int timeS ) {
    sleep ( nextState, (timeS*18)/10, 0);
  }


  /**
   * 1期間タスクを実行する。全てのサブクラスはこのメソッドで
   * 実装されなければいけない
   * GUIがアイドルのときはいつでもアイドルタスクが実行される。
   * execute()の実装はインターフェースが応答可能にするために
   * 100ms以上かかってはいけない。
   */
  protected abstract void execute() ;


  // タスクマネージャーサポート

  /**
   * クラス変数: タスクリストの現在のタスク
   */
  static protected Task taskList = null;

  /**
   * 現在のアクティブタスク
   */
  static protected Task currentTask = null ;

  /**
   * 現在実行しているタスクオブジェクトを得る
   *
   * 戻り:Task 現在実行しているタスク
   */
  static public Task getCurrentTask () {
    return currentTask ;
  }

  /**
   * タスクリストにタスクを加える
   *
   * 入力:Task aNewTask: タスクリストに加えるタスク
   */
  public static void addTask(Task aNewTask) {
    if ( taskList == null ) {
      aNewTask.nextTask = aNewTask ;
    }
    else
    {
      aNewTask.nextTask = taskList.nextTask ;
      taskList.nextTask = aNewTask ;
    }

    aNewTask.taskStatus = taskRunning ;
    taskList = aNewTask ;
  }

  /**
   * タスクリストからタスクを削除する
   *
   * 入力:aTaskToRemove:タスクリストから削除するタスク
   *
   * 戻り:boolean リストからタスクが削除できたらtrue
   */
  public static boolean removeTask(Task aTaskToRemove) {
    // 空のリストを始めに処理する

    if ( taskList != null ) {
      // 現在のタスクからリストを走査し見つかったらそれを削除する

      Task scanTask = taskList ;

      do {
        if ( scanTask.nextTask == aTaskToRemove ) {
          // 削除するタスクが見つかった

          if ( scanTask == aTaskToRemove ) {
            // リストの中の一つだけのタスクを削除する
            taskList = null ;
          }
          else
          {
            // 現在のタスクが削除されたかどうかチェックする

            if ( taskList == aTaskToRemove ) {
              taskList = aTaskToRemove.nextTask ;
            }

            scanTask.nextTask = aTaskToRemove.nextTask ;
          }

          aTaskToRemove.nextTask  = null ;
          return true ;
        }

        // リストの中の次のタスクをチェックする
        scanTask = scanTask.nextTask ;
      }
      while ( scanTask != taskList ); //全てのリストが走査されたら脱出
    }

    return false ;
  }

  /**
   * ラウンドロビン法でタスクリストの中の各々のタスクを
   * 永久に実行する。実行するタスクが無くなった時に戻る
   */
  public static void TaskManager() {
    while ( taskList != null ) {
      // execute()がremoveTaskを通して変えることができるように
      // taskListを変える
      currentTask = taskList ;
      taskList = taskList.nextTask ;

      if (currentTask.state == checkTimer) {
        // タスクがタイムアウトを待機している
        if (currentTask.timer.timeout(currentTask.hi,currentTask.lo)) {
          // タイムアウトが起こったら、次の状態を設定する
          currentTask.state = currentTask.sleepState ;

          // もし、次の状態が不適切に設定されていたらば抜け出す
          if ( currentTask.state == checkTimer ) {
            currentTask.state = terminate ;
          }
        }
      } else {
        // 次の状態を呼び出す
        currentTask.execute () ;
      }
    }
  }
}

protected
 protectedが付与されたメンバ変数、メソッドは同一クラス、
同一パッケージ、サブクラスから参照することができます。
注意点はサブクラスからアクセスできるのはサブクラスの
オブジェクトを通してのみということです。サブクラスから
スーパークラスのオブジェクトを通して参照することはできません。 

Task.javaファイルはオブジェクトとクラスメソッドからなっています。クラスメソッドは全てのアクティブなタスクを管理するタスクマネージャーを実装します。タスクマネージャーはTask.TaskManager()を用いて初期化されます。一つあるいは複数のタスクが作られた後に開始しなければいけません。

Taskクラスは仮想的なメソッドexecute()を含んでいるのでabstractクラスと呼ばれます。Taskクラスはスーパークラスとして用いることが出来るがオブジェクトを作ることは出来ません。Taskオブジェクトはそれ自身何もしませんが、そのサブクラスでは使用するexecuteメソッドを定義して使います。

サンプルのtaskに入る前に、Taskクラスで定義されているメソッドについて考えて見ましょう。これらは二つの種類に分けられます。クラスメソッドをまず先に見てみましょう。これらは全てのアクティブなタスクをコントロールするタスクマネージャを実装しサポートしています。Taskオブジェクトメソッドを見てみましょう。

execute()メソッドの中で用いられているメソッド。

The class methods include:
void TaskManager ()
void addTask ( Task aTask )
boolean removeTask ( Task aTask )
boolean running ( Task aTask )
The object methods include:
void execute()
void resume()
void suspend()
void start()
void stop()
void notify(Object object)
void nextState ( int state )
void sleep ( int state, int hi, int lo )
void sleep ( int state, int timeMS )
void sleepSec ( int state, int timeSec )
メソッドの数は多くなく、必要最小限の機能になっています。Taskクラスは次のクラスの階層的スーパークラスになっています。
InterruptTask
TimerTask
TaskToneGenerator
CallableTask

セマフォを利用可能にするSemaphoreタスクもあり、マルチタスクの環境で有用です。これらのクラスの各々はこの章の後ろでさらに詳細に説明します。InterruptTask非同期的割込みでプリエンプティブな割り込みのサポートはありません。TimerTaskTaskToneGeneratorのような単純なタイマー依存の骨組みになっています。CallableTaskクラスは呼んだり戻ったり可能な状態遷移マシンの骨組みになっています。Taskクラスの基本的な状態遷移マシンのサポートは単一レベルの状態遷移マシンに限られています。

さらに高級なタスククラスはありますが、全てのアプリケーション必要とはされません。この場合、Taskのような単純なタスククラスのひとつが用いられます。同様に、InterruptTaskTimerTaskCallableTaskもアブストラクトクラスです。TaskToneGeneratorクラスはアブストラクトクラスではなく、TaskToneGeneratorのタスクオブジェクトは作ることが出来ます。

基本的なTaskManagerは任意の数のアクティブなタスクをサポートします。TaskManagerの詳細な動作はTaskクラスメソッドのTaskManagerセクションの中で担当しています。アクティブタスクリストにタスクを付け加えたり、取り去ることが可能ですが、全てのアクティブタスクはラウンドロビン法で呼ばれます。executeメソッドが呼ばれたときに、一つのタスクがそのタイムスライスで走ります。そのメソッドが戻ったときにリストの次のタスクにコントロールを譲渡します。通常。executeメソッドは100μ秒以上走らせるべきではありません。さもないと他のタスクが応答できない独占システムになってしまいます。長い時間がかかる実行はタスクのexecuteメソッドが呼ばれる時間が少なくなるように複数の状態に分散させるべきです。

Taskクラスによって与えられている基本的な役目はexecuteメソッドを用いる状態遷移マシンを実行し、開始、停止、指定期間のスリープする能力を持っています。sleepメソッドは基本的なTaskクラスの一部であるシングルタイマーオブジェクトを用いています。多くのタスクはこのタイプのサービスを利用します。executeメソッドの構文はこのサービスが異なって与えられるオブジェクトの名前は要求されないから非常に簡単化されてもいます。

Taskクラスメソッド - TaskMamager

Taskクラスのメソッドは次のようになります。

void TaskManager ()
void addTask ( Task aTask )
boolean removeTask ( Task aTask )
boolean running ( Task aTask )

TaskManagerメソッドは全てのアクティブなタスクをコントロールします。タスクの初期セットが作られた後で呼ばれます。付加的なタスクは後でアクティブタスクリストに新しく作られたり、削除されたりできます。TaskManagerメソッドはアクティブリストの中にタスクが無いときに戻ります。これはシステムが永久にアイドル状態になるのを防ぎます。TaskManagerはノンプリエンプティブなマルチタスクシステムを実装していることを肝に銘じてください。一度タスクリストが無くなれば、走っているのはTaskManagerメソッドのみです。アクティブリストに新しいタスクを加えないので、TaskManagerは永久にアイドルに戻るかアイドルのままでいます。前者の動作はいかにTaskManagerが働くかです。

TaskManagerはアクティブタスクリストをタスクの循環リストとして維持します。これは各々のタスクオブジェクトはリストの中で次のタスクの参照を持っていることを意味します。そのリストが一つのタスクを保持していたとすれば、この参照はそのタスクそれ自身になります。循環リストは注意深い実装が要求されますが、次のアクティブなタスクがいつも利用可能ですから実行に対してより効率的です。あるマルチタスキングシステムは非循環リストとしてアクティブリストを実装していますが、タスクマネージャーはリストの終わりをチェックし、リストの終わりに達したときには始点に戻らなければいけないことを意味します。このTaskManagerはそのようなチェックを行う必要はありません。

addTaskremoveTaskメソッドはアクティブリストにタスクオブジェクトを加えたり、削除したりするときに用います。これらのメソッドは通常直接には呼ばれません。その代わりに、開始、停止のようなタスクオブジェクトメソッドが用いられます。これらのメソッドはaddTaskremoteTaskメソッドを用います。

タスクの動向を排他的に知るアプリケーションに有用ですが実行しているメソッドに対しても同様に正しく使えます。クラスメソッドを順番に呼び出すマッチングタスクメソッドを使うことも可能です。

タスクオブジェクトのメソッド

タスクオブジェクトのメソッドは下のようになります。
void execute()
void resume()
void suspend()
void start()
void stop()
void notify(Object object)
void nextState ( int state )
void sleep ( int state, int hi, int lo )
void sleep ( int state, int timeMS )
void sleepSec ( int state, int timeSec )


タスクオブジェクトの定義の一部である状態に名付けられた変数もあります。 Taskを元とした全てのクラスは、この変数をオブジェクトのメソッドで用いることが出来ることを意味します。

executeメソッドはそのタスクオブジェクトがアクティブオブジェクトになる度にタスクマネージャーによって呼ばれます。そのタスクオブジェクトの状態変数はexecuteメソッドの中でスイッチ文で通常用いられます。そのexecuteメソッドは実行時間が最小になるように書かなければなりません。100μ秒以下で作るのが良いでしょう。

その状態変数は整数型を用いますが、数字を用いると状態の名前と混乱します。ですから、各々の状態はシンボル名を用います。これはタスククラスの定義の最初に定数の定義を用いて行います。

例えば、

class task1 extends Task {
    static final int newState = 1 ;
    static final int anotherState = 2 ; 

    public void execute () {
        switch ( state ) {
            case initialState: 
                 // この状態で行うことを記述
                 break; 
            case newState: 
                 // この状態で行うことを記述
                 break; 
            case anotherState: 
                 // この状態で行うことを記述
                 break; 
            default: 
                 stop ();
                 break;
        }
    }
} 
変数の前についているstatic final intは整数型の定数を表しています。状態の数字はユニークである必要があります。それには連番が簡単ですが、そのようにしなければならないことはありません。定数値が使われると実際の値は無関係です。同様に状態定数は正の数値にすべきです。 ゼロとマイナスの数値は内部のTaskManagerの使用のために確保されています。

startstopメソッドはTaskManagerのアクティブリストにタスクを加えたり減らしたりするときに用います。タスクはそれが作られたときに開始し、startメソッドはそのタスクが非活動状態のときにのみ必要になるものです。これは通常他のタスクから呼ばれます。stopメソッドはタスクを止めたいそのタスクそれ自身か他のタスクによって呼ばれます。オペレーティングシステムは非常に制限されているので、他のタスクから保護する機構はありません。これはJ-Botのような小さい組み込みシステムでは普通のことです。

suspendresumeメソッドはstartstopメソッドと全く同じです。その規則の一致と一般的な構文は標準的なJavaのスレッドと一緒に用いられます。notifyメソッドはスーパークラスがEvent抽象クラスであるため必要になります。notifyメソッドはresumeメソッドと全く同じです。そのスーパークラスが明白にそのメソッド名を決めていますから必要となります。

sleepメソッドはスリープ期間が切れてスリープする総時間の後に入る状態が必要になります。そのメソッドはミリ秒、秒単位のタイムアウト時間のタイマーオブジェクトに相当します。本質的にsleepメソッド呼び出しはタイムアウト値を保存し、タスクのtimerオブジェクトを用いてTimer.markメソッドを実行します。TaskManagerはタイムアウトが起こるまで普通のラウンドロビン法でタスクをチェックします。その場合、状態変数はsleepメソッド呼び出しで渡された値に変えられ、executeメソッドが呼び出されるでしょう。実際にタスクはアクティブリストに残っており、全てのタスクがsleepメソッド呼び出しを通してtimerオブジェクトを待機している場合には、TaskManagerは終了しません。

最後にnextStateメソッドがあります。状態変数を直接設定する代わりに用います。これは、デバッグやトレース機能をタスクに連結し使えるようにします。そして同様に続いて起こる状態変化を知る必要があるだろうタスククラスのサブクラス化を利用できるようにします。
最初のマルチタスクのプログラム
import stamp.core.*;
import stamp.util.os.*;

/**
 * Multitasking Test 1
 */

public class MutlitaskingTest1 extends Task {
  // execute()の状態
  final static int state1 = 1 ;
  final static int state2 = 2 ;

  String name ;

  MutlitaskingTest1 ( String name ) {
    this.name = name ;
  }

  /**
   * タスク名を得る
   */
  public String name () {
    return name ;
  }

  public void show ( String text ) {
    System.out.print ( name ) ;
    System.out.print ( ": " ) ;
    System.out.println ( text ) ;
  }

  protected void execute () {
    switch ( state ) {
    case initialState:
      show ( "Initial state" ) ;
      nextState ( state1 ) ;
      break ;

    case state1:
      show ( "State 1" ) ;
      nextState ( state2 ) ;
      break ;

    case state2:
      show ( "State 2" ) ;
      stop () ;
      break ;

    default:    // 異常な状態を捕獲し停止するためにdefaultにする
      stop () ;
      break;
    }
  }

  public static void main() {
    new MutlitaskingTest1 ( "Task 1" ) ;
    new MutlitaskingTest1 ( "Task 2" ) ;
    new MutlitaskingTest1 ( "Task 3" ) ;

    Task.TaskManager () ;
    System.out.println ( "All done" ) ;
  }
}

コンストラクターはタスクの名前である一つのString型の引数を持っています。そのタスクは現在の状態を表示し、出力の一部としてタスク名を含めます。各々のタスクはタスクがスタートする初期化状態を含めて3つの状態を段階的に移動します。タスクは「State 2」を表示して終了します。

mainメソッドはタスクが作られるところであり、タスクマネージャーはそのタスクを実行します。3つのタスクがmainメソッドの最初で作られます。コンストラクターの引数は各々を識別する助けになります。タスクへの参照はそのタスクが他のタスクによって操作される時に限って必要になります。それは、そのコンストラクターがそのタスクをタスクマネージャーのアクティブリストに付け加えるからです。

アクティブリストの中のタスクはTask.TaskManagerメソッドが呼ばれたときに実行されます。各々のタスクのexecuteメソッドは全てのタスクが終了するまで呼ばれます。この場合、各々のタスクはメッセージウィンドウに次のようにメッセージをプリントします。

Task1:Initial state

各々のタスクからの出力は各々のタスクが一行の出力を生成した後でexecuteメソッドを出るので重なるでしょう。

TaskManagerメソッドはstate2で起こるような、全てのタスクが停止したときに戻ります。state2のアクションの順序はexecuteメソッドが完了するように実行するので無関係であることに注意してください。stopメソッドはアクティブリストから呼んでいるタスクを単純に削除します。これはexecuteメソッドが戻るまでタスクには影響を与えません。

InterruptTaskクラス

基本的なTaskクラスの定義は、新しいタスクを生成するのを簡易化するTaskクラスを拡張する多くの方法がありますが、多くのマルチタスクのアプリケーションでは十分です。使える操作の一つの型は他のタスクに割り込みをかける能力です。それにより、現在の仕事が完了する前に新しい要求を扱うことが出来ます。InterruptTaskはこの型のサービスを行います。これを次のリストに示します。

InterruptTaskタスク
/**
 * このタイプのタスクはInterrupt()メソッドを用いて割り込まれる
 * ことができます。
 * タスクの状態をInterruptに変えて、現在の状態を保存します。
 * タスクはlastStateを用いて再開(resume)することができます。
 *
 *  public void execute (int state) {
 *   switch ( state ) {
 *   case interrupt:
 *     // do something here with interruptValue
 *
 *     nextState ( lastState ) ;
 *     break ;
 */

package stamp.util.os;

import stamp.util.*;

public abstract class InterruptTask extends Task {
  public int interruptState = terminate ;
  public int interruptValue = 0 ;

  /**
   * タスクの割り込みに用いる。割り込み状態に入る前に
   * 多重割り込みが起こった場合、最初の割り込みのみが認識されます。
   * 他の全ては無視されます。
   *
   * 入力:int interruptValue:
   *       割り込み原因を決めるためにタスクに与えられる値
   *
   * 戻り:boolean 割り込みが処理されたならばtrue
   */
  public boolean interrupt ( int interruptValue ) {
    if ( state != interrupt ) {
      this.interruptValue = interruptValue ;
      interruptState = state ;
      state = interrupt ;
      return true ;
    } else
      return false ;
  }

  /**
   * 割り込みの前の状態で再開する。
   * これは割り込み状態からのみ呼ばれるべきであるResume state prior to   */
  public void resumeInterrupt () {
    state = interruptState ;
  }

  /**
   * タスクの割り込みに用いる。割り込み状態に入る前に
   * 多重割り込みが起こった場合、最初の割り込みのみが認識されます。
   * 他の全ては無視されます。
   */
  public boolean interrupt () {
    return interrupt(0);
  }
}


InterruptTaskは3個のメソッドと2個のオブジェクト変数を持っています。メソッドは2個のinterruptメソッドとresumeinterruptメソッドです。最初のinterruptメソッドは一つの整数型の引数を持ち、それは割り込み状態に入ったときにタスクによって参照されるinterruptValue変数に保存されます。そのタスクのexecuteメソッドはそれに従って割り込み状態を操作する必要があります。

目的は通常二つのことのうち一つをする事です。第一にinterruptValueを調べ、この情報に基づいてある操作を実行することです。二番目に次に何をするか決定することです。割り込みの前に持っていた値に状態を戻すresumeInterruptメソッドを呼ぶことによって出来ます。そのタスクが再び割り込むことが出来るように新しい状態に設定することも出来ます。

InterruptTaskは一度に一箇所からのみ割り込まれます。interruptメソッドはそのタスクがすでに割り込まれている場合はfalseを返します。

次のリストはタスククラスがTaskの代わりにInterruptTaskから拡張されていることを除いては最初のマルチタスクテストプログラムの変形です。State 1 は他のタスクが割り込まれるように変えられています。割り込むためのタスクがタスクのコンストラクターに引数として渡されています。もしその引数がnullでしたら何も起こりません。

MutlitaskingTest2
import stamp.core.*;
import stamp.util.os.*;

/**
 * Multitasking Test 2
 */

public class MutlitaskingTest2 extends InterruptTask {
  // execute()の状態
  final static int state1 = 1 ;
  final static int state2 = 2 ;

  String name ;
  InterruptTask taskToInterrupt ;

  MutlitaskingTest2 ( String name, InterruptTask taskToInterrupt ) {
    this.name = name ;
    this.taskToInterrupt = taskToInterrupt ;
  }

  public void show ( String text ) {
    System.out.print ( name ) ;
    System.out.print ( ": " ) ;
    System.out.println ( text ) ;
  }

  protected void execute () {
    switch ( state ) {
    case initialState:
      show ( "Initial state" ) ;
      nextState ( state1 ) ;
      break ;

    case state1:
      show ( "State 1" ) ;
      nextState ( state2 ) ;
      if ( taskToInterrupt != null )
        if ( taskToInterrupt.interrupt ())
          show ( "Task interrupted" ) ;
        else
          show ( "Task already interrupted" ) ;
      break ;

    case state2:
      show ( "State 2" ) ;
      stop () ;
      break ;

    case interrupt:
      show ( "Interrupted" ) ;
      resumeInterrupt () ;
      break ;

    default:    // 異常な状態を捕獲し停止するためにdefaultにする
      stop () ;
      break;
    }
  }

  public static void main() {
    MutlitaskingTest2 task1 = new MutlitaskingTest2 ( "Task 1", null ) ;
    new MutlitaskingTest2 ( "Task 2", task1 ) ;
    new MutlitaskingTest2 ( "Task 3", task1 ) ;

    Task.TaskManager () ;
    System.out.println ( "All done" ) ;
  }
}


始めのタスクはtask1に保存され他の二つのタスクに引数を渡すことが出来ます。他の二つのタスクの各々はstate1でtask1に割り込むことを試みるでしょう。始めのタスクは割り込み状態使うだけで、InterruptValue変数はこの演習問題では用いません。始めのタスクは割り込みされたことの表示をプリントした後でその仕事を単に再開します。他の二つのタスクはtask1に割り込むことが出来るかどうかを表示するでしょう。

割り込みのサポートは制限されてはいるが役に立ちます。通常、このタイプのタスクは一つあるいはそれ以上の割り込みのタイプを扱うために設定されます。多重割り込みのタイプはinterruptメソッドのパラメータ化版を用いて扱うことが出来ます。他のタスクが何の割り込みを生じさせたかの提示はありませんが、このタイプの機能はもし必要ならば付け加えることが出来ます。一般的に、割り込みサポートを持つ理由はアプリケーションで明確にされ、その割り込みは一つの発生源か、発生源が気にならないもののどちらかでしょう。

TimerTask クラス

TimerTaskInterruptTaskの拡張です。ここではその構成をみるだけで、TimerTaskを用いているTaskToneGeneratorについては次の項目で例題を紹介します。次のリストはTimerTaskです。

TimerTask -- タイマーを用いた単純化な方法
/**
 * 単純なタイマーを基本として運用するタスク
 * タイマーが開始されたときにタスクが開始するように設計されている。
 * handleTimeoutメソッドがタイムアウトが起こった
 * 時によばれる。 
 * タイマーは再開(restart)されることができ、あるいは
 * タスクは終了することができる。
 * 
 * 典型的な使用法はTaskManager用いた音発生である。
 * これはタイマーが走っている間に他のタスクが実行できる。
 * handleTimeoutメソッドは同様に定義される。
 * デフォールトでinterruptメソッドを呼ぶことは
 * タイマーを止めます。
 */

package stamp.util.os;


public abstract class TimerTask extends InterruptTask {
  final static int timeout = 1 ;

  /**
   * 割り込みが起動されたときに呼ばれる
   * もしサブクラスがこの状態を処理するならば、再定義される。
   *
   * 戻り:boolean false
   */
  public boolean handleInterrupt ( int interruptValue ) {
    return false ;
  }

  /**
   * タイムアウトが起こったときに呼ばれる。
   * もしサブクラスがこの状態を処理するならば、再定義される。
   *
   * 戻り:boolean true: resumeだったら
   *              false: terminateだったら
   */
  public abstract boolean handleTimeout () ;

  /**
   * タイマーが走っているかどうかをチェックする。
   *
   * 戻り:boolean true: タイマーが入っていれば
   */
  public boolean timerRunning () {
    return state == checkTimer ;
  }

  /**
   * TaskManagerに呼ばれるタスク実行ルーチン 
   */
  public void execute () {
    switch ( state ) {
    case interrupt:
      if ( handleInterrupt ( interruptValue ))
        resumeInterrupt () ;
      else
        nextState ( terminate ) ;
      break ;

    case timeout:
      if ( handleTimeout ())
        break ;     // タイムアウトが処理されたら続ける

      // でなければ、タスクをstop()するように下に落ちる

    // case initialState:
    // case terminate:
    default:
      stop () ;
      break ;
    }
  }

  /**
   * Start timer and call  on timeout.
   * タイムアウト時にタイマーを開始し、handleTimeout()を呼ぶ。???
   * 入力:int hi タイムアウト値、詳しくはTimer.timeout(hi,lo)を見よ
   * 入力:int lo タイムアウト値
   */
  public void sleep ( int hi, int lo ) {
    sleep ( timeout, hi, lo ) ;
    resume () ;
  }

  /**
   * タイムアウト時にタイマーを開始し、handleTimeout()を呼ぶ。???
   *
   * 入力:int timeMS: ミリ秒単位のタイムアウト値
   */
  public void sleep ( int timeMS ) {
    // スーパークラスを用いる
    super.sleep ( timeout, timeMS ) ;
    resume () ;
  }

  /**
   * タイムアウト時にタイマーを開始し、handleTimeout()を呼ぶ。???
   *
   * 入力:int timeS: 秒単位のタイムアウト値
   */
  public void sleepSec ( int timeS ) {
    sleepSec ( timeout, timeS );
    resume () ;
  }
}
TimerTaskは音を発生するような単純な時間を基本としたタスクをサポートするように作られています。これはCPU.delayの代わりにタスクのtimerオブジェクトを用いる一つの方法です。CPU.delayを用いることはマルチタスク環境では、バックグラウンドの仮想周辺装置を除いて全ては完全な停止になるため、良い方法ではありません。

TimerTaskはPWMの仮想周辺装置を用いて音を発生を開始するような操作ができ、PWMを停止する時刻を決定することができます。これはここで説明する興味あるソースコードになります。

最初にexecuteメソッドを見てみましょう。スイッチ文のdefaultの部分にあり、(i>initialStateには無いことに注意しましょう(コメントに注意が書いてある)。この場合、defaultの部分はタスクを実行する最初の状態になり、その後タスクが停止します。つまり、TimerTaskに対する通常の状態が停止するからです。行う仕事があるときに開始されます。そのタスクはいくつかの設定を行い、ある時間経過するまで待機します。このタイプの仕事はTimerTaskクラスに基づいたTimerToneGeneratorクラスの中で実行されます。TimerTaskクラスはアブストラクトクラスなので、TaskToneGeneratorクラスのようなサブクラスが必要になります。

executeのスイッチ文は二つの状態を扱います。:timeoutinterruptです。これは通常のタスクとは違ってTimerTaskサブクラスにあります。TimerTaskの場合ではサブクラスはexecuteメソッドではなくhandleTimeoutメソッドを定義しなければいけません。

TimerTaskの典型的な機能は開始したタスクを停止したり待機させたりさせることです。通常、メソッドはサブクラスの中で定義され、そのタスクオブジェクトはTaskToneGeneratorクラスの中のplayToneのように開始させることができます。このメソッドは設定を行い、TimerTasksleepメソッドの一つを呼びます。これらのメソッドはタイムアウト値を設定しタスクを開始します。handleTimeoutメソッドはタイムアウトが発生したときに呼ばれます。もし、handleTimeoutメソッドがtrueを返した場合、タスクは実行を続けることを仮定します。これは通常他のアクションが設定された場合、そのタスクが待機期間に入ることを意味します。もしそうでなかったら、タスクは停止します。

使用できるsleepメソッドは3個あり、Timerクラスで使えるメソッドの型と一致します。これらはtick、ミリ秒、秒を基にしたタイムアウトを持っています。

TimerTaskは割り込み処理も設定します。handleInterruptメソッドはそのタスクが割り込まれたときに呼ばれます。このメソッドの結果は、もしそのタスクがタイムアウトが起こるまで走り続けるならばtrueにします。そのタスクが終了すべき場合はfalseの値を返すべきです。最後の場合、そのメソッドは必要な整理を実行します。TaskToneGeneratorクラスでは、この整理プロセスは音の出力を停止します。

TimerTaskオブジェクトは様々な目的で用いることができます。例えば、watchdog timerタスクは他のタスクから周期的な割り込みが必要です。もしその割り込み発生に失敗すると、watchdog timerタスクは何かがおかしいと仮定し、それに応じた行動をします。watchdog timerタスクは組み込みシステムでは常識です。

TimerTaskの実世界の例としてTaskToneGeneratorクラスに行きましょう。

TaskToneGeneratorクラス

TaskToneGeneratorオブジェクトはマルチタスク環境では以前で用いたFreqoutクラスの代わりに使うべきでしょう。FreqoutオブジェクトはCPU.delayメソッドを用いて固定長の音を発生します。マルチタスク環境ではFreqoutオブジェクトを用いているタスクは音が止まるまでJavelinは独占されます。 TaskToneGeneratorTimerTaskのサブクラスです。単音あるいは音のリストで指定された音の列を発生することが出来るためTimerTasksより多少複雑です。次のリストはTaskToneGeneratorクラスです。
TaskToneGenerator -- TaskManagerを用いて音を発生させる
/**
 * TaskManagerを用いた音発生器
 * 
 * TaskTimerサポートを用いて指定された期間音を発生させる。
 * FreqoutがしていたようにCPU.delayは用いていない。
 */

package stamp.util.os;

import stamp.core.*;


public class TaskToneGenerator extends TimerTask {
  protected PWM pwm ;
  protected int pin ;
  protected int tones[] ;
  protected int tonesIndex ;
  protected boolean pwmRunning = false ;

  final static int endTone = 1 ;  // 終了時の状態

  final static int noMoreTones = -1 ;

  /**
   * 音発生出力を設定する
   *
   * 入力: int pin: CPU.pin10のように出力ピンを設定する
   */
  public TaskToneGenerator ( int pin ) {
    this.pin = pin ;
    pwm = new PWM ( pin ) ;
  }

  /**
   * タスク名を得る
   *
   * 戻り:String タスク名
   */
  public String name () {
    return "TaskToneGenerator" ;
  }

  /**
   * 出力周波数を設定する
   *
   * 入力:int frequency 10ヘルツ単位の周波数(0 - 11k)
   */
  protected void setFrequency ( int frequency ) {
    if ( frequency == 0 )
      frequency = 1 ;

    int halfCycleTime = 5761 / frequency ;

    pwm.update ( halfCycleTime, halfCycleTime ) ;
  }

  /**
   * 固定時間だけ音を発生する
   *
   * 入力:int frequency  10ヘルツ単位の周波数(0 - 11k)
   * 入力:int time      ミリ秒単位の持続時間
   */
  public void playTone (int frequency, int time) {
    tonesIndex = noMoreTones ;
    playToneNow ( frequency, time ) ;
  }

  /**
   * 音の組を発生する。配列は偶数個の要素が含まれる。
   * 要素の始めの値は周波数
   * 二番目は持続時間
   * 配列に少なくとも二つないと何もしない。
   * 
   * 入力:int[] tones: 音の配列(frequency, duration)の対
   */
  public void playTones ( int tones[] ) {
    if ( tones.length >= 2 )
    {
      this.tones = tones ;
      tonesIndex = 0 ;
      startNextTone () ;
    }
  }

  /* Internal routine */
  protected void playToneNow ( int frequency, int time ) {
    // PWMを設定する
    setFrequency ( frequency ) ;

    // PWMとタスクがすでに入っているかどうかチェックする
    if ( ! pwmRunning ) {
      pwmRunning = true ;
      pwm.start () ;    // PWMを開始する
    }

    /* 指定した時間Sleepする
     * 後でtimerRunningのstatusを設定するからtimerRunning
     * のチェックをした後に実行しなければならない
     */
    sleep(time) ;
  }


  /* 内部ルーチン  */
  protected void startNextTone () {
    // tonesIndexはいつも正しい対を参照する

    playToneNow ( tones[tonesIndex], tones[tonesIndex+1] ) ;
    tonesIndex += 2 ;

    // リストの中の最後の音かどうかtonesIndexを調整する
    if (( tonesIndex + 2 ) > tones.length ) {
      tonesIndex = noMoreTones ;
    }
  }

  /**
   * 音が出ていれば音の発生を切る
   */
  public void stopTone () {
    interrupt ( 0 ) ;
  }

  /**
   * 音が出ていれば音の発生を切る
   *
   * 入力:int interruptValue: 無視される
   *
   * 戻り:タスクが停止したときfalse
   */
  public boolean handleInterrupt ( int interruptValue ) {
    if ( pwmRunning ) {
      tonesIndex = noMoreTones ;    //次のタイムアウトでdisableにする
      handleTimeout () ;            //タイムアウトさせる
    }
    return false ;
  }

  /**
   * TaskManagerで呼ばれるタスク実行ルーチン
   *
   * 戻り:boolean: 終了したときfalse、残りがあるときtrue
   */
  public boolean handleTimeout () {
    if ( tonesIndex == noMoreTones ) {
      // 残りの音がないときPWMを切る
      pwmRunning = false ;
      pwm.stop () ;      // 音を切る
      CPU.readPin (pin);
      return false ;     // タスクを停止
    } else {
      // Get next tone
      startNextTone () ; // タイマーと次の音を開始
      return true ;      // タスクを続ける
    }
  }
  
  /**
   * 
   */
  public boolean running() {
    if( pwmRunning ) return true;
    return false;
  }
}

TaskToneGeneratorクラスはPWMオブジェクトと出力ピン番号を含んで多くのオブジェクト変数を持っています。他の変数は音リストで指定された音を演奏する際の音を発生させるために用います。

TaskToneGeneratorのコンストラクタはCPU.pin10のようにピン番号を引数として持ちます。これはPWMオブジェクトを設定するのに用いられます。システムはスピーカーを一つだけ持っているので通常一つのTaskToneGeneratorオブジェクトが作られます。

setFrequencyメソッドはPWMの設定に用いられますが、これはprotectedメソッドです。これはそのオブジェクトによってではなく、他のメソッドからのみ呼ばれることを意味します。音の発生を扱うオブジェクトメソッドによって用いられます。

playToneplayTonesはタスクを開始するメソッドです。playToneメソッドは定数のnoMoreTonestonesIndexオブジェクト変数を設定します。この音はplayToneNowを用いて設定されます。

playTonesメソッドはバラメータとして整数型の配列を用います。その配列の要素数は整数でなければいけません。2つの要素の最初は周波数で二番目は持続時間です。playTonesメソッドに渡す同様のタイプの引数があります。リストの中の全ての音が演奏されたときにそのタスクは停止します。playTonesメソッドは始めに配列の中に少なくとも一対あることを確かめます。配列の参照をオブジェクトのインスタンス変数に保存し、配列の最初の要素を参照するtonesIndexを設定します。startNextToneメソッドは新しい音を出すための参照された要素を用います。

残りのメソッドはprotectedであり、クラスの中のみで用いられます。playToneNowメソッドは音の周波数を設定し、PWMを開始します。そのタスクは音の持続時間sleepします。

リストの最後にあるhandleTimeoutメソッドはタスクがsleepからwake upしたときに呼ばれます。もしPWMを行う音がない場合には停止し、スピーカに用いられているピンは入力に設定され確実に次の音は開始されません。もし、多くの音があったならば、startNextToneメソッドが呼ばれます。handleTimeoutメソッドはもし音がもうない場合にfalseを戻します。これはタスクが停止するようにします。次のplayToneあるいはplayTones呼び出しで再開します。もし戻り値がtrueの場合にはタスクは実行し続けます。startNextToneメソッドは次の音を設定し、sleepメソッドを呼びます。

stopToneはアクティブなplayToneを停止するために呼ばれるか、あるいはplayTonesは出力を初期化します。stopToneメソッドはこれを行うためにinterruptを用いています。タスクはhandleInterruptメソッドを呼ぶことによって割り込みを通知されます。このメソッドはPWMがまだ実行しているかどうかをチェックし、もしそうだったらそれを止めます。

次のプログラムは音がどのように発生されるかを示します。
MutlitaskingTest3
import stamp.core.*;
import stamp.util.os.*;

/**
 * Multitasking Test 3 - Tone generation
 */

public class MutlitaskingTest3 extends InterruptTask {
  final static int waitForToneDone = 1 ;
  final static int waitForTonesDone = 2 ;

  // 音リストは周波数と持続時間(msec)の対の組である
  final static int toneList [] = { 120, 50, 240, 50, 360, 50 } ;

  TaskToneGenerator toneGenerator = new TaskToneGenerator ( CPU.pin10 ) ;

  protected void execute () {
    switch ( state ) {
    case initialState:
      System.out.println ( "Initial state" ) ;
      toneGenerator.playTone ( 200, 1000 ) ;
      nextState ( waitForToneDone ) ;
      break ;

    case waitForToneDone:
      if ( toneGenerator.running ()) {
        System.out.println ( "Waiting for tone to end" ) ;
      } else {
        toneGenerator.playTones ( toneList ) ;
        nextState ( waitForTonesDone ) ;
      }
      break ;

    case waitForTonesDone:
      if ( toneGenerator.running ()) {
        System.out.println ( "Waiting for tone list to end" ) ;
      } else {
        stop () ;
      }
      break ;

    default:    // 異常な状態を捕獲し停止するためにdefaultにする
      stop () ;
      break;
    }
  }

  public static void main() {
    new MutlitaskingTest3 () ;

    Task.TaskManager () ;
    System.out.println ( "All done" ) ;
  }
}

サンプルプログラムは二つのタスクを実際に作ります。一つはMultiaskingTest3でもう一つはTaskToneGeneratorです。MultitaskingTest3タスクはinitialState中で一つのtoneGeneratorを開始します。playTonesメソッド呼び出しで再び開始される前にtoneGeneratorが停止するのを待機します。

通常、TaskToneGeneratorは他のタスクが仕事をしている間に、音の発生を扱います。MultitaskingTest3タスクは多くの状態メッセージを単純にプリントします。もしFreqoutオブジェクトが用いられた場合はこのようなことはできません。

テキストの塊をプリントする能力は重要でないと思われますが、音が発生されている間にいかに他のアクションができるかを示しています。

CallableTaskクラス

多くのマルチタスクアプリケーションは基本として任意のタスククラスを用いてシングルレベルの状態遷移マシンで実行することができます。CallableTaskクラスは多重状態遷移マシンを実装したメカニズムを持っています。要するに、Javelinの制限の中で 残っているルーチンを呼び出す方法を得ています。タスクの中から開始された全ての呼び出しは即座に戻る必要があるメソッドを実行します。

CallableTaskに対する必要性はより複雑なアプリケーションを考慮する時にさらに明白になります。例えば、シリアルポートを通して任意の数の文字を送るタスクは、シリアルポートの仮想周辺装置の中に作られる出力バッファが決して満杯にならないならば、このサポートなしに実行できます。もしそうだったら、文字を送る呼び出しはバッファの先頭の文字が送られるまで戻らないでしょう。

従来の状態遷移マシンのタスクはもしバッファの量が確保されていれば、状態をチェックすることによってこれを申し分なく扱うことができます。シングルレベルの状態遷移マシンの問題は定義されている状態のみしかサポートしないことです。もし、そのタスクの異なった部分でも文字を送らなければならない場合には、プログラマーは文字が送られた後にどの状態に入るかを痕跡する方法を設定するか、CallableTaskサポートを用いる必要があります。

本質的に、CallableTaskcallStatereturnStateメソッドを一つのメソッドを呼んだり、それから戻ってくるのと同様に用います。これらのメソッドは典型的なJavaの呼び出しより分かり易く、冗長ですが、マルチタスクの制限内で動作します。最小限のオーバーヘッドが必要にもなります。

CallableTaskはリンクされたリストとして実際に実装されているcall stackを管理します。各々の呼び出しは、戻り状態数と付随のパラメータを持ったエントリーをスタックに作ります。パラメータの参照は整数のように組み込みオブジェクトを除いてどんなオブジェクトでもできます。パラメータとして渡されるオブジェクトは呼ばれた状態によってアクセスできる任意の数のオブジェクト変数を含めることができるため、さらに柔軟性があることを示しています。もし必要なら、その状態はオブジェクトを通して情報を戻すことができます。パラメータオブジェクトクラスはアプリケーション依存になります。

CallableTaskは二つのファイルで構成されます。次のリストはCallableTaskクラスファイルです。二番目はCallStackEntryです。スタックのエントリーは呼び出しの状態の痕跡を保存するためのCallableTaskクラスによって用いられます。

CallableTask
/**
 * この型のタスクは呼び出し規約の基本的な状態をサポートする。
 * 
 * タスクは他の状態を呼ぶことよって状態を変えることができる
 * これらの状態は呼び出し状態に戻ることができる。パラメータはまだ
 * 実装されていないがサブクラスは簡単にできる
 */

package stamp.util.os;

import stamp.util.*;

public abstract class CallableTask extends InterruptTask {
  // スタックはタスクに対して排他的である
  protected CallStackEntry stack = null ;

  /**
   * 現在のパラメータを得る
   * 戻り:直近のcallStateで渡されたパラメータを戻す
   */
  public Object getParameter () {
    return ( stack == null ) ? null : stack.parameter ;
  }

  /**
   * 状態を呼ぶ
   *
   * 入力:int callState: 入る次の状態
   * 入力:Object parameter: 呼び出しパラメータ
   * 入力:int returnState: returnState()が呼ばれたときに入る状態
   */
  public void callState ( int callState, Object parameter, int returnState ) {
    // 新しいスタックエントリーを加える
    stack = CallStackEntry.getEntry ( returnState, stack, parameter ) ;

    // 新しい状態を設定する
    nextState ( callState ) ;
  }

  /**
   * 状態を呼ぶ
   *
   * 入力:int callState: 入る次の状態
   * 入力:int returnState: returnState()が呼ばれたときに入る状態
   */
  public void callState ( int callState, int returnState ) {
    callState ( callState, null, returnState ) ;
  }


  /**
   * 一番最近のcallStateで指定された前の状態に戻る
   * もし前の呼び出し無しで実行されたらばタスクが終了する
   */
  public void returnState () {
    CallStackEntry oldEntry ;

    if ( stack == null ) {
      state = terminate ;
    } else {
      // Setup return state
      state = stack.returnState ;

      // スタックをPopする
      oldEntry = stack ;
      stack = oldEntry.nextEntry ;

      // Free entry
      oldEntry.free () ;

      // 注意: ガーベージコレクションがサポートされているならば
      //       次を付け加えるべきである
      // stackEntry.parameter = null
    }
  }
}


次のリストはCallableTaskのスタックエントリークラスでCallStackEntryクラスです。

CallStackEntry
/**
 * CallableTaskスタックを実装するのに用いる
 */

package stamp.util.os;

import stamp.util.*;

public class CallStackEntry {
  protected CallStackEntry nextEntry ;
  protected int returnState ;
  protected Object parameter ;

  protected static CallStackEntry freeList = null ;

  protected CallStackEntry () {
  }

  /**
   * freeリストのエントリーを戻す
   */
  void free () {
    nextEntry = freeList ;
    freeList = this ;
  }


  /**
   * 用いていないスタックエントリーを返す
   * フリーリストが空ならば新しいエントリーが作られる
   * 
   * 入力:int returnState: 新しいエントリーに保存される
   * 入力:CallStackEntry nextEntry:新しいエントリーに保存される
   * 入力:Object parameter:新しいエントリーに保存される
   */
  static CallStackEntry getEntry
      ( int returnState
      , CallStackEntry nextEntry
      , Object parameter) {
    CallStackEntry result ;

    if ( freeList == null ) {
      result = new CallStackEntry () ;
    } else {
      result = freeList ;
      freeList = result.nextEntry ;
    }

    // スタックリストにエントリーを加える
    result.returnState = returnState ;
    result.nextEntry = nextEntry ;
    result.parameter = parameter ;

    return result ;
  }
}

CallStackEntryから開始して逆方向に仕事をみてみましょう。各々のエントリーはreturnStateと呼んだ状態に対するパラメータの痕跡を保存します。nextEntry変数は二つの任務があります。一つのエントリーがCallableTaskの中でスタックされた時、もしあれば、前のエントリーを参照します。これがスタックの中で最初のエントリーだった場合は、それはnullになります。returnStateメソッドがスタックのトップで呼ばれた場合、エントリーはCallStackEntryのクラスメソッドによって管理されるfreeListの頭に移動されます。

CallStackEntrynewを用いてCallableTaskによって配置(allocate)されません。その代わりに、新しいオブジェクトを生成するgetEntryメソッドを呼びます。freeListが空の時のみそうなります。エントリーがもはや必要が無くなった場合、freeメソッドを用いてfreeListに戻されます。塵集め(gabage collection)が無いためJavelinで行います。普通のJavaではオブジェクトは単純に参照をなくせば、ガーベージコレクション(塵集め)を通して最後にはフリーメモリーに戻されます。
garbage collection
 OSのメモリ管理機能の一つ。プログラムが使用しなくなったメモリ領域や、プログラム間の隙間のメモリ領域を集めて、連続した利用可能なメモリ領域を増やす技術。これが不完全なOSは次第に利用可能なメモリが減ってゆくため、一定期間ごとに再起動を強いられることになる。Java言語の実行環境(JVM)は自身がガーベジコレクション機能を持っており、Javaプログラマがメモリ管理に気を使わなくてもいいようにしている。
「garbage」は「ごみ、がらくた」という意味。

目に留まるものは何もしないpretectedCallStackEntryに対するコンストラクターがあることです。これは新しいオブジェクトを作る他のクラスを防ぐためです。新しいオブジェクトはgetEntryメソッドでのみ作ることができます。

CallableTaskクラスに話を戻しましょう。callStatereturnStatecallStateは2つ、あるいは3つのパラメータをとります。2つのパラメータは呼び出しの状態と戻る状態である。callStateの3つめのパラメータについては後で説明します。 callStateがcallとreturn状態の両方が必要である理由は、現在の状態が通常その呼び出しが戻るべき状態でないからである。 典型的にretrurn状態はcallStateメソッドが呼ばれるcase文の次のcase文になるでしょう。

callStateメソッドはnextStateメソッドのように働きます。その新しい状態にはそのタスクのexecuteメソッドが再び呼ばれるまで入りません。

returnStateメソッドは引数はなく、callStateに一致する際にreturn状態に状態を変えます。

呼ばれた(called)状態に入ったとき、callStateで渡されたパラメータがgetParameterパラメータを用いて使えるようになります。前に述べたように、渡されたパラメータはクラスオブジェクトのオブジェクトでなくてはいけません。理論的には、これはどんなオブジェクトでも良いのですが、実際にはintとかbyteなどの組み込み型の値のようなものはパラメータとしては不適切です。一般的に、パラメータクラスは一つあるいはそれ以上の引数によって定義されます。戻り値も勿論使うことが出来ます。例えば、シリアルルーチンとして次のようなパラメータを使うことが出来ます。
aCallStateParameter
class aCallStateParameter extends Object {
  public int bufferSize;
  public byte buffer[20];
  public boolean success;
}

もし、このパラメータが出力に用いられたら、bufferSizeはバッファ配列のバイト数に設定されるでしょう。success変数はreturnStateが呼ばれる前にシリアル出力ルーチンによって設定されます。それはデータが正常に送られたかどうかを示しています。

実際にStringのパラメータを用いて簡単なプログラムで見てみましょう。intとかbyteと違って、StringクラスはスーパークラスとしてObjectクラスを持っています。
Multitasking Test 4
import stamp.core.*;
import stamp.util.os.*;

/**
 * Multitasking Test 4 - Callable Task test
 */

public class MutlitaskingTest4 extends CallableTask {
  final static int state1 = 1 ;
  final static int state2 = 2 ;
  final static int call1 = 10 ;
  final static int call2 = 20 ;
  final static int call2a = 21 ;

  protected void execute () {
    switch ( state ) {
    case initialState:
      System.out.println ( "Starting" ) ;

      callState ( call1, "test base",  state1 ) ;
      break ;

    case state1:
      System.out.println ( "State 1" ) ;
      callState ( call2, state2 );
      break ;

    case state2:
      System.out.println ( "State 2" ) ;
      nextState ( terminate ) ;
      break ;

    // --- Call 1  -----

    case call1:
      System.out.println ( "Enter call 1" ) ;

      System.out.println ( (String) getParameter ()) ;

      System.out.println ( "Exit call 1" ) ;
      returnState () ;
      break ;


    // --- Call 2  -----

    case call2:
      System.out.println ( "Entering call 2" ) ;
      callState ( call1, "test 2", call2a ) ;
      break ;

    case call2a:
      System.out.println ( "Done with call 2" ) ;
      returnState () ;
      break ;

    // ---- End of program ---

    default:    // 異常の状態を検知した場合終了する
    case terminate:
      System.out.println ( "All done" ) ;
      stop () ;
      break;
    }
  }

  public static void main() {
    new MutlitaskingTest4 () ;

    Task.TaskManager () ;
    System.out.println ( "No more tasks running" ) ;
  }
}

プログラムは一つのCallableTaskを作ります(実際にはMultitaskingTest4)。入ったらその状態をプリントすることは以前のテストプログラムと同じです。このプログラムからの出力は下に示すようになります。


CallableTaskは必ずしも必要なものではないですが、必要性が出たときには非常に手ごろに使えます。

Semaphoreクラス

タスクは共有されている変数を用いてお互いに通信することが出来ます。タスクがオブジェクトにアクセスできるのはstaticあるいはオブジェクト変数です。問題は、このタイプのアクセスが無制限であることです。いくつかのアプリケーションでは、タスク内通信(intertask, interprocessとも言います)をより制御したい必要があります。

全範囲のタスク内通信メソッドを実装することは可能ですが、Javelinはメモリの制限があり、Semaphoreクラスのようなものが必要になります。

タスク内通信
Javaのスレッドでは、JavaはJavelinではサポートされていない自分自身のタスク内通信サービスを持っています。Semaphoreクラスは標準のJavaでは実装することはできますが、他のメソッドは普通使いません。


Semaphoreクラスの定義は次のようになります。

Semaphoreクラス
/*
 * Semaphore class
 * 
 * 資源をコントロールするタスクによって用いられる。
 * 一度に一つのタスクによって取得することができる。
 * 続いて起こるタスクは中断されセマフォリストに加えられる
 */

package stamp.util.os;

import stamp.util.*;

/**
 * タスクオブジェクトに対しての標準的なセマフォサポート
 * ready状態から開始する
 */

public class Semaphore extends Event {
  protected static Semaphore semaphoreList ;

  protected Semaphore nextSemaphore ;
  protected Task acquiredTask ;
  protected Task waitingTaskList ;

  /**
   * セマフォオブジェクトを作る
   */
  public Semaphore () {
    nextSemaphore = semaphoreList ;
    semaphoreList = this ;
    acquiredTask = null ;
    waitingTaskList = null ;
  }

  /**
   * セマフォがreadyかどうかを調べる
   *
   * 戻り:取得されるセマフォがreadyならばtrue
   */
  public boolean ready () {
    return ( acquiredTask == null ) ;
  }

  /**
   * セマフォを取得する。タスクの次の状態を設定する
   *
   * この作用を実現するにはタスクはexecute()を抜けなければならない
   * もしそのタスクがセマフォを条件付で取得しなければならないならば
   * ready()を用いる。
   * もしセマフォがreadyでなかったならば、task.suspend()を実行せよ
   * もしセマフォが取得できたら実行が続けられる。
   * これはもし可能ならばあるアクションを実行した後でセマフォをすぐに
   * タスクが解放することを許します。適切にnextStateを設定する。
   * release()に一つ以上一致することはないことを念頭に入れよ。
   * 例:
   *  case 9: // これはいつもコントロールを与える
   *     semaphore.acquire(11);
   *     break;
   *  case 10: // もし取得されなかったらなばコントロールを与える
   *     if ( semaphore.acquire(11))
   *       break;
   *  case 11:
   *     // 何かをする
   *     semaphore.relese() ;
   *     nextState(12);
   *     break ;
   *
   * 戻り:もしセマフォが取得されたならばfalse
   *       もし中断(suspend)だったらtrue
   */
  public boolean acquire ( int nextState ) {
    Task currentTask = Task.currentTask ;	// static protected

    // 次の状態に行かせる
    currentTask.nextState ( nextState ) ;

    if ( ready ()) {
      acquiredTask = Task.currentTask ;
      return false ;
    }

    // 実行リスト()からタスクを削除する
    if ( ! currentTask.suspend ()) {
      return true ; // タスクがすでにsuspendしていた。
    }

    // タスクはセマフォ待ちと表す。
    currentTask.taskStatus = Task.taskSemaphoreWait ; // final int

    // 待ちリストにタスクを加える
    if ( waitingTaskList == null ) {
      // リストが空だった
      waitingTaskList = Task.currentTask ;
    } else {
      // リストの最後に付け加える
      // 注意: task.nextTask はnullであるべき
      Task lastTask = waitingTaskList ;

      // リストの最後に移動
      while ( lastTask.nextTask != null )
        lastTask = lastTask.nextTask ;

      // リストの最後にタスクを追加する
      lastTask.nextTask = Task.currentTask ;  // static protected
      Task.currentTask.nextTask = null ; // 丁度この場合
    }

    return true ;
  }

  /**
   * セマフォの解放。一つが待っていれば次のタスクを開始する
   * 解放するタスクはそれをする事が許可されていると仮定する
   */
  public void release () {
    if ( waitingTaskList == null ) {
      acquiredTask = null ;
    }
    else {
      // nextStateセット(集合)でタスクがsuspendしている
      // 最初の待機タスクをResumeする
      acquiredTask = waitingTaskList ;

      waitingTaskList = waitingTaskList.nextTask ;
      acquiredTask.nextTask = null ;

      // 仕事をresumeするにはtaskStatusがtaskSuspendedでなければならない
      acquiredTask.taskStatus = Task.taskSuspended ;  // final static
      acquiredTask.resume () ;
    }
  }
  /**
   * イベントが起こったときにセマフォを解放する
   * Semaphoreクラスはnotifyメソッドのみを持っている
   * イベントクラスに基づいている
   */
  public void notify (Object object) {
    release () ;
  }

  /**
   * 名前を得る
   *
   * 戻り:セマフォの名前を返す
   */
  public String name () {
    return "Semaphore" ;
  }
}


Semaphoreオブジェクトは一つのタスクによって取得されます。もし二番目のタスクが、Semaphoreを取得しようとした場合、二番目のタスクは待機リスト(wait list)に付け加えられます。待機リストの先頭にあるタスクはSemaphoreオブジェクトを持っているタスクが手放す時にSemaphoreを取得します。

acquireメソッドはパラメータとして状態を持っています。その理由は、acquireメソッドがSemaphoreオブジェクトの待機リストにそのタスクを付け加えるからです。そのメソッドはもしsemaphoreを取得した場合、trueを返します。これは、そのタスクは続けることができ、semaphoreによって保護された資源を用いることができることを意味します。もし、メソッドがfalseを返した場合、そのタスクはexecuteメソッドから戻るべきです。acquireメソッドに対する状態パラメータはexecuteメソッドが次に呼ばれたときの状態になります。acquireメソッドはacquiredTask変数のなかでSemaphoreを取得したタスクのタスク参照を保存します。

割り込みの場合
そのタスクがInterruptTaskで割り込みが起こった場合、タスクのexecuteメソッドの次の状態値が割り込みの状態になります。セマフォのacquireメソッド呼び出しで示された状態は次のexecute呼び出しになります。同様に、割り込みの状態はセマフォが取得されるまで入りません。


releaseメソッドはSemaphoreオブジェクトに関連付けられた資源とともに完了したときにのみ呼ばれるべきです。EventクラスがSemaphoreクラスのスーパークラスですからnotifyメソッドを含んでいます。causeメソッドはセマフォを開放しますから、プログラムはセマフォが取得された後で起こる設定のみを行うべきです。

acquireメソッドはSemaphoreオブジェクトがすでに取得されているときに待機リストにタスクを付け加えます。これは必ずしも望まれたことではありません。例えば、利用できるようになったときにタスクがほかの事をしたり、コントロールされた資源が必要になったりするからです。この場合、readyメソッドはSemaphoreオブジェクトのステータスを問い合わせすることができます。もしオブジェクトが取得できた場合はtrueを返します。このタイプの手続きはマルチタスクシステムがノンプリエンプティブだから動作します。readyメソッドが呼ばれる時点と続く起こる取得したメソッド呼び出しの間でタスクスイッチがおこるため、もしこれがプリエンプティブなマルチタスクだとしたら動作しません。

SemaphoreクラスはsemaphoreListクラス変数とnextSemaphoreオブジェクト変数を用いてSemaphoreオブジェクトのリストを保存します。そのリストはデバッグの目的のみで用いられます。一般的な運用には要求されません。

どのようにしてSemaphoreオブジェクトが使われるかを示すサンプルアプリケーションを次に示します。

Maltitasking Test 5 - セマフォのデモ
import stamp.core.*;
import stamp.util.os.*;

/**
 * Multitasking Test 5 - セマフォのデモ
 */

public class MutlitaskingTest5 extends Task {
  static public Semaphore semaphore = new Semaphore () ;

  String name ;

  MutlitaskingTest5 ( String name ) {
    this.name = name ;
  }

  public void show ( String text ) {
    System.out.print ( name ) ;
    System.out.print ( ": " ) ;
    System.out.println ( text ) ;
  }


  // execute()の状態
  final static int state1 = 1 ;
  final static int state2 = 2 ;
  final static int state3 = 3 ;

  protected void execute () {
    switch ( state ) {
    case initialState:
      show ( "Initial state - acquire semaphore" ) ;
      semaphore.acquire ( state1 ) ;
      break ;

    case state1:
      show ( "State 1 - semaphore acquired" ) ;
      sleep(state2,1000) ;      // 待機(wait)して状態2に行く
      break ;

    case state2:
      show ( "State 2 - done sleeping" ) ;
      show ( "Releasing semaphore" ) ;
      semaphore.release () ;
      stop () ;
      break ;


    default:    // 異常の状態を検知した場合終了する
      stop () ;
      break;
    }
  }

  public static void main() {
    new MutlitaskingTest5 ( "Task 1" ) ;
    new MutlitaskingTest5 ( "Task 2" ) ;
    new MutlitaskingTest5 ( "Task 3" ) ;

    // 各々のタスクはセマフォを取得を試みる
    // タスクは少しの期間待機する
    // そして、セマフォを解放し終了する。
    Task.TaskManager () ;
    System.out.println ( "All done" ) ;
  }
}


そのタスクはセマフォにアクセスを試みそれを解放します。Semaphoreオブジェクトを解放した後でタスクを終了します。結局は全てのタスクは戻ります。

Semaphoreオブジェクトは分離に用いられることができる、あるいはSemaphoreによって制御されるオブジェクトのクラスに対してスーパークラスのように用いることができます。どちらのアプローチも正しいが、後者は他の、多分より使える、スーパークラスを用いることをそのクラスから妨げるからさらに限定的な傾向になります。

実験4-2 J-Botをマルチタスクで動かす

実験1はとても長かったが、この実験を通して用いられるマルチタスクのサポートを導入しました。サンプルプログラムはとても簡単でマルチタスクシステムの特別な側面に焦点を当てて企画してあります。ここで、これらのツールを上手く利用してJ-Botを動かしてみましょう。ソースコードは以前のシングルタスクの例よりも多少複雑に見えますが、これからのためになることを忘れないようにしてください。この新しいアプローチはバックグラウンドで他のタスクを実行させることを許します。これは障害物検知、他のJ-Bot、コンピュータ、より進んだ記録装置との通信と立案プログラムを含みます。マルチタスクシステムが無ければそのような機能は作り管理することは極めて困難です。

次のBasicMultutaskingBotクラスは以前に定義したBasicBotクラスを用いています。動きを扱う際に時間消費を独占するCPU.delayメソッド呼び出しを用いること無しに動かせます。

MultitaskingJBotクラス
package JBot ;

import stamp.core.*;
import stamp.util.os.*;

/**
 * MultitaskingJBot クラス
 *
 * マルチタスクシステムを用いてJ-Botを走らせるプログラム
 */

public class MultitaskingJBot extends Task {
  protected JBotInterface jbot ;

  static final int checkMovement = 1 ;

  /**
   * J-Botオブジェクトを監視するためにセットアップする
   */
  public void notify ( Object jbotInterface ) {
    jbot = (JBotInterface) jbotInterface ;
    nextState(checkMovement);
    resume () ;
  }

  /**
   * タスクを実行するメソッド
   */
  protected void execute () {
    switch ( state ) {
    case checkMovement:
      if (jbot.movementDone()) {
        // 動作が終了したためタスクを停止する
        stop () ;
      }
      break;

    case initialState:
    default:          // defaultは異常な状態を捕獲する
      stop () ;
      break;
    }
  }
}


MultitaslingJBotクラスはBasicBotのようにJBotInterfaceオブジェクトと、移動、旋回、回転を含むパブリックメソッドの組み合わせを用いて動き始めるほかのタスクと一緒に動作するように設計されています。これらのメソッドは基本的に全く同じ事をします。JBotInterfaceオブジェクトがセットアップされるとそのstartEventMultitaskingJBotオブジェクトを参照します。JBotInterfaceオブジェクトは MultitaslingJBotnotifyメソッドを呼ぶ際に引数として自分自身の参照を渡します。これはMultitaslingJBotjbotオブジェクト変数に保存され、タスクが走っている間使うことができます。そのタスクはnotifyメソッドの中にあるresumeメソッドを通して開始されます。

MultitaskingJBotexecuteメソッドは全てのタスクを制御するTask.TaskManagerによって周期的に呼ばれます。executeメソッドはtrueが返されるまでjbotのmovementDoneメソッドを呼び出します。そのタスクは、それ自身停止し、jbotのcauseNextEventメソッドを呼び出します。これは、必要ならば次の動作を開始させるようにjbotによって管理されているイベントを知られます。これは、MultitaskingJBotオブジェクトをリスタートするようにします。

次のサンプルアプリケーションはいかにこのタスクが用いられるかを示します。

MultitaskingJBotクラスをテストする
import stamp.core.*;
import stamp.util.os.* ;
import JBot.* ;

/**
 * MultitaskingJBotクラスをテストする
 * 
 * BasicMultitaskingJBotのクラスメソッドを用いてJ-Botを
 * 走らせるプログラム
 */

public class MultitaskingJBotTest1 extends Task {
  JBotInterface jbot = new RampingJBot ( new MultitaskingJBot ()) ;

  protected void execute () {
    switch ( state ) {
    case initialState:
      jbot.move ( 2 ) ;
      nextState ( 1 ) ;         // 完了のためpollすることが必要
      break;

    case 1:
      if ( jbot.movementDone ()) {
        // Non-polled version.
        // 動作が終わるまでタスクはsuspendになる

        jbot.pivot ( -2 ) ;
        jbot.wait ( 2 ) ;       // 終わるまでこのタスクはsuspendする
      }
      break;

    case 2:
      jbot.turn ( 1 ) ;         // 短い回転をさせる
      jbot.wait ( 3 ) ;         // 終わるまでこのタスクはsuspendする
      break;

    case 3:
      jbot.stop();              // 待機しないでバックグラウンド
                                // タスクが走る
    default:                    // 異常な状態を捕獲
      stop () ;
      break;
    }
  }

  public static void main () {
    new MultitaskingJBotTest1 () ;

    Task.TaskManager () ;
    System.out.println ( "All done" ) ;
  }
}


MultitaskingJBotTest1プログラムは自分自身のタスクオブジェクトを作り、おなじみのTask.TaskManager呼び出しを用いて走るように開始します。executeメソッドの中でほんの数状態しか使わないので定義された定数を用いずにちょっとインチキしています。

MultitaskingJBotのタスクオブジェクトはMultitaskingJBotTest1タスクオブジェクトが作られたときに作られ、実際に始めに二つのタスクが走ります。MultitaskingJBotオブジェクトの参照は保存され、JBotInterfaceを基本としたオブジェクトでのみ用いられます。

メインタスクの最初の状態initialStateはJ-Botを前進させるjbot.moveを呼ぶことによってJ-Botを開始させます。メインタスクは次の状態を1に設定します。この1の状態はjbot.movementDoneメソッドでtrueを返すまで確認します。これはMultitaskingJBotオブジェクトでも起こりますが、例はいかに問題を起こさずに両方ともメソッドを使うことができるかを示しています。

movementDoneメソッドがtrueを返したとき、jbot.pivotメソッドが呼ばれ、続いてjbot.waitが呼ばれます。後者は次の状態を2に設定しそのタスクをsleep状態にします。MultitaskingJBotexecuteメソッドがmovementDoneメソッドがtrueを返し、causeNextEventが呼ばれたことを知らされる時にそのタスクは再開されます。これはMultitaskingJBotオブジェクトに対するnotifyメソッドを順に呼び、MultitaskingJBotは時間的に後の時点でTask.TaskManagerによって呼ばれるそれ自身のexecuteメソッドを用いて続いて再開します。executeメソッドで用いられる状態値は前に設定した2になります。MultitaskingJBotはこの時点で停止します。

メインタスクは状態2でjbot.turnを呼ぶためにその処理を繰り返し、状態3でサーボモータを停止します。状態3のjbot.waitは状態3ではjbot.stopが不可欠なので必要です。

実験4-3 障害物検知でのMultitaskingJ-Bot

前の実験におけるJ-Botのマルチタスクサポートはフィードバックなしの動作を扱いました。この実験では簡単なプログラムなので実際のセンサーを使いませんが、障害物検知のサポートを付け加えます。これは後の実験で付け加えます。

マルチタスクサポートはJ-Botが障害物の近くか接触しているかどうか決定するためのセンサーをモニターするタスクを持たせるには比較的簡単です。次の段階はセンサーオブジェクトの基本となるアブストラクトクラスを定義することです。このスーパークラスでオブジェクトを用いることはランダムウォークあるいは迷路探索のようなものを実装する動作アルゴリズムを使えるようにし、そして、異なったタイプのセンサーのこの実装を用います。

この実験では、センサーのシミュレーションを作ります。それは乱数発生器を基本としています。これは単純な衝突回避アルゴリズムを用いています。

次のリストは、センサーに対するアブストラクトクラスの定義です。

基本的なアブストラクト型のsensorクラス
package JBot;

import stamp.util.os.* ;

/**
 * 基本的なアブストラクト型のsensorクラス
 * 
 * センサー情報にアクセスするための標準メソッド
 * 通常センサーは変化が起こったときにイベントを通知します。
 * 多くの場合イベントは障害物を検出するために待機するようなタスク
 *
 * 検出は180度前方であることを仮定する
 * 前方位置は90度である。
 * 最左は0度、左は45度である。
 */

public abstract class BaseSensor {
  protected Event event = Event.nullEvent ;
  protected boolean ready = true ;

  /**
   * 範囲: 障害物は検出されていない
   */
  static final int none = -1 ;

  /**
   * 検出: 障害物は左
   */
  static final int left = 45 ;

  /**
   * 検出: 障害物は右
   */
  static final int right = 135 ;

  /**
   * 検出: 障害物は前
   */
  static final int front = 90 ;

  /**
   * obstacleDetectedが呼ばれることができるかどうかを示す
   *
   * 戻り:boolean: 可能ならばtrue
   */
  public boolean ready () {
    return ready ;
  }

  /**
   * 障害物が検出されたかどうか示す
   * 通常、ポーリング対イベント使用のときに用いられる
   *
   * 戻り:boolean
   */
  public abstract boolean obstacleDetected () ;

  /**
   * 初期障害物位置を示す
   * 単純な検出システムに対して左と右の物体検出は前方を返す
   *
   * 戻り:障害物の相対的方向 (left, right, etc.)
   */
  public abstract int obstacleDirection () ;

  /**
   * 指定された方向にある障害物の距離を得る。
   * 値noneは障害物が無いことを表す
   *
   * 入力:int direction: 探したい方向
   *
   * 戻り:指定された方向に対する障害物までの距離
   */
  public abstract int obstacleDistance ( int direction ) ;

  /**
   * イベントの最小報告距離を設定する
   * この距離の外側での障害物は報告は起こらない
   * 最小値は0である。
   *
   * 入力:int minimumDistance:障害物を検出する最少数(インチ)
   */
  public void setMinimumEventDistance () {
    /* デフォールトの場合最小距離は無視する
     * 例えば、接触方向センサーは接触したときにだけ物体を検出できる。
     */
  }

  // このクラスあるいはサブクラスでの使用のためProtectedクラスにする

  /**
   * 報告イベントを設定する
   *
   * 入力:Event event: 変化が起こったときに報告するEventオブジェクト
   */
  public void setEvent ( Event event ) {
    this.event = Event.checkEvent(event) ;
  }

  /**
   * 障害物のステータスが変化したときにイベントを生じる
   * サブクラスのメソッドによって呼ばれる
   */
  protected void notify () {
    notify (null) ;
  }

  /**
   * 障害物のステータスが変化したときにイベントを生じる
   * サブクラスのメソッドによって呼ばれる
   */
  protected void notify (Object object) {
    event.notify (this) ;
  }
}


センサーは多くのことをできると仮定します。それは障害物を検出できます。J-Botに相対的な方向を決定したり、障害物までの距離を決定したりできます。アブストラクトクラスでのサポートは高解像度ですが、実装ではそうではないでしょう。例えば、次に出てくる接触ヒゲセンサーは左と右の物体を検出するように限られ、物体はJ-Botが検出したときは接触に違いないので、J-Botからの距離はいつも0です。赤外線範囲センサーはさらに広い正確な読みを与えます。

次のリストはBaseSensorのサブクラスのサンプルです。以前に注意したように、障害物が見つかったときを示すのに乱数発生器を用いています。

Random sensorクラス
package JBot;

import stamp.util.os.* ;
import java.util.* ;  // Randomクラスのため

/**
 * Random sensorクラス
 * 
 * このセンサーはランダム障害物情報を発生する
 * 確かめられたときに障害物情報の変化でのみ動作する
 * 方向と距離はランダムである
 */

public class RandomSensor extends BaseSensor {
  protected int direction ;
  protected int distance ;
  protected Random generator = new Random () ;

  /**
   * 0とlimitの間の乱数を発生する
   *
   * 入力:int limit: 乱数の最大値
   *
   * 戻り:int 検出された障害物
   */
  protected int random ( int limit ) {
    int result = generator.next () / (Random.MAX_RAND/(limit+1)) ;

    return ( result > limit ) ? ( result - 1 ) : result ;
  }

  /**
   * 障害物が検出されたかどうか示す
   * 通常、確認対イベント使用のときに用いられる
   *
   * 戻り:boolean
   */
  public boolean obstacleDetected () {
    if ( random ( 9 ) == 0 ) {
      // 障害物は1:10 で発見される

      direction = random(4)*45 ;  // 0-180の値
      distance = random(10) ;
      notify () ;
      return true ;
    } else {
      // 何も検出されない。保存された値をリセットする。

      direction = 0 ;
      distance = none ;
      return false ;
    }
  }

  /**
   * 初期障害物位置を示す
   * 単純な検出システムに対して左と右の物体検出は前方を返す
   *
   * 戻り:障害物の相対的方向 (left, right, etc.)
   */
  public int obstacleDirection () {
    return direction ;
  }

  /**
   * 指定された方向にある障害物の距離を得る。
   * 値noneは障害物が無いことを表す
   *
   * 入力:int direction: 探したい方向
   *
   * 戻り:指定された方向に対する障害物までの距離
   */
  public int obstacleDistance ( int direction ) {
    return distance ;
  }

  /**
   * イベントの最小報告距離を設定する
   * この距離の外側での障害物は報告は起こらない
   * 最小値は0である。
   *
   * 入力:int minimumDistance:障害物を検出する最少数(インチ)
   */
  public void setMinimumEventDistance () {
    /* デフォールトの場合最小距離は無視する
     * 例えば、接触方向センサーは接触したときにだけ物体を検出できる。
     */
  }
}


障害物の乱数発生は1/10秒におよそ1回起こり、ランダムセンサーオブジェクトはobstacleDetectedメソッドを用いて報告されます。ランダムな方向と距離を保存し、制御タスクはオブジェクトが再び確認されるまで変わらない値を得ます。ここでは、イベントオブジェクトはテストプログラムの中では設定されていませんが、どのように物体が検出されたときにnotifyメソッドが呼ばれるかを後のアプリケーションで使うでしょう。もし、センサーオブジェクトが障害物情報を用いている物体に独立に動作しているならば、タスクはイベントを通して伝えられるステータスの中の変化と確認をすることを設定することができます。

テストクラスの中にJ-Botをサポートするクラスを変わりに入れると、障害物回避に対する単独のサポートクラスを作ります。これは、後で出てくるほかのアプリケーションで用いられるタスクに使えます。次のリストはAvoidObstacleTaskクラスのソースコードです。

単純な障害物回避タスク
package JBot;

import stamp.util.os.* ;

/**
 * 単純な障害物回避タスク
 * 
 * センサーオブジェクトを用いて障害物から離れているように試みる
 * J-Botは固定の増加量で移動する
 */

public class AvoidObstacleTask extends Task {
  JBotInterface jbot ;
  BaseSensor sensor ;

  public AvoidObstacleTask ( BaseSensor sensor, JBotInterface jbot ) {
    this.sensor = sensor ;
    this.jbot = jbot ;
  }

  protected void execute () {
    final int turnAround = 1 ;

    switch ( state ) {
    case initialState:
      if ( sensor.obstacleDetected ()) {
        int direction = sensor.obstacleDirection () ;

        if ( sensor.obstacleDistance ( direction ) < 5 ) {
          // 接近しすぎ、5センチバックして回転する

          jbot.move ( -5 ) ;
          jbot.wait ( turnAround ) ;
        } else {
          // オブジェクトから離れて旋回する十分な領域

          if ( direction < 75 ) {
            // 左に何かがある
            jbot.pivot ( -2 ) ;
          } else {
            // 前か左に何かある
            jbot.pivot ( 2 ) ;
          }
          jbot.wait ( turnAround ) ;
        }
      } else {
        // 何もない。2センチ前に移動する。

        jbot.move ( 2 ) ;
        jbot.wait ( initialState ) ;
      }
      break;

    case turnAround:
      // J-Botを後退させる。180度旋回させる時間

      jbot.pivot ( 4 ) ;
      jbot.wait ( initialState ) ;
      break;

    default:                    // デフォールトは異常な状態を捕獲する
      stop () ;
      break;
    }
  }
}


AvoidObstacleTaskはそのコンストラクターの中にセンサーとJBotInterfaceが必要です。これはシングルタスクのセンサーとJBotInterfaceからマルチタスク版まで任意の組み合わせで用いることができます。

AvoidObstacleTaskは移動するのに単純なアルゴリズムを用いています。物体が道筋にあるかどうか知るためにセンサーをチェックします。もし物体がJ-Botに接触あるいは非常に接近した場合は、プログラムはJ-Botを後退させ4旋回ステップで180度回転させます。もし障害物が遠く離れていた場合は、J-Botは障害物から遠いところで旋回を試みます。障害物が検出されない場合は、J-Botは1インチ前に移動します。

プログラムには2つの前提があります。一つ目は、何も障害物が検出されない場合は1インチ移動できるということ。もう一つは、出発したら後ろには障害物はないということです。始めの前提は接触センサーでは正しくありませんが、障害物が1インチほど押されるか、もし動かせない固定物体にぶつかった場合、短時間車輪が空回りするかどちらかなので、現実的な意味で働くでしょう。どちらの場合でも、J-Botは補正して永久に続行できるようにすべきです。

大部分の仕事は今提示したAvoidObstacleTaskクラスの中でなされるから、テストプログラムは劇的に短くなっています。

AvoidObjstacleTaskクラスのテスト
import stamp.core.*;
import stamp.util.os.* ;
import JBot.* ;

/**
 * AvoidObjstacleTaskクラスのテスト
 * 
 * 障害物を避けるようにJ-Botを回転させる
 */

public class AvoidObstacleTaskTest1 {
  public static void main () {
    new AvoidObstacleTask
          ( new RandomSensor ()
          , new RampingJBot ( new MultitaskingJBot ())) ;

    Task.TaskManager () ;
    System.out.println ( "All done" ) ;
  }
}


テストプログラムはAvoidObstacleTaskオブジェクトを作るシンプルなmainメソッドです。タスクへのパラメータはRandomSensorRampingJBotオブジェクトです。J-Botはあたかも内蔵のセンサーを用いて障害物を検出しているかのように周りをうろつきます。適切なセンサーを用いてテストプログラムを単純に変えることによって後で定義される他のセンサーを提示されるクラスを用いることができます。もしセンサーがタスクを要求したら、センサーオブジェクトによってもメインメソッドでも同様に作ることができます。

テストプログラムが利用できるので、J-Botはプログラムできるようになり、永久に走るでしょう。勿論、動きは決まった時間の後に他のタスクをストップさせるようなタイマータスクを加えることにより制限することができます。これは、章の終わりに書いてある練習問題のひとつです。

実験4-4 タスクステータスとガーベージコレクション無しでの動作

この時点までに提示したマルチタスクのプログラムは比較的シンプルでテストされています。タスクの数が増えたりタスクがより複雑になってデバッグがより複雑になります。幸運にもデバッグが楽になる方法があります。

この時点で、Javelinを用いてメモリー割り当てを簡単に見るのが便利です。特にJavelinはガーベージコレクションをサポートしていません。これはそのアプリケーションを終了させてしまうほど全てのメモリーを割り付けてしまうことが可能ですから、もしアプリケーションが適切に設計されていない場合に問題になります。もしJavelinがIDEが走っているPCにつながっているのなら、Javelinはエラーをデバッガーに通知するでしょう。

何かが起こる前に問題を捕捉することが好ましいでしょう。マルチタスクシステムの動向をつかむ、いくつかのステータスクラスとタスクを提示します。それらはどんな組み合わせのタスクでも用いることができます。今回の実験では何処でも用いていませんが、新しいアプリケーションを作る時や修正する時に問題に遭遇した場合に付け加えるべきです。

最初のステップはShowStatusクラスです。これはTaskStatusSemaphoreStatusクラスの基礎になります。次のソースがShowStatusクラスです。

マルチタスクのステータスをサポートするクラス
package stamp.util.os;

import java.io.* ;
import stamp.core.* ;


/**
 * マルチタスクのステータスをサポートするクラス
 * このクラスはオブジェクトのみ
 */

public class ShowStatus {
  static public PrintStream stream = System.out ;

  protected static void print ( Event event ) {
    stream.print ( "<") ;
    stream.print ( event.name ()) ;
    stream.print ( "> ") ;
  }

  protected static void print ( int value ) {
    stream.print ( value ) ;
  }

  protected static void print ( String string ) {
    stream.print ( string ) ;
  }

  protected static void println ( String string ) {
    stream.println ( string ) ;
  }

  protected static void println () {
    stream.println ( " " ) ;
  }
}


このクラスは、TaskStatusSemaphoreStatusクラスのようにクラスメソッドのみでできています。コンストラクタは何もしないクラスオブジェクトを作るのを防ぐように保護されています。このクラスはstream変数を変えることによってリダイレクトされる基本的なprintをサポートしています。EventクラスはTaskSemaphoreクラスね基本であることを注意してください。

次はTaskStatusクラスです。

マルチタスクのステータスを表示する
package stamp.util.os;

import java.io.* ;
import stamp.core.* ;


/**
 * マルチタスクのステータスを表示する
 * 
 * アクティブタスクとフリーメモリを表示する
 * これはクラスオブジェクトのみ
 */

public class TaskStatus extends ShowStatus {
  protected TaskStatus () {
    // オブジェクトの生成を防ぐ
  }

  protected static void printTaskStatus ( int taskStatus ) {
    switch ( taskStatus ) {
    case Task.taskRunning:
      print ( "Running   " ) ;
      break ;

    case Task.taskSuspended:
      print ( "Suspended " ) ;
      break ;

    case Task.taskSemaphoreWait:
      print ( "Semaphore " ) ;
      break ;

    default:
      print ( "-- Unknown status -- " ) ;
      break ;
    }
  }

  protected static void printStateName ( int state ) {
    switch ( state ) {
    case Task.initialState:
      print ( "initial " ) ;
      break ;

    case Task.checkTimer:
      print ( "sleeping" ) ;
      break ;

    case Task.interrupt:
      print ( "interrupt" ) ;
      break ;

    case Task.terminate:
      print ( "terminate" ) ;
      break ;

    case Task.stopped:
      print ( "stopped  " ) ;
      break ;

    default:
      print ( state ) ;
      break ;
    }
  }

  /**
   * メモリーのステータスを書き出す
   */
  public static void printMemoryStatus () {
    print   ( Memory.freeMemory ()) ;
    println ( " bytes free" ) ;
  }


  /**
   * タスクのステータスを書き出す
   */
  public static void printTaskStatus ( Task task ) {
    print   ( task ) ;
    printTaskStatus ( task.taskStatus ) ;
    printStateName ( task.state ) ;
    println () ;
  }

  /**
   * タスクのリストを書き出す
   *
   * 入力:String prefix: タスクのリストの前に書く
   * 入力:boolean running; もし走っているタスクをリストする場合にtrue
   */
  public static void printTasks ( String prefix, boolean running ) {
    println ( prefix ) ;

    for ( Task task = Task.taskStatusList
        ; task != null
        ; task = task.nextTaskStatus
        ) {
      if ( task.taskStatus() == task.taskRunning ) {
        print ( " " ) ;
        printTaskStatus ( task ) ;
      }
    }
  }

  /**
   * 全てのタスクの詳細な説明をする
   *
   */
  public static void show () {
    Task task = Task.taskList ;

    println ( "== Task Status ==" ) ;
    printMemoryStatus () ;

    printTasks ( "Tasks running", true ) ;
    printTasks ( "Tasks not running", false ) ;

    if ( task == null ) {
      println ( "-- Empty task list --" ) ;
    } else {
      // Check taskList integrity
      do {
        task = task.nextTask ;
        if ( task == null ) {
          println ( "== Error: Task list not circular ==" ) ;
          break ;
        }
      } while ( Task.taskList != task ) ;
    }

    println () ;
  }
}


showメソッドは全てのタスクの詳細な説明をします。個々のタスクステータスはprintTaskStatusを用いて得ることができます。このサポートはTaskクラスの代わりにこのクラスに含まれているので、もしステータスメソッドが使われなければTaskを用いてオーバーヘッドが付加されません。

SemaphoreStatusクラスはTaskStatusクラスに非常に似ています。

マルチタスクのステータスを表示する
package stamp.util.os;

import java.io.* ;
import stamp.core.* ;


/**
 * マルチタスクのステータスを表示する
 * 
 * アクティブタスクとフリーメモリを表示する
 * これはクラスオブジェクトのみ
 */

public class SemaphoreStatus extends ShowStatus {
  protected SemaphoreStatus () {
    // オブジェクトの生成を防ぐ
  }

  /**
   * セマフォ情報を含んだタスクステータスを書き出す
   */
  public static void printTaskStatus ( Task task ) {
    // 一般的なステータスを書き出す
    TaskStatus.printTaskStatus ( task ) ;

    // 取得したセマフォを書き出す
    for ( Semaphore semaphore = Semaphore.semaphoreList
        ; semaphore != null
        ; semaphore = semaphore.nextSemaphore
        ) {
      if ( semaphore.acquiredTask == task ) {
        print ( " Acquired " ) ;
        print ( semaphore ) ;
        println () ;
      }
    }

    // セマフォ待ちステータスを書き出す
    for ( Semaphore semaphore = Semaphore.semaphoreList
        ; semaphore != null
        ; semaphore = semaphore.nextSemaphore
        ) {
      for ( Task waitingTask = semaphore.waitingTaskList
          ; waitingTask != null
          ; waitingTask = waitingTask.nextTask
          ) {
        if ( waitingTask == task ) {
          print ( " Waiting for " ) ;
          print ( semaphore ) ;
          println () ;
        }
      }
    }
  }

  /**
   * 全てのセマフォの詳細な説明をする
   */
  public static void show () {
    println ( "== Semaphore Status ==" ) ;

    for ( Semaphore semaphore = Semaphore.semaphoreList
        ; semaphore != null
        ; semaphore = semaphore.nextSemaphore
        ) {
      print ( semaphore ) ;
      if ( semaphore.ready ()) {
        println ( "ready" ) ;
      } else {
        print ( "acquired by " ) ;
        print ( semaphore.acquiredTask) ;
        println () ;

        if ( semaphore.acquiredTask.state == Task.stopped ) {
          println ( " ** Error: Task stopped **" ) ;
        }

        println ( " Waiting tasks" ) ;

        for ( Task task = semaphore.waitingTaskList
            ; task != null
            ; task = task.nextTask
            ) {
          print ( "  " ) ;
          print ( task ) ;
          println () ;
        }
      }
    }

    println () ;
  }
}


showメソッドはTaskStatusクラスの中のshowメソッドと似た動作をします。そのメソッドは、タスクに含まれている詳細を与える各々のセマフォのsemaphoreListwaitingTaskListを歩き回ります。printTaskStatusメソッドは同じ名前のTaskStatusと比べてより詳細な報告をします。両方のshowメソッドは通常一緒に呼ばれますが、SemaphoreStatus.printTaskStatusTaskStatusクラスで与えられる情報のスーパーセット(上位集合)なので単独で通常使われます。一般的にもしセマフォがアプリケーションによって用いられないならばSemaphoreStatusクラスは必要がありません。

次はWatchHeapTaskです。このタスクはメモリー利用と変化の報告を追跡するように設計されています。次のソースがこのクラス定義です。

ヒープ空間を監視するクラス
package stamp.util.os;

import stamp.core.*;
import stamp.util.os.*;

/**
 * ヒープ空間を監視するクラス
 * 
 * 全てのメモリーが割り当てられた後でこのタスクを開始する
 * フリーメモリーを追跡し、変化を報告する。
 */

public class WatchHeapTask extends Task {
  protected int     freeMemory = 0 ;
  protected boolean enable = true ;
  public    Event   event ;

  /**
   * WatchHeapTaskを作る
   * System.outにエラーを報告する
   */
  public WatchHeapTask () {
  }


  /**
   * Control error checking
   */
  public void enable ( boolean enableNow ) {
    enable = enableNow ;

    if ( enableNow ) {
      freeMemory = 0 ;
    }
  }

  /**
   * タスク名を返す
   */
  public String name () {
    return "WatchHeap" ;
  }

  /**
   * WatchHeapTaskを作る
   * イベントを通知することによりエラーを報告する
   *
   * 入力け:Event event:ヒープが成長したときに通知するイベント
   */
  public WatchHeapTask ( Event event ) {
    this.event = event ;
  }

  protected void execute () {
    /* Memory.freeMemoryはスタック位置依存である
     * 一貫性のある情報を与えるために同じ位置で呼ばれる必要がある
     */
    int latestFreeMemory = Memory.freeMemory () ;

    // このタスクだけが走っているならば終了する

    if ( nextTask == this ) {
      stop () ;
      return ;
    }

    if ( enable ) {
      if ( freeMemory == 0 ) {
        freeMemory = latestFreeMemory ;
      } else if ( freeMemory != latestFreeMemory ) {
        // Memory usage has increased (cannot decrease)

        if ( event == null ) {
          System.out.println ( "Error: Memory leak" ) ;
          System.out.print   ( " Last: " ) ;
          System.out.println ( freeMemory ) ;
          System.out.print   ( " Now:  " ) ;
          System.out.println ( latestFreeMemory ) ;
        } else {
          event.notify () ;
        }

        // Reset low water mark
        freeMemory = latestFreeMemory ;
      }
    }
  }
}


メモリーチェックはenableメソッドでenableにしたりdisableにしたりできます。チェックすることを可能にすることはfreeMemory変数をリセットして、そのタスク実行を次に設定します。これはそのタスクを一時停止するのと異なることに注意してください。後者の場合、freeMemory変数はリセットされますが、タスクはそのタスクが再開されるまでどんな変化でも検出することができません。

この全ての新しく見つけた情報で何ができるでしょうか。元のマルチタスクのデモプログラムに戻って見ると次のようになります。

MutlitaskingTest6
import stamp.core.*;
import stamp.util.os.*;

/**
 * MutlitaskingTest6
 */

public class MutlitaskingTest6 extends Task {
  // execute() states
  final static int state1 = 1 ;
  final static int state2 = 2 ;
  final static int state3 = 3 ;

  static Semaphore semaphore = new Semaphore () ;

  String name ;

  MutlitaskingTest6 ( String name ) {
    this.name = name ;
  }

  /**
   * タスク名を得る
   *
   * 戻り: タスク名
   */
  public String name () {
    return name ;
  }

  public void show ( String text ) {
    System.out.print ( name ) ;
    System.out.print ( ": " ) ;
    System.out.println ( text ) ;
  }

  protected void execute () {
    switch ( state ) {
    case initialState:
      show ( "Initial state" ) ;
      nextState ( state1 ) ;
      break ;

    case state1:
      show ( "State 1" ) ;
      new Integer ( 0 ) ; // memory leak !!!
      nextState ( state2 ) ;
      break ;

    case state2:
      show ( "State 2" ) ;
      if ( ! semaphore.acquire ( state3 )) {
        stop () ;
      }
      break ;

    case state3:
      show ( "State 3" ) ;
    default:    // おかしな状態を捕獲した場合はデフォールトで終了
      stop () ;
      break;
    }
  }

  public static void main() {
    Task task1 = new MutlitaskingTest6 ( "Task 1" ) ;
    Task task2 = new MutlitaskingTest6 ( "Task 2" ) ;
    Task task3 = new MutlitaskingTest6 ( "Task 3" ) ;
    Task task4 = new WatchHeapTask () ;

    // 一般的なステータスを表示
    TaskStatus.show () ;
    SemaphoreStatus.show () ;

    // タスクを走らせる
    Task.TaskManager () ;

    // 一般的なステータスを表示
    TaskStatus.show () ;
    SemaphoreStatus.show () ;

    // ステータスを表示
    SemaphoreStatus.printTaskStatus ( task1 ) ;
    SemaphoreStatus.printTaskStatus ( task2 ) ;
    SemaphoreStatus.printTaskStatus ( task3 ) ;
    SemaphoreStatus.printTaskStatus ( task4 ) ;

    System.out.println ( "All done" ) ;
  }
}


MutitaskingTest6オブジェクトは全部それに付けられている唯一の名前を持っています。これはもしクラスのただ1つのインスタンスが作られるならば、クラス定義で度々行われます。この場合、3つの異なったタスクがデバッグに利用できます。ただ1つのSemaphoreがありその名前はデフォールトとして残してあります。

TaskStatusSemaphoreStatusshowメソッドはタスクマネージャが走っているプログラムの開始する前後の一般的な概観を見るのに用いられます。これらのメソッドはシステムが同様に走っている間呼ばれることができます。個々のタスクステータスの情報はprintTaskStatusメソッドを用いて終わりに表示されます。

WatchHeapTaskオブジェクトは同様に作られます。Integerオブジェクトとして割り付けることによってMultitaskingTest6のstate1でメモリーリークをしています。これはフリースペースの総量を減らすからどんなJavaのオブジェクトでもできます。

mainメソッドを走らせることはメッセージウィンドウの中に短いテキストを表示します。初期のスタータス情報は走っている4つのタスクを表示します。全ての3つのタスクはほとんど同時にメモリーを割り付けるからただ一度だけですが、WatchHeapTaskはメモリーリークも報告します。WatchHeapTaskタスクは1つの増加に気付くだけですが、その問題をあなたに通知します。

終わりでの情報はexecuteメソッドの中でプログラムされた2つのエラーを表示します。最初のは孤立したセマフォを取得した間task1が停止したことです。2番目の問題は他の2つのタスクが決して解放されないこのセマフォで待機になっていることです。これは他の2つのタスクが決して続けられないことを意味しています。

MultitaskingTest6クラスは2つの共通のプログラムエラーといかに検出したかを表示します。そのステータスクラスは同様に表示されます。それらはマルチタスクのアプリケーションの実行の中に考慮されます。

問題4-4

  1. プリエンプティブとノンプリエンプティブのマルチタスクシステムの違いを自分の言葉で説明しなさい。

  2. バックグラウンドの仮想周辺マルチタスクとノンプリエンプティブな状態遷移マシンのクラスの違いを説明しなさい。

  3. あるタスクがCPU.delay(30000)と呼んだら他のタスクはどのようなことが起こるか?

  4. セマフォオブジェクトを何故使うのか説明しなさい。

  5. J-Botをコントロールする二つのタスクを設定しなさい。同時に一つのタスクしかアクセスできないように制限するセマフォを用いなさい。

  6. DistributeEventオブジェクトにゼロあるいはそれ以上のイベントを加える(取り去る)ことの出来るDistributeEventクラスを作りなさい。

  7. AvoidObstacleTaskTest1プログラムにタイマー機能を入れたタスクを加えなさい。そのタイマータスクは15秒間スリープしていて、J-Botをコントロールしている他のタスクを停止させます。

  8. WatchHeapTaskは現在メモリーリークがあるかをチェックします。フリーメモリーが少なくなったかどうかをチェックすることも出来ます。フリーメモリーが1000byte以下になったらエラーを発生するように、executeメソッドにこのタイプのチェックを加えなさい。テストを確かめるためにこのエラーが起こる十分なオブジェクトを作りなさい。その後、制限値を変数になるようにクラスを修正しなさい。

  9. マルチタスクをするJ-Botは二つのタスクだけを用いています。J-Botが床を動き回る間に音を鳴らすためにTaskToneGeneratorを用いるもう一つのタスクを付け加えなさい。
    音の発生はJ-Botの動作に影響するだろうか?

  10. 残念なことにJ-Botは動作をしているときには普通PCと接続していません。J-Bot限られた動作に対してプログラムされているときJ-BotをPCに接続し続けるのは困難です。以前の実験のようにプログラムを走らせることは、実際的ではありません。J-Botはエラーが起こったときに単純に停止します。メモリリークエラーが起こった場合に音が鳴るようにWatchHeapSpaceタスクを用いなさい。ヒント:TaskToneGeneratorの代わりにFreqoutを用いる。

  11. オンデマンドでステータス情報を得られると便利です。メッセージウィンドウへのPCのキーボード入力を待つタスクを使えば可能になります。入力をポーリングしスペースキーが押されたときにステータス情報を配信するようなタスクを付け加えなさい。

実験4終わり