9.タスクについて
TOP
11.音を鳴らす

10.セマフォについて

前項においてマルチタスクを学習してました。これを用いれば、異なる複数の動作(例えば、モーターを回しながらLCDに表示するなど)を可能にします。しかし、タスクを並列に実行されるがゆえに起こる問題もあります。たとえば、タスクが他のタスクの実行を妨害することが挙げられます。この問題を解決するために用いられる変数をセマフォと呼びます。この項ではセマフォの簡単な説明と使用の仕方について説明します。

1.セマフォがない場合の問題

 まず、セマフォの役割を理解するために、セマフォ使用しない場合にどのような問題が起こるのか、間違ったプログラムをわざと動かしてみてその動作を見てみましょう。ここではタッチセンサーの使用の項で用いたタッチセンサーつきロボットを用います。今回は2つのタスクを用いそれぞれ以下の動作をするようにします。
次のプログラムを作成してください。ここでは作成ファイル名をsemaphore1.cとします。
作成したプログラムsemaphore1.c
#include <dmotor.h>
#include <unistd.h>
#include <dsensor.h>

#define MOVE_TIME 2000
#define TURN_TIME 1500

tid_t t_sensor_press,t_square;

int square(); // move square funciton
int sensor_press(); // sensor press funciton

int main(int argc, char *argv[]) {
	t_sensor_press=execi(&sensor_press,0, NULL,10, DEFAULT_STACK_SIZE);
	t_square=execi(&square,0, NULL,10, DEFAULT_STACK_SIZE);
	
	return 0;
}

int square(){ // move square funciton
	motor_a_speed(MAX_SPEED);
	motor_c_speed(MAX_SPEED);
	while(1){
		motor_a_dir(fwd);
		motor_c_dir(fwd);
		msleep(MOVE_TIME);

		motor_a_dir(fwd);
		motor_c_dir(rev);
		msleep(TURN_TIME);

	}
	return 0;
}

int sensor_press(){ // sensor press funciton
	motor_a_speed(MAX_SPEED);
	motor_c_speed(MAX_SPEED);
	while(1){
		if(SENSOR_1<0xf000){
			motor_a_dir(rev);
			motor_c_dir(rev);
			msleep(MOVE_TIME/2);

			motor_a_dir(fwd);
			motor_c_dir(rev);
			msleep(TURN_TIME);

		}
	}
	return 0;
}
 それでは実行してみてください。まず、最初に直進中にタッチセンサーを反応させて見ましょう。後退・90度右折してくれるはずです。これは予定通りの動作です。では、次に90度右回転中にタッチセンサーを反応させて見ましょう。後退後、前進するはずです。これではせっかく障害物を発見したのに避けていないことになります。このときの動作はおそらくこうです。
square()内の90度右折中は30行目のmsleep()が働いています。このとき、タッチセンサーが押されると、sensor_press()内の40行目から始まるif()に処理が移り、後退を始めます。しかし、後退中の43行目のmsleepで処理がsquare()に戻ってしまいます。こうなると、24行目から始まる直進動作に入っていまい、さらに26行目のmsleepでまたまた処理がsensor_press()に移り、45行目から始まる90度右折が始まります。ここの47行目のmsleepで処理がsquare()に戻ってしまい、28行目からの90度右折を開始してしまいます。結局のところ、動作は『90度右折中タッチセンサーON』⇒『後退』⇒『前進』⇒『90度右折』⇒『90度右折』ということになってしまいます。
 このようにマルチタスクでは1つのセンサー(モーター)の権利を奪い合っている状態が起こりうるのです。この問題を解決する方法として、どのタスクがモータを使用しているかを示す変数を用いることが挙げられます。つまり、あるタスクがこの変数にモーター使用中を示している間は、他のタスクはモーターを操作できなくなるのです。この変数がセマフォなのです。

2.セマフォを用いる

 さきほどのプログラムを改良してセマフォを使い、変数の値に応じてモータを制御しましょう。このとき、変数の値に応じてモータを制御しようとするタスクは次のような順序で実行しなければなりません。
  1. セマフォ用変数がNoneZeroになるまで待つ
  2. セマフォ用変数をZeroにする
  3. モーターを使用する
  4. セマフォ用変数を元に戻す
セマフォ用変数はZeroのときにモーターが使用していることになります。ですから、上記のように、モーターが解放される(変数がNoneZeroになる)まで待って、自分がモーターを使用することを示し(変数をZeroに設定)、使用した後でモータを解放(変数を元の値に戻す)することになります。次のプログラムはこの考え方を取り入れています
次のプログラムを作成してください。ここでは作成ファイル名をsemaphore2.cとします。
作成したプログラムsemaphore2.c
#include <dmotor.h>
#include <unistd.h>
#include <dsensor.h>
#include <semaphore.h> // semaphore function is included

#define MOVE_TIME 2000
#define TURN_TIME 1523

tid_t t_sensor_press,t_square;
sem_t t_motor; // semaphore variable

int sensor_press();
int square();

int main(int argc, char *argv[]) {
	sem_init(&t_motor,0,1); // initialization of a semaphore
	
	t_sensor_press=execi(&sensor_press,0, NULL,10, DEFAULT_STACK_SIZE);
	t_square=execi(&square,0, NULL,10, DEFAULT_STACK_SIZE);
	
	return 0;
}

int square(){
	motor_a_speed(MAX_SPEED);
	motor_c_speed(MAX_SPEED);
	while(1){
		sem_wait(&t_motor); // acquisition of a semaphore
		motor_a_dir(fwd);
		motor_c_dir(fwd);
		sem_post(&t_motor); // release of a semaphore
		msleep(MOVE_TIME);

		sem_wait(&t_motor); // acquisition of a semaphore
		motor_a_dir(fwd);
		motor_c_dir(rev);
		sem_post(&t_motor); // release of a semaphore
		msleep(TURN_TIME);

	}
	return 0;
}

int sensor_press(){
	motor_a_speed(MAX_SPEED);
	motor_c_speed(MAX_SPEED);
	while(1){
		if(SENSOR_1<0xf000){
			sem_wait(&t_motor); // acquisition of a semaphore
			motor_a_dir(rev);
			motor_c_dir(rev);
			msleep(MOVE_TIME/2);

			motor_a_dir(fwd);
			motor_c_dir(rev);
			msleep(TURN_TIME);
			sem_post(&t_motor); // release of a semaphore

		}
	}
	return 0;
}
ここでもう一度動かしてみてください。90度右折中にタッチセンサーを押してみましょう。『後退』⇒『前進』にはならないはずです。
セマフォ用のAPIは『semaphore.h』内に記述されています。セマフォ用変数はsem_t型で、今回は"t_motor"としてあります。また、セマフォ用変数を初期化する関数として『sem_init()』が提供されています。第1引数はセマフォ用変数へのポインタ、第3引数はその変数の初期値を表します。この値はの範囲は0〜255で、『センサー(モーター)を一度に利用できるタスクの数』を表しています。なので、これを"2"にすると2つのタスクでそのデバイスが同時に使用可能になります。モーターの場合は同時に使用はできないので"1"にしておくべきでしょう。また、『sem_wait()』はセマフォ用変数へのポインタを引数として、『セマフォ用変数がNoneZeroになるまで待つ』、『セマフォ用変数をZeroにする』を行ってくれる関数です。『sem_post()』はセマフォ用変数へのポインタを引数として、『セマフォ用変数を解放する』を行ってくれる関数です。

なお、このプログラムは、タッチセンサーの値を wake_up 関数を使わずに for ループで監視し続けています。このため、sensor_press タスクの優先度を square タスクよりも大きくしてしまった場合、sensor_press タスクのみが実行されることになってしいます。このプログラムでは優先度が同じですので、ラウンドロビンで両方のタスクが交互に実行されますが、本来は wake_up 関数を使って、タッチセンサの入力を監視すべきでしょう。

3.まとめ

 この章ではセマフォについて学習しました。また、マルチタスクに関連する問題点について、注意すべき事を学習しました。これら問題の対策としてセマフォはマルチタスクを含むプログラムには便利で、またほとんどの場合には必要となります。タスクとセマフォは合わせて理解しておいてください。
9.タスクについて
TOP
11.音を鳴らす