8.ボタンの制御
TOP
10.セマフォについて

9.タスクについて

一言で言うとタスクとはプログラムの実行単位です。ここまではタスクに関して特に意識してプログラムしていなかったことと思います。それは、タスクが1つだけ(シングルタスクといいます)でそれを順序通りこなしていくようなプログラムしか組んでなかったからです。しかし、複数の処理を同時に実行させたいとき、シングルタスクではできません。そういったときに、マルチタスクが必要となってきます。brickOSはマルチタスク機能を提供しています。マルチタスクとは複数のタスク(仕事)切り替えながら処理することでほぼ同時に処理していく仕組みのことをいいます。この項ではタスク管理の簡単な説明とマルチタスクプログラミングの仕方について説明します。

1.まずはタスクを知る

 まず、マルチタスクプログラムを組んでみて、その動作を見てましょう。
次のプログラムを作成してください。ここでは作成ファイル名をtask1.cとします。
作成したプログラムtask1.c
#include<conio.h>
#include<unistd.h> // execi function is included

tid_t t_task1,t_task2,t_task3;

int task1(){ // task1 function
	int roop;
	for(roop=0;roop<500;roop++){
		cputs("task1");
	}
	return 0;
}
int task2(){ // task2 fanction
	int roop;
	for(roop=0;roop<500;roop++){
		cputs("task2");
	}
	return 0;
}
int task3(){// task3 fanction
	int roop;
	for(roop=0;roop<500;roop++){
		cputs("task3");
	}
	return 0;
}

int main(int argc, char **argv) {
	t_task1=execi(&task1,0,NULL,10,DEFAULT_STACK_SIZE); // make task1
	t_task2=execi(&task2,0,NULL,15,DEFAULT_STACK_SIZE); // make task2
	t_task3=execi(&task3,0,NULL,20,DEFAULT_STACK_SIZE); // make task3
	return 0;
}
 それではタスクの説明に入りましょう。このプログラムではタスクは3つあります。task1,2,3はそれぞれLCDに『task1,2,3』と500回表示するタスクです。このタスクを生成するAPIが『execi()』です。これはtid_t型(タスクID)でタスク生成に成功したときタスクID番号を返し、失敗した場合は-1を返します。第1引数は実行関数(ここではtask1(),task2(),task3()のへポインタ、第2引数は実行関数への引数の数(ここではいずれも0個)、第3引数は実行関数への引数としての文字列へのポインタ(ここでは引数はなしなのでNULL)、第4引数は実行タスクの優先度、第5引数は実行タスクのスタックサイズ(byte)(ここではDEFAULT_STACK_SIZE)を示します。ここで注目して欲しいのがタスクの優先度です。優先度(プライオリティ)は1〜20で最高20、最低1です。これを踏まえた上でこのプログラムを実行してみてください。このときのタスクの状態を示します。(次の図ではタスク優先度が 10,50,100となっていますが、これを10,15,20と読み替えて下さい。)

brickOSでは1タイムスライス(timeslice)毎にタスクスケジューラが起動され、エントリーしたすべてのタスクのなかから優先度が高い順に実行権の評価を行って、実行条件を満たしたタスクが見つかると起動します。よって、優先度が10,15,20だった場合、LCDには『task3⇒2⇒1』と表示されるのです。今回は優先度をそれぞれ10,15,20としたのでその順番ですが、値を変えることにより表示される順番は変わってくるはずです。
brickOSでは、1タイムスライスは20msecに設定されています。
次に3つのタスクすべての優先度を同じ値にして下さい。ここではすべて15にしたとします。そのときのタスクの状態を示します。(次の図ではタスク優先度が100となっていますが、これを15と読み替えて下さい。)

brickOSでは優先度が同じ場合、1つのタスクにCPUを 一定時間(確認していないが、おそらく1 timeslice)利用させ、時間が過ぎたら待っている次のタスクの処理にCPUを利用させる方式(ラウンドロビン方式という)を用いています。よってこのときLCDには『1,2,3』が入れ替わり立ち代り表示されます。

2.sleepの意味

 いままで『sleep()』関数は頻繁に使ってきました。この関数は引数として指定した時間まで、そのタスクを眠らせるという動作をします。言い換えるとタスクの実行権を放棄しているのです。このとき、タスクはどうなっているのか見てましょう。
次のプログラムを作成してください。ここでは作成ファイル名をtask2.cとします。
作成したプログラムtask2.c
#include<conio.h>
#include<unistd.h> // execi function is included

tid_t t_task1,t_task2;

int task1(){ // task1 function
	cputs("task1");
	sleep(5);
	cputs("task1");
	return 0;
}
int task2(){ // task2 fanction
	cputs("task2");
	sleep(1);
	cputs("task2");
	return 0;
}

int main(int argc, char **argv) {
	t_task1=execi(&task1,0,NULL,20,DEFAULT_STACK_SIZE); // make task1
	t_task2=execi(&task2,0,NULL,10,DEFAULT_STACK_SIZE); // make task2
	return 0;
} 
今回はtask1,task2ともにsleep()関数を使っています。このときのタスクの状態を示します。

実行状態のタスクが sleepで実行権を放棄すると、タイムスライスが余っていても直ちにタスクスケジューラが起動されて、実行するタスクの選定が行われます。ここではtask1(優先度20)がsleepで実行権を放棄したところでtask2(優先度10)が起動しています。これにより、LCDは『task1』⇒『sleep』となるわけです。

では、task2がsleepするとどうなるのでしょうか?実はbrickOSkernelは無条件にアイドルタスク(tm_idle_task)というものを生成していて、それに実行権が移ります。アイドルタスクとは優先度0(最低)で、他に実行条件を満たすタスクが存在しない場合に実行権が与えられて、無駄な電力を消費しないようにするタスクです。

プログラムを起動した時点で、execi()で生成したタスク以外に、main 関数の実行にあたって、優先度 10 の main タスクが生成されています。この例では、main はタスク生成を生成するだけでその実行を終えるため、上記の様な場合にアイドルタスクへと処理が移ります。

3.wati_eventの意味

いままで『wait_event()』関数も頻繁に使ってきました。この関数は引数として指定したイベント発生まで、そのタスクを眠らせるという動作をします。このとき、タスクはどうなっているのか見てましょう。 次のプログラムを作成してください。ここでは作成ファイル名をtask3.cとします。
作成したプログラムtask3.c
#include<conio.h>
#include<dsensor.h>
#include<unistd.h> // wait_event function is included

wakeup_t sensor_press_wakeup(wakeup_t data); // wakeup_function
tid_t t_task1,t_task2;

int task1(){ // task1 function
	cputs("task1");
	wait_event(&sensor_press_wakeup,0);
	cputs("ts on");
	return 0;
}
int task2(){ // task2 fanction
	cputs("task2");
	sleep(2);
	cputs("task2");
	return 0;
}

int main(int argc, char **argv) {
	t_task1=execi(&task1,0,NULL,20,DEFAULT_STACK_SIZE); // make task1
	t_task2=execi(&task2,0,NULL,10,DEFAULT_STACK_SIZE); // make task2
	return 0;
}

wakeup_t sensor_press_wakeup(wakeup_t data) { // wakeup_function
	return SENSOR_1<0xf000;
} 
今回はtask1でwait_event()関数を,task2でsleep()関数を使っています。このときのタスクの状態を示します。ここでは十分時間がたった後(task2終了後)タッチセンサーがONになったときを想定しています。

実行状態のタスクがwait_eventで実行権を放棄すると、タイムスライスが余っていても直ちにタスクスケジューラが起動されて、実行するタスクの選定が行われます。ここではtask1(優先度20)が実行権を放棄したところでtask2(優先度10)が起動しています。これにより、LCDは『task1』⇒『sleep』となるわけです。ここまでの動作はsleep関数と同様です。では、wait_event()関数ではどのタイミングで実行権を取り戻すのでしょうか。それは、wait_event()で指定されたウェイクアップ関数(ここでは『sensor_press_wakeup()』)の戻り値が"NoneNull"のときに実行権を取り戻します。ただし、ウェイクアップ関数の評価は常に行われているわけではなく、タイムスライス毎に行われます。よってタイムスライスの間にwait_event()から復帰することはありません。

4.課題

光センサでライントレースを行い、途中で障害物に衝突したら少し下がって停止す るプログラムを作成せよ。タッチセンサと光センサはともに前面に取りつけること。

ライントレースするタスクと、タッチセンサの反応を待ち障害物回避行動をするタスクを生成する。 このとき、どちらの優先度を高くすべきかを考慮すること。また、回避行動で sleep 関数を呼んだ ときに、ライントレースタスクが動いているとそちらが実行されてしまう。それを回避するには、 sleep を呼ぶ前にライトレースタスクを kill(タスクID) しておけばよい。

5.まとめ

 この章ではタスクについて学習しました。マルチタスクに動かすことにより今までできなかった動作をすることが可能になります。この章は難しかったと思いますが、重要な概念ですので、説明を読むなり、自分でプログラムを組んでみるなりしてよく理解しておいてください。また、複数のタスク間の同期についてはセマフォの項で説明します。
8.ボタンの制御
TOP
10.セマフォについて