マルチタスク

マルチタスク、スレッド、状態遷移マシン

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

RobotStateMachine
class RobotStateMachine { 
private:
    static const int forward = 1;
    static const int pivotLeft = 2;
    static const int pivotRight = 3;
    int state;

public:
    RobotStateMachine()
    {
       state = forward ; 
    }
    void nextState ( int state ) {
        this->state = state;
    }

    int 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)という。ラウンドロビン方式ではすべてのプロセスが平等に扱われる。

これから、次のような階層構造のクラスを構築していきます。
この図の見方は、Eventクラスが親で、子供のクラスがSemaphoreクラスとTaskクラスです。孫クラスがInterruptTaskクラスということになります。

Event
Semaphore
Task
InterruptTask
TimerTask
TaskToneGenerator
CallableTask
WatchHeapTask
ShowStatus
TaskStatus
SemaphoreStatus

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


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

イベント



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

#include "common.h"

/**
 * イベントが生じたことを示すためのインターフェース
 */
class Event {
  public:
    static Event ev;
    static Event* nullEvent;
    static Event* checkEvent ( Event* event );
    void notify( void* object );
    void notify();
    char* name ();
    Event ();
    Event (int x);
    String Name;
};
#endif
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 




Event.cpp
#include "Event.h"

Event Event::ev;
Event* Event::nullEvent= &Event::ev;

Event* Event::checkEvent ( Event* event )
{
  return (event == nullEvent) ? nullEvent : event ;
}

void Event::notify( void* object ) {
}

void Event::notify() {
    notify(( void*)null);
}

char* Event::name () {
    return Name ;
}

Event::Event () {
  nullEvent = &ev;
  Name = "Event" ;
}
Event::Event (int x) {
  Name = "Event" ;
}
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28


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

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

タスク

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

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

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

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

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

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


Task.h
#ifndef _TASK
#define _TASK

#include "Event.h"
#include "mbed.h"

class Task : public Event {

  public:
    const static int taskRunning=0;
    const static int taskSuspended=1;
    const static int taskSemaphoreWait=2;
    const static int initialState=0;
    const static int checkTimer=-1;  // sleep()のサポート用
    const static int _interrupt=-2;  // InterruptTaskを見よ
    const static int terminate=-3;
    const static int stopped=-4;
    Timer timer;
  
    Task();      // コンストラクタ
    static void addTask(Task* aNewTask);
    char* name ();
    int taskStatus();
    int getState ();
    bool suspend ();
    static bool removeTask(Task* aTaskToRemove);
    bool stop();
    bool resume (); 
    bool start();
    void notify( void* object );
    void nextState(int state);
    void sleep(int state);
    void sleep ( int nextState, unsigned int hi, unsigned int lo ) ;
    void sleep ( int nextState, int timeMS ) ;
    void sleepSec ( int nextState, int timeS );
    static Task* getCurrentTask ();
    virtual int timeout(int tm);
    virtual int timeout(int hi, int lo);
    static void TaskManager();

    static Task* currentTask;
    Task* nextTask;
    int __taskStatus ;
    int state;
    static Task* taskStatusList;
    Task* nextTaskStatus;
    static Task* taskList;

  protected:
      virtual void execute(){}
      int sleepState;
      int hi, lo;
};

#endif
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 




Task.cpp
#include "Task.h"

Task* Task::taskStatusList  = (Task*)null ;
Task* Task::taskList = (Task*)null;
Task* Task::currentTask = (Task*)null ;
Task::Task() {
  timer = Timer();
  addTask ( this ) ;
    // タスクをステータスリストに加える
  nextTaskStatus = taskStatusList ;
  taskStatusList = this ;
  state = initialState;
  sleepState = checkTimer;
  Name = "Task";
}

void Task::addTask(Task* aNewTask) {
  if ( Task::taskList == (Task*)null ) {
    aNewTask->nextTask = aNewTask ;
  }
  else
  {
    aNewTask->nextTask = taskList->nextTask ;
    taskList->nextTask = aNewTask ;
  }
  aNewTask->__taskStatus = taskRunning ;
  taskList = aNewTask ;
}

bool Task::removeTask(Task* aTaskToRemove) {
    // 空のリストを始めに処理する

    if ( taskList != (Task*)null ) {
      // 現在のタスクからリストを走査し見つかったらそれを削除する
      Task* scanTask = taskList ;
      do {
        if ( scanTask->nextTask == aTaskToRemove ) {
          // 削除するタスクが見つかった
          if ( scanTask == aTaskToRemove ) {
            // リストの中の一つだけのタスクを削除する
            taskList = (Task*)null ;
          }
          else
          {
            // 現在のタスクが削除されたかどうかチェックする
            if ( taskList == aTaskToRemove ) {
              taskList = aTaskToRemove->nextTask ;
            }

            scanTask->nextTask = aTaskToRemove->nextTask ;
          }
          aTaskToRemove->nextTask  = (Task*)null ;
          return true ;
        }

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

void Task::TaskManager() {

      Task *p, *q;
      while ( taskList != (Task*)null ) {
        // execute()がremoveTaskを通して変えることができるように
        // taskListを変える
       q = p = taskList;
        while(p != (Task*)null) {
          p = p->nextTask;
          if(p == q) break;
        }
        currentTask = taskList ;
        taskList = taskList->nextTask ;
        if (currentTask->state == checkTimer) {
          // タスクがタイムアウトを待機している
          if (currentTask->timeout(currentTask->hi,currentTask->lo)) {
            // タイムアウトが起こったら、次の状態を設定する
            currentTask->state = currentTask->sleepState ;
            // もし、次の状態が不適切に設定されていたらば抜け出す
            if ( currentTask->state == checkTimer ) {
              currentTask->state = terminate ;
            }
          }

        } else {
          // 次の状態を呼び出す
          currentTask->execute () ;
        }
      }
    }

int Task::timeout(int tm)
{
  if(  timer.read_ms() >= tm) return true;
  else return false;
}

int Task::timeout(int hi, int lo)
{
  if(  timer.read_ms() >= lo) return true;
  else return false;
}

void Task::sleep ( int nextState, int timeMS ) {
  sleep ( nextState, 0, timeMS);
}

char* Task::name () {
  return Name ;
}

int Task::taskStatus() {
  return __taskStatus;
}

int Task::getState () {
  return state ;
}

bool Task::suspend () {
  if ( __taskStatus == taskRunning ) {
  // 注意: nextTaskがnullでなかったらマネージャーはnull
  // であってはいけない
    __taskStatus = taskSuspended ;
    return removeTask ( this ) ;
  }
  return false ;
}

bool Task::stop() {
  if ( suspend ()) {
    state = stopped ;
    return true ;
  }
  return false ;
}

bool Task::resume () {
  if ( __taskStatus == taskSuspended ) {
    // 注意: マネージャーはnextTaskがnullでなかったならばnullで
    //       であってはいけない
    addTask ( this ) ;
    return true;
  }
  return false ;
}

bool Task::start() {
  return resume () ;
}

void Task::notify( void* object ) {
  resume () ;
}

void Task::nextState(int state) {
  this->state = state ;
}

void Task::sleep(int state) {
  this->state = state ;
  suspend();
}

void Task::sleep ( int nextState, unsigned int hi, unsigned int lo ) 
{
  this->hi = hi;
  this->lo = lo;
  timer.reset();
  sleepState = nextState ;
  state = checkTimer ;
}

void Task::sleepSec ( int nextState, int timeS ) {
  sleep ( nextState, 0, timeS*1000);}

// タスクマネージャーサポート
Task* Task::getCurrentTask () {
  return currentTask ;
}
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 


protectedが付与されたメンバ変数、メソッドは同一クラス、 同一パッケージ、サブクラスから参照することができます。 注意点はサブクラスからアクセスできるのはサブクラスの オブジェクトを通してのみということです。サブクラスから スーパークラスのオブジェクトを通して参照することはできません。
Task.h, Task.cppファイルはオブジェクトとクラスメソッドからなっています。クラスメソッドは全てのアクティブなタスクを管理するタスクマネージャーを実装します。タスクマネージャーはTask::TaskManager()を用いて初期化されます。一つあるいは複数のタスクが作られた後に開始しなければいけません。

Taskクラスは仮想関数(仮想メソッド)execute()を含んでいるので抽象クラスと呼ばれます。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μ秒以下で作るのが良いでしょう。

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

例えば、

class task1 extends Task {
private:
    static const int initialState = 1 ;
    static const int newState = 2 ;
    static const int anotherState = 3 ; 

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

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

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

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

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