マルチタスク4

CallableTaskクラス CallableTask.h

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

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

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

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

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

CallableTask.h
#ifndef _CALLABLESTACKENTRY
#define _CALLABLESTACKENTRY

#include "InterruptTask.h"
#include "common.h"
#include "CallStackEntry.h"
class CallableTask : public InterruptTask
{
  // スタックはタスクに対して排他的である
protected:
  CallStackEntry* stack;   // 修正

/**
 * 現在のパラメータを得る
 * 戻り:直近のcallStateで渡されたパラメータを戻す
 */
public:
  CallableTask();
  Object getParameter ();
/**
 * 状態を呼ぶ
 *
 * 入力:int callState: 入る次の状態
 * 入力:Object parameter: 呼び出しパラメータ
 * 入力:int returnState: returnState()が呼ばれたときに入る状態
 */
  void callState ( int callState, Object parameter, int returnState );
  void setState ( int callState, Object parameter, int returnState );

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

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


CallableTaskクラス CallableTask.cpp



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

  /**
   * 現在のパラメータを得る
   * 戻り:直近のcallStateで渡されたパラメータを戻す
   */
CallableTask::CallableTask()
{
  stack  = (CallStackEntry*)null;
}

Object CallableTask::getParameter () {
    return ( stack == (CallStackEntry*)null ) ? (CallStackEntry*)null : (Object)(stack->parameter) ;
}

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

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

  if ( stack == (CallStackEntry*)null ) {
    state = terminate ;
  } else {
      // Setup return state
    state = stack->returnState ;

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

      // Free entry
    oldEntry->free () ;
  }
}


CallStackEntry.h

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

CallStackEntry.h
#ifndef _CALLSTACKENTRY
#define _CALLSTACKENTRY
#include "common.h"

/**
 * CallableTaskスタックを実装するのに用いる
 */
class CallStackEntry {
  protected:
    static CallStackEntry* freeList;
    CallStackEntry (){};
  public:
    void* parameter ;
    int returnState ;
    CallStackEntry* nextEntry ;
/**
  * freeリストのエントリーを戻す
  */
    void free ();
/**
 * 用いていないスタックエントリーを返す
 * フリーリストが空ならば新しいエントリーが作られる
 *
 * 入力:int returnState: 新しいエントリーに保存される
 * 入力:CallStackEntry nextEntry:新しいエントリーに保存される
 * 入力:Object parameter:新しいエントリーに保存される
 */
  static CallStackEntry* getEntry
      ( int returnState
      , CallStackEntry* nextEntry
      , void* parameter);
};
#endif


CallStackEntry.cpp



CallStackEntry.cpp
#include "CallStackEntry.h"
#include "common.h"
/**
  * freeリストのエントリーを戻す
  */
void CallStackEntry::free () {
  nextEntry = freeList ;
  freeList = this ;
}

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

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

  return result ;
}

CallStackEntry* CallStackEntry::freeList = (CallStackEntry*)null ;


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

CallStackEntrynewを用いてCallableTaskによって配置(allocate)されません。その代わりに、新しいオブジェクトを生成するgetEntryメソッドを呼びます。freeListが空の時のみそうなります。エントリーがもはや必要が無くなった場合、freeメソッドを用いてfreeListに戻されます。塵集め(gabage collection)が無いためmbed(C++)で行います。普通のJava、C#ではオブジェクトは単純に参照をなくせば、ガーベージコレクション(塵集め)を通して最後にはフリーメモリーに戻されます。
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クラスを持っています。

MutlitaskingTest4.h



MutlitaskingTest4.h
#include "CallableTask.h"
#include "common.h"

class MutlitaskingTest4 : public CallableTask
{
  private:
    static const int state1 = 1;
    static const int state2 = 2;
    static const int call1 = 10;
    static const int call2 = 20;
    static const int call3 = 30;
  protected:
    void execute ();
  public:
    MutlitaskingTest4();
};


MultitaskingTest4.cpp



MultitaskingTest4.cpp
#include "MultitaskingTest4.h"

void MutlitaskingTest4::execute ()
{
  switch ( state ) {
    case initialState:
      pc.printf ( "Starting\n" ) ;

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

    case state1:
      pc.printf ( "State 1\n" ) ;
      callState ( call2, state2 );
      break ;

    case state2:
      pc.printf ( "State 2\n" ) ;
      nextState ( terminate ) ;
      break ;

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

    case call1:
      pc.printf ( "Enter call 1\n" ) ;

      pc.printf ( "%s\n",(char*) getParameter ()) ;

      pc.printf ( "Exit call 1\n" ) ;
      returnState () ;
      break ;


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

    case call2:
      pc.printf ( "Entering call 2\n" ) ;
      callState ( call1, (void*)"test 2", call3 ) ;
      break ;

    case call3:
      pc.printf ( "Done with call 2\n" ) ;
      returnState () ;
      break ;

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

    default:    // 異常の状態を検知した場合終了する
       pc.printf("異常の状態 state:%d\n", state);
    case terminate:
      pc.printf ( "All done\n" ) ;
      stop () ;
      break;
  }
}

MutlitaskingTest4::MutlitaskingTest4()
{
  state = initialState;
  pc.printf("MutlitaskingTest4\n");
}


メインプログラム



main.cpp
#include "mbed.h"
#include "MultitaskingTest4.h"

Serial pc(USBTX, USBRX); // tx, rx

int main()
{
  pc.baud(115200);
  MutlitaskingTest4 mt4 ;

  Task::TaskManager () ;
  pc.printf( "main End\n" ) ;
}

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

実行結果
MutlitaskingTest4
Starting
Enter call 1
test base
Exit call 1
State 1
Entering call 2
Enter call 1
test 2
Exit call 1
Done with call 2
State 2
All done
main End

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