EV3RTを使ってみる(5.5)

Toppers/EV3RTを使ってプログラムを作成したときの記事です。
この記事を書いた時のEV3RTのバージョンはβ3です。
目次はこちら

前回の記事で排他制御の1つであるmutex機能の紹介をしましたが、このmutex機能を使うときに注意しなくてはならないことがあるようですので、今回は下記のプログラムを使ってmutexの実装方式を確認してみます。今回使うプログラムは前回のプログラムとほとんど同じですが、高優先度タスクであるtask1の中のアンロック処理(unl_mtx)とロック処理(loc_mtx)との間にあったsleep処理(tslp_tsk)を削除しています。つまり、task1はMTX1をアンロックすると待機処理を含まずに再びロックを試みるようなプログラムになっています。

INCLUDE("app_common.cfg");

#include "app.h"

DOMAIN(TDOM_APP) {
  CRE_TSK(TASK1, { TA_ACT, 0, task1, TMIN_APP_TPRI + 1, STACK_SIZE, NULL });
  CRE_TSK(TASK2, { TA_ACT, 0, task2, TMIN_APP_TPRI + 2, STACK_SIZE, NULL });
  CRE_MTX(MTX1, { TA_TPRI, 0 });
}

ATT_MOD("app.o");
void task1(intptr_t exinf);
void task2(intptr_t exinf);
void display();
ulong_t getTime();
#include "ev3api.h"
#include "app.h"
#include "kernel_cfg.h"

int count1 = 0;
int count2 = 0;

/* 高優先度タスク */
void task1(intptr_t exinf)
{
  while(getTime() < 10000){
    loc_mtx(MTX1);

    for(int i = 0; i < 10; i++){
      count1 += 1;
      display();
      tslp_tsk(100);
    }

    unl_mtx(MTX1);
    /* 待機せずにMTX1のロックを試みる */
  }
}

/* 低優先度タスク */
void task2(intptr_t exinf)
{
  while(getTime() < 10000){
    loc_mtx(MTX1);

    for(int i = 0; i < 10; i++){
      count2 += 1;
      display();
      tslp_tsk(100);
    }

    unl_mtx(MTX1);
    tslp_tsk(100);
  }
}

/* 状態を表示する。 */
void display()
{
  char message1[32];
  char message2[32];
  char message3[32];
  int time = (int) getTime();

  sprintf(message1, "time = %d", time);
  sprintf(message2, "count1 = %d", count1);
  sprintf(message3, "count2 = %d", count2);

  ev3_led_set_color(LED_GREEN);
  ev3_lcd_set_font(EV3_FONT_MEDIUM);
  ev3_lcd_draw_string(message1, 20, 30);
  ev3_lcd_draw_string(message2, 20, 50);
  ev3_lcd_draw_string(message3, 20, 70);
}

/* 経過時間を返す。 */
ulong_t getTime()
{
  static ulong_t start = -1;
  ulong_t time;

  get_tim(&time);

  if(start < 0){
    start = time;
  }

  return time - start;
}

さて、この排他制御の実装方式には大きく分けて2通りが存在するため、このプログラムを実行した場合に予想される実行結果も2通り存在します。1つ目の実装方式は、アンロック処理(unl_mtx)では資源の解放だけを行い、ロック処理(loc_mtx)が呼ばれたときに資源を獲得するタスクを判定する方式です。この場合、高優先度タスクであるtask1が資源を専有することになりますので、下図の実行パターンAのようにtask1が資源を使い続けることになります。2つ目の実装方式は、アンロック処理(unl_mtx)が呼ばれた時に待機状態となっているタスクに資源解放を通知する方式です。この方式の場合、task1のアンロック処理(unl_mtx)からtask2のロック処理(loc_mtx)に処理が移ることになりますので、下図の実行パターンBのようにtask1とtask2が交互に実行されることになります。

2015-04-01-1

実際に上のプログラムを実行してみたところ、実行結果は上図の実行パターンBと同じ結果になりました。EV3RTのmutex実装では、アンロック処理(unl_mtx)を呼び出した時に待機状態のタスクに対して実行可能であることを通知する仕組みになっているようです 1

2015-04-01-2

さて、この実装方式を採用しているmutex機能ですが、使用上の注意点があります。それは、高優先度タスクがアンロック処理を実行した時点で、低優先度タスクのロック処理が実行される可能性があるという点です。例えば、高優先度タスクの中でロック処理とアンロック処理を繰り返している場合、高優先度タスクの処理途中に低優先度タスクの処理が割り込んでしまう可能性があります。そして、低優先度タスクの排他制御範囲内の処理が長い時間を要する場合、それは高優先度タスクの実行時間に決定的な影響を与えるかもしれません。

以前、ETロボコンのモデリング審査委員の先生から排他制御の設計についても書いて欲しいという話を聞いたのですが、この辺りの機能や特性を理解してモデリングしているのかを見たいのかもしれません。5ページしかないモデリング資料に排他制御の設計まで書くスペースなんか無いよと思ったりもしますけどね。

Notes:

  1. まさに教科書通りの正統派の実装方式ですね。