タスクスイッチの基本部分を制作
【1】はじめに
この文書の目的は2つのタスクをスイッチャが一定時間ごとに切り替える動作を行なうプログラムを制作することである。
2つのタスクとは2つの無限ループの関数である。無限ループなのでそれぞれのタスクはなにもしなければ終わることが無い。
そこを上位のスイッチャが無理やり切り替えることになる。
2つのタスク | |
タスク1 |
タスク2 |
void task0(void) { int cnt=0; while (1) { D_INT(); /*このデモでは通信中は割り込み禁止 にしてリソースの競合を避けている*/ SCI1_printf("task0 %4d\n",cnt); E_INT(); msecwait(20); cnt++; if (cnt==100) cnt=0; } } |
void task1(void) { int cnt=1000; while (1) { D_INT(); /*このデモでは通信中は割り込み禁止 にしてリソースの競合を避けている*/ SCI1_printf("task1 %4d\n",cnt); E_INT(); msecwait(20); cnt++; if (cnt==1100) cnt=1000; } } |
期待される動作は,タスク1が一定時間
task0
1
task0 2
:
を表示したら,タスク2に切り替わって,
task1 1001
task1
1002
:
を表示し,これが交互に永遠に続くといったものである。(2つのタスクのラウンドロビン方式タスク切り替え)
タスクスイッチはCPUの仕組みに依存する部分があり,これをH8CPUに依存した形で作るものである。
なお,開発環境はインラインアセンブリ言語記述の使える日立評価版コンパイラVer.2を用いている。
インラインアセンブリ言語記述とはCプログラム中に無理やりアセンブリプログラムを挿入する方法であるが,
一般的には機種依存性が強いため使われることはない。
タスクの切り替えは,CPUの割り込み処理機構(例外処理)を使って行われるため,最初に割り込み処理機構の仕組み
を確認してからタスク切り替えの説明を行う。
インターバルタイマを用いたタイマー割り込みは,割り込み処理の1例であった。
【2】H8CPUにおける割り込み処理(例外処理)のとスタック
2.1 タイマ割り込み割り込みを1回だけ行うプログラム
次のプログラムはタイマ割り込み割り込みを1回だけ行うものである。起動後,LEDが消灯しているのを確認した後,
EnterKeyを押すと,プログラムは先に進み,タイマ割り込みを可能にして,割り込みルーチンが変数doneを1にして
くれるのをループで待っている。タイマ割り込みは0.5秒周期なので,0.5秒経過するとタイマ割り込み関数が動作し,
LEDを点灯して,変数doneを1にする。処理が割り込みからmainに戻ると,タイマー割り込みが動作しなくなるようにして,
無限ループに入り,プログラムは停止したように見える。
ここまでは以前行なったタイマー割り込みプログラムの書き方の確認である。ただい,この割り込みは1回だけ起こる。
1回だけ起こることを確認するためにLEDを点灯させる。
テストプログラム taskstartDemo1.c
/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"
volatile int done=0; /*最初は0,割り込み処理が終わったら1になる*/
main()
{
initLed(); /*LEDユニットの初期化 点滅させるが,動作確認用*/
initSCI1(); /*シリアル通信の初期化*/
SCI1_printf("\n\n\n *** interrupt function starting demo ***\n\n");
SCI1_printf("Check LEDs. If you see LEDs are off, push [ENTER] key.\n");
getCharSCI1();initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用*/
E_INT(); /*CPU割り込み許可*/
startTimer01(); /*時間割り込みタイマスタートch0,ch1*/while(!done); /*割り込み処理が終わるまでループ*/
stopTimer01(); /*時間割り込みタイマーストップch0,ch1*/
D_INT(); /*CPU割り込み禁止*/SCI1_printf("Done! Check LEDs. If LEDs are lightend, OK!!\n");
while(1); /*無限ループ*/
}/*この作業によってLEDが点灯し割り込み処理に入ったことが確認される*/
void lightenLED()
{
SCI1_printf("Turning on LEDs.\n");
turnOnLed(0);
turnOnLed(1);
}/*ベクトルテーブルの設定*/
#pragma asm
.SECTION MYVEC, DATA, LOCATE=H'000070
.ORG H'000070 ;IMIA1
.DATA.L _TimerIntFunction
.SECTION P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm#pragma interrupt (TimerIntFunction)
void TimerIntFunction()
{
lightenLED();
done=1;
ITU1.TSR.BIT.IMFA=0; /*タイマユニットに割込を受付けたことを知らせる*/
}実行結果
起動後LEDが消えているのを確認し,EnterKeyを押すと,さらに進んで以下の
ようになって止まったように見える(mainの無限ループを実行している)。*** interrupt function starting demo ***
Check LEDs. If you see LEDs are off, push [ENTER] key.
Turning on LEDs.
Done! Check LEDs. If LEDs are lightend, OK!!
テストプログラム taskstartDemo2.c
/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"
volatile int done=0; /*最初は0,割り込み処理が終わったら1になる*/
main()
{
initLed(); /*LEDユニットの初期化 点滅させるが,動作確認用*/
initSCI1(); /*シリアル通信の初期化*/
SCI1_printf("\n\n\n *** interrupt function starting demo ***\n\n");
SCI1_printf("Check LEDs. If you see LEDs are off, push [ENTER] key.\n");
getCharSCI1();initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用*/
E_INT(); /*CPU割り込み許可*/
startTimer01(); /*時間割り込みタイマスタートch0,ch1*/while(!done); /*割り込み処理が終わるまでループ*/
stopTimer01(); /*時間割り込みタイマーストップch0,ch1*/
D_INT(); /*CPU割り込み禁止*/SCI1_printf("Done! Check LEDs. If LEDs are lightend, OK!!\n");
while(1); /*無限ループ*/
}/*この作業によってLEDが点灯し割り込み処理に入ったことが確認される*/
void lightenLED()
{
SCI1_printf("Turning on LEDs.\n");
turnOnLed(0);
turnOnLed(1);
}/*ベクトルテーブルの設定*/
#pragma asm
.SECTION MYVEC, DATA, LOCATE=H'000070
.ORG H'000070 ;IMIA1
.DATA.L _TimerIntFunction
.SECTION P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm#pragma asm
_TimerIntFunction: ; function: _TimerIntFunction()
;スタックポインタの値とスタック領域ををCの変数に保存する
;本来なら非割り込みルーチンはある仕事をしているはずなので,er0などのレジスタは
;使用中であり,割り込みルーチン内でこれらのレジスタを利用する場合は,レジスタの
;内容をスタックに退避させ,割り込みルーチン終了直前に復帰させるのが普通の使い方である。
;本プログラムでは,main関数は何も作業していないので,レジスタ退避を行っていない。; ここに来た証拠にLEDを点灯する関数をcall
jsr @_lightenLED; 割り込み処理があったことをmainに知らせる
mov.w #1, r0
mov.w r0, @_done ;Cの変数doneに1を代入;タイマユニットに割り込みを受け付けたことを知らせる
BCLR.B #0,@H'FFFF71:8 ;ITU1.TSR.BIT.IMFA=0;rte ; ReTurn from Exception
#pragma endasm実行結果
起動後LEDが消えているのを確認し,EnterKeyを押すと,さらに進んで以下の
ようになって止まったように見える。*** interrupt function starting demo ***
Check LEDs. If you see LEDs are off, push [ENTER] key.
Turning on LEDs.
Done! Check LEDs. If LEDs are lightend, OK!!
CPUが割り込みを受け付ける状態(
E_INT(); が実行されている)のとき,割り込みを受け付けると,
すぐに次の3つのことを行う。
(1)現在作業中の次の命令のアドレス3バイトとCCR(コンディションコードレジスタ)
1バイトの4バイトをスタックにPUSHする
これは,割り込み処理が終了し,割り込み前の作業に戻るためである
(2)割り込みベクトルテーブルに設定されたアドレスに配置されているルーチン(割り
込み関数)にジャンプする
(3)CPUを割り込み禁止状態にする(CCRの割り込み禁止ビットを変更する)
この時,スタックポインタ(レジスタer7)とスタックの状態は次のようになっている。
スタックの様子
割り込み関数では,自分の仕事をした後に,
「rte」(ReTurn from
Exception)
で,スタックから戻り番地を読みだして元の場所に戻り,CCRもスタックから読み戻す。
図のようにスタックポインタが割り込み前の位置を指すようになり,スタックは未使用領域に戻る。
ためしに,プログラムでこのことを確かめてみよう。
プログラムの流れは,先の2つのプログラム(taskstartDem1.c,taskstartDem2.c)と同じである。
また,main起動直後のスタックポインタとスタック領域のメモリを表示し,割り込み処理中でもス
タックポインタとスタック領域のメモリを表示している。さらに割り込み処理から抜け出した直後の
スタックポインタとスタック領域のメモリを表示している。
スタックポインタとスタック領域の表示作業では,この作業を直接行わずに,インラインアセンブ
リ言語記述でスタックポインタとスタック領域を保存し,後から保存した値を表示している。(表示
したいところで表示ルーチン呼び出してしまうと,スタックやスタックポインタが変化してしまう。
そのため,一度別の領域に保存してから表示ルーチンで保存内容を表示している。)
インラインアセンブリ言語記述をC関数内で書く場合は,使用したいレジスタがCプログラムで変数と
して使用されている可能性がある場合は,インラインアッセンブリ言語記述の先頭と末尾で使用中の
レジスタをスタックに退避・復帰しなければならない場合がある。
C言語での変数名stackpointer,stackは,アセンブリ言語中では_stackpointer,_stackのように記述
されているが,同じ変数である。
テストプログラム taskstartDemo3.c
/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"
unsigned long int stackpointer; /*スタックポインタの値を保存する場所*/
unsigned char stack[0x30]; /*スタック領域を保存する場所*/volatile int done=0; /*最初は0,割り込み処理が終わったら1になる*/
void dumpStackBuffer(void)
{
unsigned long int address=0xFFEF0;
int i;
SCI1_printf("address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F\n");
for (i=0; i<0x30; i++,address++) {
if (i%0x10==0) SCI1_printf ("%05lx : ",address);
SCI1_printf("%02x ",stack[i]);
if (i%0x10==0xf) SCI1_printf("\n");
}
}
main()
{
initLed(); /*LEDユニットの初期化 点滅させるが,動作確認用*/
initSCI1(); /*シリアル通信の初期化*/
SCI1_printf("\n\n\n *** interrupt function starting demo ***\n\n");
SCI1_printf("Check LEDs. If you see LEDs are off, push [ENTER] key.\n");
getCharSCI1();/*スタックポインタの値とスタック領域をCの変数に保存する アセンブリ言語でないと書けない*/
#pragma asm
mov.l er7, @_stackpointer ;er7(SP)の値をCの変数stackpointerに格納
;stackpointerはCの変数stackpointerのアドレスを意味するので
;「@」をつけて,そのアドレスの指す場所の意味にしている
mov.l #H'FFEF0, er0 ;er0に0xffef0を格納
mov.l #_stack, er1 ;er1にCの変数stackのアドレスを格納
;すなわちCの配列変数stackの先頭アドレス
mov.b #H'30:8, r2h ;r2hに0x30(8bit値)を格納
bra la02
la01:
mov.b @er0+, r2l ;er0の指しているバイト値をr2lに格納し,その後er0の値を1増やす
mov.b r2l, @er1 ;r2lの値を,er1の指しているアドレスに保存
inc.l #1, er1 ;er1の値を1増やす
dec.b r2h ;r2hの値を1減らす
la02:
bne la01 ;直前の演算結果が0でなかったらla01へジャンプ
#pragma endasmSCI1_printf("Stack Area (main satrting)\n");
dumpStackBuffer();
SCI1_printf("StackPointer= %05lx\n\n", stackpointer);initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用*/
E_INT(); /*CPU割り込み許可*/
startTimer01(); /*時間割り込みタイマスタートch0,ch1*/while(!done); /*割り込み処理が終わるまでループ*/
/*スタックポインタの値とスタック領域をCの変数に保存する アセンブリ言語でないと書けない*/
/*上にあったアセンブリプログラムの部分と同じ*/
#pragma asm
mov.l er7, @_stackpointer
mov.l #H'FFEF0, er0
mov.l #_stack, er1
mov.b #H'30:8, r2h
bra lb02
lb01:
mov.b @er0+, r2l
mov.b r2l, @er1
inc.l #1, er1
dec.b r2h
lb02:
bne lb01
#pragma endasmstopTimer01(); /*時間割り込みタイマーストップch0,ch1*/
D_INT(); /*CPU割り込み禁止*/SCI1_printf("Stack Area (main terminating)\n");
dumpStackBuffer();
SCI1_printf("StackPointer= %05lx\n\n", stackpointer);SCI1_printf("Done! Check LEDs. If LEDs are lightend, OK!!\n");
while(1); /*無限ループ*/
} /*end of main*/
/*この作業によってLEDが点灯し割り込み処理に入ったことが確認される*/
void lightenLED()
{
SCI1_printf("Turning on LEDs.\n");
turnOnLed(0);
turnOnLed(1);
}
void printSituation(void)
{
SCI1_printf("Stack Area in interrupt routine\n");
dumpStackBuffer();
SCI1_printf("StackPointer= %05lx\n\n", stackpointer);
}
/*ベクトルテーブルの設定*/
#pragma asm
.SECTION MYVEC, DATA, LOCATE=H'000070
.ORG H'000070 ;IMIA1
.DATA.L _TimerIntFunction
.SECTION P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm#pragma asm
_TimerIntFunction: ; function: _TimerIntFunction()
;スタックポインタの値とスタック領域ををCの変数に保存する
;本来なら非割り込みルーチンはある仕事をしているはずなので,er0などのレジスタは
;使用中であり,割り込みルーチン内でこれらのレジスタを利用する場合は,レジスタの
;内容をスタックに退避させ,割り込みルーチン終了直前に復帰させるのが普通の使い方である。
;本プログラムでは,main関数は何も作業していないので,レジスタ退避を行っていない。;この部分は上にあったアセンブリプログラムの部分と同じ
mov.l er7, @_stackpointer;
mov.l #H'FFEF0, er0
mov.l #_stack, er1
mov.b #H'30:8, r2h
bra lx02
lx01:
mov.b @er0+, r2l
mov.b r2l, @er1
inc.l #1, er1
dec.b r2h
lx02:
bne lx01; 保存したスタックポインタの値とスタック領域を表示する
jsr @_printSituation; ここに来た証拠にLEDを点灯
jsr @_lightenLED; 割り込み処理があったことをmainに知らせる
mov.w #1, r0
mov.w r0, @_done ;Cの変数doneに1を代入;タイマユニットに割り込みを受け付けたことを知らせる
BCLR.B #0,@H'FFFF71:8 ;ITU1.TSR.BIT.IMFA=0;rte ; ReTurn from Exception
#pragma endasm実行結果
起動後LEDが消えているのを確認し,EnterKeyを押すと,さらに進んで以下の
ようになって止まったように見える。
main起動直後(main starting),割り込みルーチン内に入った時(in interrupt routin),
mainに戻ってきた時(main terminating)の3か所での出力が見える。
青字のところは,前の2つのプログラムと同じ内容である。*** interrupt function starting demo ***
Check LEDs. If you see LEDs are off, push [ENTER] key.
Stack Area (main satrting)
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ffef0 : 00 00 00 00 00 00 00 04 00 00 0c b4 00 0f ff 0c
fff00 : 00 00 00 00 00 00 0e ea 00 00 0e f0 00 00 01 56
fff10 : ff ff ff ff ff ff ff ff ff ff ff ff 7f 00 00 00
StackPointer= fff0cStack Area in interrupt routine
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ffef0 : 00 00 00 04 00 00 0c b4 00 0f ff 08 00 00 00 00
fff00 : 00 00 0f 42 00 00 11 f4 44 00 0f 5e 00 00 01 56
fff10 : ff ff ff ff ff ff ff ff ff ff ff ff 7f 00 00 00
StackPointer= fff08Turning on LEDs.
Stack Area (main terminating)
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ffef0 : 00 00 0c b4 00 0f ff 04 00 00 00 00 00 00 00 00
fff00 : 00 00 00 00 00 00 10 50 44 00 0f 5e 00 00 01 56
fff10 : ff ff ff ff ff ff ff ff ff ff ff ff 7f 00 00 00
StackPointer= fff0cDone! Check LEDs. If LEDs are lightend, OK!!
mapファイルの抜粋 _main H'00000EC4 DAT
_printSituation H'00000FF0 DAT
スタートアップルーチンでは
MOV.L
#H'FFF10,ER7 ;set stack
pointer
を実行しているため,最初はstackpointer=fff10であったはずである。
スタートアップルーチンから,このプログラムのmainへサブルーチンコール(jsr命令)でmainに飛んできているので,
この時に,スタートアップルーチンの戻り番地がスタックの先頭(実際には最後尾)に積まれているはずである。
実行結果ではmain中でスタックとスタックポインタを保存した時は,stackpointer=fff0cであった。
スタックは4バイト消費され,00000156が保存されている。これがスタートアップルーチンへの戻り番地である。
割り込みルーチン起動直後ではstackpointer=fff08となり,さらにスタックは4バイト使用されている。
44000f5eのうち44はCCRの値であり,000f5eは,タイマ割り込みが生じた時の戻りアドレスである。
実際mapファイルで確認するとmainは000ec4から始まっており,mainの次の関数printSituationは000ff0から始まって
いるため,mainの無限ループを回っていた時に割り込みがかかり,戻りアドレスとして000f5eが格納されているのは
妥当である。
mainに戻ってきた時には,スタックポインタは割り込み処理以前の状態に戻っている。スタックには消されずに残骸
が残っている。
割り込みルーチン起動直後のスタックの様子は,マニュアルに書かれているとおりであることが検証された。
【3】割り込み時にタスクを切り替えるアイディア
2つのタスクを交替で実行するのは,それぞれのタスクを実行しているCPUの各レジスタの値やスタック領域をメモリ上に退避させたり復元させたりす ることで実現できる。あるタスクを実行中のCPUの各レジスタの値やスタック領域は,コンピュータ内部のタスク実行環境であり,コンテキストと呼ばれてい る。
タイマ割り込みを使って,2つのタスクを交替で実行するには,スタック領域,すべての汎用レジスタ(ER0..ER7,ER7はスタックポインタ),CCR(コンディションコードレジスタ),PC(プログラムカウンタ)を退避させたり復元させたりすればよい。
割り込みの仕組み上,PCおよびCCRはすでにスタックに載っているため,スタック領域と汎用レジスタのみ退避させたり復元させたりすればよい。すなわち,コンテキストはスタック領域と汎用レジスタ領域で構成すればよい。
またスタック領域は,あらかじめタスクごとに使用されるバイト数を予測して,割り当てておけばよい。
そうすると,スタック領域は他の場所に保存する必要がなくなり,スタックポインタでいつでも使えるようになっているので,コンテキストとして退避・復帰させるのは汎用レジスタのみでよいことになる。
タイマ割り込み時に,タスク0からタスク1に切り替えるには,(逆の切り替えも同様に考えればよい)
(1)スタックポインタはタスク0のスタック領域の次に行うべき命令のアドレスが保存されてい
る場所を示した状態になっているので,これを保全した上で,全汎用レジスタをタスク0コ
ンテキスト保存領域に保存。
(2)タスク1コンテキスト保存領域の内容を全汎用レジスタに復帰,この時スタックポインタは
タスク1のスタック領域の次に行うべき命令のアドレス(戻り番地+CCR)が保存されている
場所を示した状態になっている。
(3)割り込み処理から復帰(rte)命令でタスク1の続きの処理に入る
とすればよい。
タスクを最初に起動するときも,割り込みから復帰するふりをしなくてはならないため,各タスクのコンテキストは,割り込みルーチン入ってきた時の状
況を作っておかなければならない。すなわち,タスクの先頭番地を実行しようとした時に,「割り込みがかかってしまって先頭番地とCCRがスタックに積まれ
てしまっている状態」を作っておかなければならない。汎用レジスタはどうでもよいが,スタックポインタは積まれている「戻り番地(タスク関数の先頭番地)
とCCR」の4バイトの先頭(CCRのところ)を指すように作らなければならない。
スタックの状態を次に示す。
上が若いアドレス,スタックポインタが指しているところ以下が有効なデータ
ER0-ER7はそれぞれ4バイトで退避されるコンテキストの保存領域とスタックの状態
この考え方に従って作ったデモプログラムを次に示す。
割り込み処理は関数TaskSwitcher()(アセンブリ言語表現は_TaskSwitcher)である。(レジスタを壊さないように工夫されている)
TaskSwitcher()の作業でタスク0からタスク1に切り替わる時の手順
(逆方向の切り替えも同様になる)タスク切り替えルーチン(割り込み処理ルーチン)に来た時
スタックの状態
「タスク0の戻り番地とCCR」がタスク0のスタックの一番上にある。
↓
どちらのコンテキスト保存領域にしまうべきかを考えて,
スタックポインタ(ER7)をタスク0のER7保存場所に保存し,
汎用レジスタER6..ER0をpush命令でタスク0のレジスタ退避領域へ保存する。
(ここまででスタック0のコンテキストの保存完了)
↓
タスクスイッチャが自分の仕事を行うことができるところ
(しかしここでは何もしない)↓
(ここからはタスク1起動準備)
スタックポインタを一時的に変更して
スタック1よりpop命令により汎用レジスタER0..ER7を復帰
スタックの性質により、popの順番が逆になることに注意
↓
この段階で「タスク1の戻り番地とCCR」がタスク1のスタックの一番上にある。
↓
「rte」命令でタスク1の実行に戻る
ダウンロード |
ここで2つのプログラム例を提示する。
1つは,2つのタスクを切り替えながら作業を行うプログラムである。もう1つは,基本的に動作は同じだが,
コンテキスト領域やスタックを表示しながら,2つのタスクを行うプログラムである。
3.1 2つのタスク切り替えを行うプログラム
タイマ割り込みにより,周期的なタスク切り替えが行われる。タイマ割り込みによりタスクスイッチルーチン
(TaskSwitcher)が起動し,タスク切り替えを行なっている。
タスク切り替え動作によるタスク終了時には,その時実行中のプログラムカウンタが,スタックに入り,スタ
ックポインタがそのアドレスを指すようになっている状態で保存される。
mainでは,2つのタスクのコンテキストの準備を行なった後に最初のタスクを起動する。
int tasksw=0; は現在起動中のタスク番号を保持している。
最初のタスクを起動するときには,タスク(関数)の先頭アドレスをスタックポインタに載せて,割り込み
ルーチン用のリターン命令を行なって,最初のタスクが起動されるが,直接タスク(関数)の先頭アドレス
をスタックポインタに載せるわけではなく,コンテキストのスタックポインタ領域に載せておく。
タスク切り替え動作によるタスク終了時には,その時実行中のプログラムカウンタが,スタックに入り,スタ
ックポインタがそのアドレスを指すようになっている状態で保存されるので,その逆のことをすることによっ
て,タスクの最初の起動を行っていることになる。
この作業は,mainの最後のところで行われ,mainには二度と戻っては来ない。
その後一定時間経つとタイマ割り込みが起こり,タスクスイッチルーチン(TaskSwitcher)が起動し,タスク
切り替えを行なっている。taskswで,直前まで動作していたタスク番号を読み取り,その番号のコンテキスト
バッファにコンテキストを保存する。続いて次の起動すべきタスクのコンテキストをコンテキストバッファか
ら取り出して,SP領域に起動スべきアドレスをセットして,割り込み処理をrteで終了すると,次のタスクが
始まる。
タスク切り替えデモプログラム taskSwitcherDemo1.c
/**********************************************************
インターバルタイマ割り込みによって2つのタスクの切り替えを行う
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"
/*************** 2つのタスクの実体 ここから ********************/
void msecwait(int msec)
/*mesc間なにもしない時間稼ぎ関数*/
{
int i,j;
for (i=0;i<msec;i++) {
for (j=0;j<1588;j++); /*1588は実測によって求めた値*/
}
}
void task0(void)
{
int cnt=0;
while (1) {
D_INT(); /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
SCI1_printf("task0 %4d\n",cnt);
E_INT();
msecwait(20);
cnt++;
if (cnt==100) cnt=0;
}
}
void task1(void)
{
int cnt=1000;
while (1) {
D_INT(); /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
SCI1_printf("task1 %4d\n",cnt);
E_INT();
msecwait(20);
cnt++;
if (cnt==1100) cnt=1000;
}
}
/*************** 2つのタスクの実体 ここまで ********************/
typedef union {
unsigned char Bccr;
unsigned long int returnAddress;
} stack_t;
typedef struct {
unsigned char stack[128]; /*stackとstack0の合わさったものが実際のスタック*/
stack_t stack0; /*初期状態はここに戻りアドレスとCCRが入っている*/
unsigned long int Ber0; /*ここより下のBer0~Ber7は各レジスタの保存領域*/
unsigned long int Ber1;
unsigned long int Ber2;
unsigned long int Ber3;
unsigned long int Ber4;
unsigned long int Ber5;
unsigned long int Ber6;
unsigned long int Ber7; /*初期状態ではstack0をさしている*/
} context_t;
context_t context0,context1; /*コンテキストバッファの実体*/
int tasksw=0; /* 0 または 1 :どちらのタスクが実行されているかを示している*/
unsigned long int Ber0; /*ER0の退避場所 タスクスイッチャ起動直後にちょっと使う*/
const unsigned long int buff0Address=(unsigned long int)&context0.Ber0;
const unsigned long int buff1Address=(unsigned long int)&context1.Ber0;
const unsigned long int stackPtr0Address=(unsigned long int)&context0.Ber7;
const unsigned long int stackPtr1Address=(unsigned long int)&context1.Ber7;
unsigned long int systemstackPtr; /*スイッチャ用スタックポインタ退避場所*/
main()
{
initLed(); /*LEDユニットの初期化 点滅させるが,動作確認用*/
initSCI1(); /*シリアル通信の初期化*/
SCI1_printf("\n\n\n *** task switcher demo ***\n\n");
initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用 このタイミングでタスク切り替えが起こる*/
E_INT(); /*CPU割り込み許可*/
/*コンテキストバッファにコンテキストを2つの作成*/
context0.stack0.returnAddress=(unsigned long int)task0;
context0.Ber7=(unsigned long int)&(context0.stack0);
context1.stack0.returnAddress=(unsigned long int)task1;
context1.Ber7=(unsigned long int)&(context1.stack0);
startTimer01(); /*時間割り込みタイマスタートch0,ch1*/
/*ここまでで2つのタスクの起動準備完了。最初はtask0を起動する*/
tasksw=0;
#pragma asm
mov.l er7, @_systemstackPtr ;タスクスイッチャのスタックポインタを退避
mov.l @_stackPtr0Address, er0 ;タスク0のスタックポインタを設定(復帰と同じ手順)
mov.l @er0, er7 ; (er0経由でer7(=SP)に設定)
rte ;ReTurn from Exception によりタスク0起動
#pragma endasm
} /*end of main*/
/*ベクトルテーブルの設定*/
#pragma asm
.SECTION MYVEC, DATA, LOCATE=H'000070
.ORG H'000070 ;IMIA1
.DATA.L _TaskSwitcher
.SECTION P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm
/*
インターバルタイマ割り込みで起動するルーチン/タスクスイッチャ本体
タスク切り替えの作業を行う
TaskSwitcherでは次のことを行う
(1)現在のタスクがtask0,task1のどちらであったか検出
(2)現在の全レジスタをcontext0,またはcontext1として保存
(3)次のタスクにむけてcontext0,またはcontext1を全レジスタに載せる
(4)rteで新タスクを起動(割り込みから戻るふりをする)
ただし,タスクスイッチャが起動した時点では,すべてのレジスタは,
スイッチャ起動前に動作していたタスクが使用中である。
そのため,スイッチャが起動直後において,全レジスタを保存する前にいくつかの
レジスタを使用する場合は,そのレジスタを退避し,作業が終了した時点で復帰させ
全レジスタをコンテキスト保存場所に保存する手順となる。以下の★参照
*/
#pragma asm
_TaskSwitcher: ; function: TaskSwitcher
;タイマユニットに割り込みを受け付けたことを知らせる
BCLR.B #0,@H'FFFF71:8 ;ITU1.TSR.BIT.IMFA=0;
; ** 旧コンテキスト保存 **
mov.l er0, @_Ber0 ;直前タスク番号確認のためER0を使いたいのでER0を退避★↓
mov.w @_tasksw, r0
cmp.w #1, r0 ;r0が1であるかどうか調べる
beq RETUNEDFROMTASK1 ;直前がタスク1,次がタスク0ならジャンプ
RETUNEDFROMTASK0:
mov.w #1, r0 ;次のタスクはタスク1
mov.w r0, @_tasksw ;この段階で変数taskswの中身は次のコンテキスト番号
;直前はタスク0だったのでコンテキスト0に保存する準備
mov.l @_stackPtr0Address, er0
bra SAVECONTEXT
RETUNEDFROMTASK1:
mov.w #0, r0 ;次のタスクはタスク0
mov.w r0, @_tasksw ;この段階で変数taskswの中身は次のコンテキスト番号
;直前はタスク1だったのでコンテキスト1に保存する準備
mov.l @_stackPtr1Address, er0
SAVECONTEXT:
;コンテキストの保存
mov.l er7, @er0 ;er0の指しているアドレスにer7(=SP)を保存する
mov.l er0, er7 ;er0の値をer7(=SP)に設定する
mov.l @_Ber0, er0 ;ER0を使い終えたので復帰★↑
push.l er6
push.l er5
push.l er4
push.l er3
push.l er2
push.l er1
push.l er0
;この段階で,すべてのレジスタをスイッチャが自由に使えるようになった
; ** 次に実行すべき新コンテキスト設定 **
mov.w @_tasksw, r0 ;変数taskswの中身は次のタスク番号である
cmp.w #1, r0
beq SETREADYTASK1 ;新タスクが1ならジャンプ
SETREADYTASK0:
;コンテキスト0用のスタックを指すようにスタックポインタを設定
mov.l @_buff0Address, er7
bra SETCONTEXTANDGO
SETREADYTASK1:
;コンテキスト1用のスタックを指すようにスタックポインタを設定
mov.l @_buff1Address, er7
SETCONTEXTANDGO:
;コンテキストを復旧させて,次のタスク起動
pop.l er0
pop.l er1
pop.l er2
pop.l er3
pop.l er4
pop.l er5
pop.l er6
pop.l er7
rte ;ReTurn from Exception により新タスク起動
#pragma endasm実行結果 *** task switcher demo ***
task0 0
task0 1
task0 2
task0 3
task0 4
task0 5
task0 6
task0 7
task0 8
task0 9
task0 10
task0 11
task0 12
task0 13
task0 14
task0 15
task0 16
task0 17
task0 18
task0 19
task0 20
task0 21
task0 22
task0 23
task0 24
task0 25
task0 26
task0 27
task0 28
task0 29
task0 30
task0 31
task0 32
task0 33
task0 34
task0 35
task0 36
task0 37
task0 38
task0 39
task0 40
task0 41
task0 42
task0 43
task0 44
task0 45
task0 46
task0 47
task0 48
task1 1000
task1 1001
task1 1002
task1 1003
task1 1004
task1 1005
task1 1006
task1 1007
task1 1008
task1 1009
task1 1010
task1 1011
task1 1012
task1 1013
task1 1014
task1 1015
task1 1016
task1 1017
task1 1018
task1 1019
task1 1020
task1 1021
task1 1022
task1 1023
task1 1024
task1 1025
task1 1026
task1 1027
task1 1028
task1 1029
task1 1030
task1 1031
task1 1032
task1 1033
task1 1034
task1 1035
task1 1036
task1 1037
task1 1038
task1 1039
task1 1040
task1 1041
task1 1042
task1 1043
task1 1044
task1 1045
task1 1046
task1 1047
task1 1048
task0 49
task0 50
task0 51
task0 52
task0 53
task0 54
task0 55
task0 56
task0 57
task0 58
task0 59
task0 60
task0 61
task0 62
task0 63
task0 64
task0 65
task0 66
task0 67
task0 68
task0 69
task0 70
task0 71
task0 72
task0 73
task0 74
task0 75
task0 76
task0 77
task0 78
task0 79
task0 80
task0 81
task0 82
task0 83
task0 84
task0 85
task0 86
task0 87
task0 88
task0 89
task0 90
task0 91
task0 92
task0 93
task0 94
task0 95
task0 96
task0 97
task1 1049
task1 1050
task1 1051
task1 1052
task1 1053
task1 1054
task1 1055
task1 1056
task1 1057
task1 1058
task1 1059
task1 1060
task1 1061
task1 1062
task1 1063
task1 1064
task1 1065
task1 1066
task1 1067
task1 1068
task1 1069
task1 1070
task1 1071
task1 1072
task1 1073
task1 1074
task1 1075
task1 1076
task1 1077
task1 1078
task1 1079
task1 1080
task1 1081
task1 1082
task1 1083
task1 1084
task1 1085
task1 1086
task1 1087
task1 1088
task1 1089
task1 1090
task1 1091
task1 1092
task1 1093
task1 1094
task1 1095
task1 1096
task1 1097
task0 98
task0 99
task0 0
task0 1
task0 2
task0 3
task0 4
task0 5
task0 6
task0 7
task0 8
task0 9
3.2 状況記述を加えたタスク切り替えプログラム
次のプログラムは,「3.1」のプログラムにおいて,動作を観察するためにタスク切り替え時の,スタックやスタックポ
インタなどを表示しながら作業するものである。
実行結果ではタスク切り替えが成功している様子がわかる。切り替え時に2つのコンテキスト保存状態の表示を行なっている。
また,タスク切り替え処理に入ったことを確認するために,LEDを点滅させている。
Cのプログラム中で 関数printContext(); を呼び出すのは差し支えないが,アセンブリプログラム中から jsr @_printContext
で 関数printContext(); を呼び出す場合は,ER0などのレジスタの内容が変更されても差し支えない状態のときに呼び出す必
要がある。あるいは必要なレジスタは退避させて呼び出す必要がある。本プログラムではレジスタの内容が変更されても差し
支えない状態のときに呼び出している。
タスク切り替えデモプログラム taskSwitcherDemo2.c
/**********************************************************
インターバルタイマ割り込みによって2つのタスクの切り替えを行う
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"
/*************** 2つのタスクの実体 ここから ********************/
void msecwait(int msec)
/*mesc間なにもしない時間稼ぎ関数*/
{
int i,j;
for (i=0;i<msec;i++) {
for (j=0;j<1588;j++); /*1588は実測によって求めた値*/
}
}
void task0(void)
{
int cnt=0;
while (1) {
D_INT(); /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
SCI1_printf("task0 %4d\n",cnt);
E_INT();
msecwait(20);
cnt++;
if (cnt==100) cnt=0;
}
}
void task1(void)
{
int cnt=1000;
while (1) {
D_INT(); /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
SCI1_printf("task1 %4d\n",cnt);
E_INT();
msecwait(20);
cnt++;
if (cnt==1100) cnt=1000;
}
}
/*************** 2つのタスクの実体 ここまで ********************/
typedef union {
unsigned char Bccr;
unsigned long int returnAddress;
} stack_t;
typedef struct {
unsigned char stack[128]; /*stackとstack0の合わさったものが実際のスタック*/
stack_t stack0; /*初期状態はここに戻りアドレスとCCRが入っている*/
unsigned long int Ber0; /*ここより下のBer0~Ber7は各レジスタの保存領域*/
unsigned long int Ber1;
unsigned long int Ber2;
unsigned long int Ber3;
unsigned long int Ber4;
unsigned long int Ber5;
unsigned long int Ber6;
unsigned long int Ber7; /*初期状態ではstack0をさしている*/
} context_t;
context_t context0,context1; /*コンテキストバッファの実体*/
int tasksw=0; /* 0 または 1 :どちらのタスクが実行されているかを示している*/
unsigned long int Ber0; /*ER0の退避場所 タスクスイッチャ起動直後にちょっと使う*/
const unsigned long int buff0Address=(unsigned long int)&context0.Ber0;
const unsigned long int buff1Address=(unsigned long int)&context1.Ber0;
const unsigned long int stackPtr0Address=(unsigned long int)&context0.Ber7;
const unsigned long int stackPtr1Address=(unsigned long int)&context1.Ber7;
unsigned long int systemstackPtr; /*スイッチャ用スタックポインタ退避場所*/
/*コンテキストバッファの中のレジスタの表示 確認用*/
void printContext1(context_t *con)
{
SCI1_printf("ER0 %08lx ",con->Ber0);
SCI1_printf("ER4 %08lx\n",con->Ber4);
SCI1_printf("ER1 %08lx ",con->Ber1);
SCI1_printf("ER5 %08lx\n",con->Ber5);
SCI1_printf("ER2 %08lx ",con->Ber2);
SCI1_printf("ER6 %08lx\n",con->Ber6);
SCI1_printf("ER3 %08lx ",con->Ber3);
SCI1_printf("ER7 %08lx\n",con->Ber7);
}
/*スタックのおわり周辺の表示*/
void printStackArea(context_t *con)
{
unsigned long int address=(unsigned long int)(&(con->Ber0));
int i;
address-=0x20;
address&=0xfffffff0;
SCI1_printf("tail of stack + 1 =%05lx\n",&(con->Ber0));
SCI1_printf("address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F\n");
for (i=0; i<0x30; i++,address++) {
if (i%0x10==0) SCI1_printf ("%05lx : ",address);
SCI1_printf("%02x ",*(unsigned char*)address);
if (i%0x10==0xf) SCI1_printf("\n");
}
}
/*コンテキストバッファのレジスタ表示関数 確認用*/
void printContext(void)
{
SCI1_printf(" context for task0\n");
printContext1(&context0);
printStackArea(&context0);
SCI1_printf(" context for task1\n");
printContext1(&context1);
printStackArea(&context1);
}
main()
{
initLed(); /*LEDユニットの初期化 点滅させるが,動作確認用*/
initSCI1(); /*シリアル通信の初期化*/
SCI1_printf("\n\n\n *** task switcher demo ***\n\n");
initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用 このタイミングでタスク切り替えが起こる*/
E_INT(); /*CPU割り込み許可*/
/*コンテキストバッファにコンテキストを2つの作成*/
context0.stack0.returnAddress=(unsigned long int)task0;
context0.Ber7=(unsigned long int)&(context0.stack0);
SCI1_printf("task0,stack,er7=%p %08lx %08lx\n", task0, context0.stack0.returnAddress, context0.Ber7);
context1.stack0.returnAddress=(unsigned long int)task1;
context1.Ber7=(unsigned long int)&(context1.stack0);
SCI1_printf("task1,stack,er7=%p %08lx %08lx\n", task1, context1.stack0.returnAddress, context1.Ber7);
printContext();
startTimer01(); /*時間割り込みタイマスタートch0,ch1*/
/*ここまでで2つのタスクの起動準備完了。最初はtask0を起動する*/
tasksw=0;
#pragma asm
mov.l er7, @_systemstackPtr ;自分のスタックポインタを退避
mov.l @_stackPtr0Address, er0 ;タスク0のスタックポインタを設定(復帰と同じ手順)
mov.l @er0, er7 ; (er0経由でer7(=SP)に設定)
rte ;ReTurn from Exception によりタスク0起動
#pragma endasm
} /*end of main*/
/*この作業はおまけ,インターバルタイマの動作が監視できる*/
void blinkLED()
{
static int tick=0;
if (tick==1) {
turnOnLed(0);
turnOffLed(1);
} else {
turnOffLed(0);
turnOnLed(1);
}
tick=1-tick;
}
/*ベクトルテーブルの設定*/
#pragma asm
.SECTION MYVEC, DATA, LOCATE=H'000070
.ORG H'000070 ;IMIA1
.DATA.L _TaskSwitcher
.SECTION P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm
/*
インターバルタイマ割り込みで起動するルーチン/タスクスイッチャ本体
タスク切り替えの作業を行う
TaskSwitcherでは次のことを行う
(1)現在のタスクがtask0,task1のどちらであったか検出
(2)現在の全レジスタをcontext0,またはcontext1として保存
(3)次のタスクにむけてcontext0,またはcontext1を全レジスタに載せる
(4)rteで新タスクを起動(割り込みから戻るふりをする)
ただし,タスクスイッチャが起動した時点では,すべてのレジスタは,
スイッチャ起動前に動作していたタスクが使用中である。
そのため,スイッチャが起動直後において,全レジスタを保存する前にいくつかの
レジスタを使用する場合は,そのレジスタを退避し,作業が終了した時点で復帰させ
全レジスタをコンテキスト保存場所に保存する手順となる。以下の★参照
*/
#pragma asm
_TaskSwitcher: ; function: TaskSwitcher
;タイマユニットに割り込みを受け付けたことを知らせる
BCLR.B #0,@H'FFFF71:8 ;ITU1.TSR.BIT.IMFA=0;
; ** 旧コンテキスト保存 **
mov.l er0, @_Ber0 ;直前タスク番号確認のためER0を使いたいのでER0を退避★↓
mov.w @_tasksw, r0
cmp.w #1, r0 ;r0が1であるかどうか調べる
beq RETUNEDFROMTASK1 ;直前がタスク1,次がタスク0ならジャンプ
RETUNEDFROMTASK0:
mov.w #1, r0 ;次のタスクはタスク1
mov.w r0, @_tasksw ;この段階で変数taskswの中身は次のコンテキスト番号
;直前はタスク0だったのでコンテキスト0に保存する準備
mov.l @_stackPtr0Address, er0
bra SAVECONTEXT
RETUNEDFROMTASK1:
mov.w #0, r0 ;次のタスクはタスク0
mov.w r0, @_tasksw ;この段階で変数taskswの中身は次のコンテキスト番号
;直前はタスク1だったのでコンテキスト1に保存する準備
mov.l @_stackPtr1Address, er0
SAVECONTEXT:
;コンテキストの保存
mov.l er7, @er0 ;er0の指しているアドレスにer7(=SP)を保存する
mov.l er0, er7 ;er0の値をer7(=SP)に設定する
mov.l @_Ber0, er0 ;ER0を使い終えたので復帰★↑
push.l er6
push.l er5
push.l er4
push.l er3
push.l er2
push.l er1
push.l er0
;この段階で,すべてのレジスタをスイッチャが自由に使えるようになった
mov.l @_systemstackPtr, er7 ;自分(スイッチャ)用のスタックの復帰
;この段階で,スイッチャ用のスタックが使えるようになった
jsr @_printContext ;contextのスナップショット(確認用)
jsr @_blinkLED ;LED点滅 (動作点検用)
mov.l er7, @_systemstackPtr ;自分(スイッチャ)用のスタックの保存
; ** 次に実行すべき新コンテキスト設定 **
mov.w @_tasksw, r0 ;変数taskswの中身は次のタスク番号である
cmp.w #1, r0
beq SETREADYTASK1 ;新タスクが1ならジャンプ
SETREADYTASK0:
;コンテキスト0用のスタックを指すようにスタックポインタを設定
mov.l @_buff0Address, er7
bra SETCONTEXTANDGO
SETREADYTASK1:
;コンテキスト1用のスタックを指すようにスタックポインタを設定
mov.l @_buff1Address, er7
SETCONTEXTANDGO:
;コンテキストを復旧させて,次のタスク起動
pop.l er0
pop.l er1
pop.l er2
pop.l er3
pop.l er4
pop.l er5
pop.l er6
pop.l er7
rte ;ReTurn from Exception により新タスク起動
#pragma endasm
実行結果
(ER7がスタックポインタ)*** task switcher demo ***
task0,stack,er7=00000E4E 00000e4e 000fefa4
task1,stack,er7=00000E7E 00000e7e 000ff048
context for task0
ER0 00000000 ER4 00000000
ER1 00000000 ER5 00000000
ER2 00000000 ER6 00000000
ER3 00000000 ER7 000fefa4
tail of stack + 1 =fefa8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fef90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fefa0 : 00 00 00 00 00 00 0e 4e 00 00 00 00 00 00 00 00
context for task1
ER0 00000000 ER4 00000000
ER1 00000000 ER5 00000000
ER2 00000000 ER6 00000000
ER3 00000000 ER7 000ff048
tail of stack + 1 =ff04c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff040 : 00 00 00 00 00 00 00 00 00 00 0e 7e 00 00 00 00
task0 0
task0 1
task0 2
task0 3
task0 4
task0 5
task0 6
task0 7
task0 8
task0 9
task0 10
task0 11
task0 12
task0 13
task0 14
task0 15
task0 16
task0 17
task0 18
task0 19
task0 20
task0 21
task0 22
task0 23
task0 24
task0 25
task0 26
task0 27
task0 28
task0 29
task0 30
task0 31
task0 32
task0 33
task0 34
task0 35
task0 36
task0 37
task0 38
task0 39
task0 40
task0 41
task0 42
task0 43
task0 44
task0 45
task0 46
task0 47
task0 48
context for task0
ER0 03be0014 ER4 00000000
ER1 00000011 ER5 00000000
ER2 00000000 ER6 000f0030
ER3 00000000 ER7 000fefa0
tail of stack + 1 =fefa8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 00 00 00 00 00 00 00 00 00 00 0f 00 48 00 00
fef90 : 00 04 00 00 0c b4 00 0f ef a6 00 0f 00 30 00 00
fefa0 : 21 00 0e 42 00 00 0e 72 03 be 00 14 00 00 00 11
context for task1
ER0 00000000 ER4 00000000
ER1 00000000 ER5 00000000
ER2 00000000 ER6 00000000
ER3 00000000 ER7 000ff048
tail of stack + 1 =ff04c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff040 : 00 00 00 00 00 00 00 00 48 00 0e 7e 00 00 00 00
task1 1000
task1 1001
task1 1002
task1 1003
task1 1004
task1 1005
task1 1006
task1 1007
task1 1008
task1 1009
task1 1010
task1 1011
task1 1012
task1 1013
task1 1014
task1 1015
task1 1016
task1 1017
task1 1018
task1 1019
task1 1020
task1 1021
task1 1022
task1 1023
task1 1024
task1 1025
task1 1026
task1 1027
task1 1028
context for task0
ER0 03be0014 ER4 00000000
ER1 00000011 ER5 00000000
ER2 00000000 ER6 000f0030
ER3 00000000 ER7 000fefa0
tail of stack + 1 =fefa8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 00 00 00 00 00 00 00 00 00 00 0f 00 48 00 00
fef90 : 00 04 00 00 0c b4 00 0f ef a6 00 0f 00 30 00 00
fefa0 : 21 00 0e 42 00 00 0e 72 03 be 00 14 00 00 00 11
context for task1
ER0 00390014 ER4 00000000
ER1 00000013 ER5 00000000
ER2 00000000 ER6 00000404
ER3 00000000 ER7 000ff044
tail of stack + 1 =ff04c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : 06 be 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff030 : 00 00 00 00 00 04 00 00 0c b4 00 0f f0 4a 00 00
ff040 : 04 04 00 00 21 00 0e 44 00 00 0e a4 00 39 00 14
task0 49
task0 50
task0 51
task0 52
task0 53
task0 54
task0 55
task0 56
task0 57
task0 58
task0 59
task0 60
task0 61
task0 62
task0 63
task0 64
task0 65
task0 66
task0 67
task0 68
task0 69
task0 70
task0 71
task0 72
task0 73
task0 74
task0 75
task0 76
task0 77
context for task0
ER0 02a80014 ER4 00000000
ER1 00000012 ER5 00000000
ER2 00000000 ER6 000f004d
ER3 00000000 ER7 000fefa0
tail of stack + 1 =fefa8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 00 00 00 00 00 00 00 00 00 00 0f 00 48 00 00
fef90 : 00 04 00 00 0c b4 00 0f ef a6 00 0f 00 4d 00 00
fefa0 : 21 00 0e 42 00 00 0e 72 02 a9 00 14 00 00 00 12
context for task1
ER0 00390014 ER4 00000000
ER1 00000013 ER5 00000000
ER2 00000000 ER6 00000404
ER3 00000000 ER7 000ff044
tail of stack + 1 =ff04c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : 06 be 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff030 : 00 00 00 00 00 04 00 00 0c b4 00 0f f0 4a 00 00
ff040 : 04 04 00 00 21 00 0e 42 00 00 0e a4 00 39 00 14
task1 1029
task1 1030
task1 1031
task1 1032
task1 1033
task1 1034
task1 1035
task1 1036
task1 1037
task1 1038
task1 1039
task1 1040
task1 1041
stackにはtask0の先頭アドレスe4eが見える
stackにはtask1の先頭アドレスe7eが見える
スタックの末尾アドレスはfefa7
タスク起動前のスタックの様子
戻り番地として開始アドレス000e4e(task0)が設定
スタックの末尾アドレスはff04b
タスク起動前のスタックの様子
戻り番地として開始アドレス000e7e(task1)が設定
タスク0が起動
タスク0が戻ってきた
ER0,ER1,ER5,ER6が使われている
スタックはfefa0からfefa7まで
使われている
21がCCR,000e42が戻り番地
スタックは後ろから前に向けて使用される
こちらはまだ使われていない
タスク1が起動
タスク1が戻ってきた
タスク0のコンテキストは変化していない
ER0,ER1,ER6が使われている
スタックはff044からff04bまで
使われている
21がCCR,000e44が戻り番地
タスク0が起動
タスク0が戻ってきた
ER0,ER1,ER6が使われている
スタックはfefa0からfefa7まで
使われている
【4】割り込み時にタスクを切り替えるアイディア その2
【3】のプログラムは,コンテキストの存在そのものに焦点を当てているため,レジスタ保存領域が固定されており,タスク切り替えをイメージするにはよいが,切り替え操作がスマートではない。
そこで,コンテキスト保存領域を次のように使うと切り替え操作が楽になる。(プログラム記述が楽になる)
レジスタ退避領域を別に設けるのではなく,タスクが使用中のスタック上に退避させ,スタックポインタのみ特別に退避させる。
この方法は,レジスタ保存領域が固定されていないため,とらえにくいが,コンテキスト切り替えはスマートになる。
タスク切り替え処理に来た時とタスク復帰時のスタックの状態は先の例と同じだが,コンテキストが保存されている段階では,レジスタER0..ER6もスタック上に載せるため,スタックポインタはさらに先を(若いアドレス側を)指すようになっている。
上が若いアドレス,スタックポインタが指しているところ以下が有効なデータ
ER0..ER7はそれぞれ4バイトで退避されるコンテキストの保存領域とスタックの状態
この考え方に従って作ったデモプログラムを次に示す。
割り込み処理は関数TaskSwitcher()(アセンブリ言語表現は_TaskSwitcher)である。
TaskSwitcher()の作業でタスク0からタスク1に切り替わる時の手順
(逆方向の切り替えも同様になる)タスク切り替えルーチン(割り込み処理ルーチン)に来た時
汎用レジスタER0..ER6をpush命令でタスク0領域のスタックの一番上にのせる。
↓直前のタスク番号を考慮して
スタックポインタ(ER7)をコンテキスト0のER7保存場所に保存
(ここまででスタック0のコンテキストの保存完了)
↓
タスクスイッチャが自分の仕事を行うことができるところなので
スイッチャの状況を表示することができる。
↓
コンテキスト1のER7保存場所からスタックポインタ(ER7)に復帰
↓
スタック1よりpop命令により汎用レジスタER6..ER0を復帰
スタックの性質により、popの順番が逆になることに注意
↓
この段階で「タスク1の戻り番地とCCR」がタスク1のスタックの一番上にある。
↓
「rte」命令でタスク1の実行に戻る
ダウンロード |
ここで1つのプログラム例だけを提示するが,ダウンロードファイルには2つプログラムが入っている。
1つは,2つのタスクを切り替えながら作業を行うプログラムである。もう1つは,基本的に動作は同じだが,
コンテキスト領域やスタックを表示しながら,2つのタスクを行うプログラムである。
次のプログラムは,コンテキスト領域やスタックを表示しながら,2つのタスクを行うプログラムである。
実行結果ではタスク切り替えが成功している様子がわかる。切り替え時に2つのコンテキスト保存状態の表示を行なっている。
また,タスク切り替え処理に入ったことを確認するために,LEDを点滅させている。
タスク切り替えデモプログラム taskSwitcherDemoB2.c
/**********************************************************
インターバルタイマ割り込みによって2つのタスクの切り替えを行う
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"
/*************** 2つのタスクの実体 ここから ********************/
void msecwait(int msec)
/*mesc間なにもしない時間稼ぎ関数*/
{
int i,j;
for (i=0;i<msec;i++) {
for (j=0;j<1588;j++); /*1588は実測によって求めた値*/
}
}
void task0(void)
{
int cnt=0;
while (1) {
D_INT(); /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
SCI1_printf("task0 %4d\n",cnt);
E_INT();
msecwait(20);
cnt++;
if (cnt==100) cnt=0;
}
}
void task1(void)
{
int cnt=1000;
while (1) {
D_INT(); /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
SCI1_printf("task1 %4d\n",cnt);
E_INT();
msecwait(20);
cnt++;
if (cnt==1100) cnt=1000;
}
}
/*************** 2つのタスクの実体 ここまで ********************/
typedef union {
unsigned char Bccr;
unsigned long int returnAddress;
} stack_t;
typedef struct {
unsigned long int Ber7; /*初期状態ではBer0をさしている*/
unsigned char stack[128]; /*stack~stack0の合わさったものが実際のスタック*/
unsigned long int Ber0; /*初期状態だけER0があると考える,後はスタックの一部*/
unsigned long int Ber1; /*初期状態だけER1があると考える,後はスタックの一部*/
unsigned long int Ber2; /*初期状態だけER2があると考える,後はスタックの一部*/
unsigned long int Ber3; /*初期状態だけER3があると考える,後はスタックの一部*/
unsigned long int Ber4; /*初期状態だけER4があると考える,後はスタックの一部*/
unsigned long int Ber5; /*初期状態だけER5があると考える,後はスタックの一部*/
unsigned long int Ber6; /*初期状態だけER6があると考える,後はスタックの一部*/
stack_t stack0; /*初期状態はここに戻りアドレスとCCRが入っている*/
} context_t;
context_t context0,context1; /*コンテキストバッファの実体*/
int tasksw=0; /* 0 または 1 稼働中のタスク番号*/
unsigned long int systemstackPtr; /*スイッチャ用スタックポインタ退避場所*/
/*コンテキストバッファの中のレジスタの表示 確認用*/
void printContext1(context_t *con)
{
unsigned long int *currentPtr=(unsigned long int *)(con->Ber7);
SCI1_printf("ER0 %08lx ",currentPtr[0]);
SCI1_printf("ER4 %08lx\n", currentPtr[4]);
SCI1_printf("ER1 %08lx ",currentPtr[1]);
SCI1_printf("ER5 %08lx\n", currentPtr[5]);
SCI1_printf("ER2 %08lx ",currentPtr[2]);
SCI1_printf("ER6 %08lx\n", currentPtr[6]);
SCI1_printf("ER3 %08lx ",currentPtr[3]);
SCI1_printf("ER7 %08lx\n",con->Ber7);
}
/*スタックのおわり周辺の表示*/
void printStackArea(context_t *con)
{
unsigned long int address=(unsigned long int)(&(con->stack0))+4;
int i;
SCI1_printf("tail of stack + 1 =%05lx\n",address);
address-=0x30;
address&=0xfffffff0;
SCI1_printf("address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F\n");
for (i=0; i<0x40; i++,address++) {
if (i%0x10==0) SCI1_printf ("%05lx : ",address);
SCI1_printf("%02x ",*(unsigned char*)address);
if (i%0x10==0xf) SCI1_printf("\n");
}
}
/*コンテキストバッファのレジスタ表示関数 確認用*/
void printContext(void)
{
SCI1_printf(" context for task0\n");
printContext1(&context0);
printStackArea(&context0);
SCI1_printf(" context for task1\n");
printContext1(&context1);
printStackArea(&context1);
}
main()
{
initLed(); /*LEDユニットの初期化 点滅させるが,動作確認用*/
initSCI1(); /*シリアル通信の初期化*/
SCI1_printf("\n\n\n *** task switcher demo ***\n\n");
initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用 このタイミングでタスク切り替えが起こる*/
E_INT(); /*CPU割り込み許可*/
/*コンテキストバッファにコンテキストを2つの作成*/
context0.stack0.returnAddress=(unsigned long int)task0;
context0.Ber7=(unsigned long int)&context0.Ber0;
SCI1_printf("task0,stack,er7=%p %08lx %08lx\n", task0, context0.stack0.returnAddress, context0.Ber7);
context1.stack0.returnAddress=(unsigned long int)task1;
context1.Ber7=(unsigned long int)&context1.Ber0;
SCI1_printf("task1,stack,er7=%p %08lx %08lx\n", task1, context1.stack0.returnAddress, context1.Ber7);
printContext();
startTimer01(); /*時間割り込みタイマスタートch0,ch1*/
/*ここまでで2つのタスクの起動準備完了。最初はtask0を起動する*/
tasksw=0;
#pragma asm
mov.l er7, @_systemstackPtr ;スイッチャのスタックポインタを退避
mov.l @_context0, er7 ;タスク0のスタックポインタを復帰
; context0とcontext0.Ber7は同じアドレス
pop.l er0
pop.l er1
pop.l er2
pop.l er3
pop.l er4
pop.l er5
pop.l er6
rte ;ReTurn from Exception によりタスク起動
#pragma endasm
} /*end of main*/
/*ベクトルテーブルの設定*/
#pragma asm
.SECTION MYVEC, DATA, LOCATE=H'000070
.ORG H'000070 ;IMIA1
.DATA.L _TaskSwitcher
.SECTION P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm
/*
インターバルタイマ割り込みで起動するルーチン,タスク切り替えの作業を行う
TaskSwitcherでは次のことを行う
(1)現在のタスクがtask0,task1のどちらであったか検出
(2)現在の全レジスタをcontext0,またはcontext1として保存
(3)次のタスクにむけてcontext0,またはcontext1を全レジスタに載せる
(4)rteで新タスクを起動(割り込みから戻るふりをする)
*/
#pragma asm
_TaskSwitcher: ; function: TaskSwitcher
;タイマユニットに割り込みを受け付けたことを知らせる
BCLR.B #0,@H'FFFF71:8 ;ITU1.TSR.BIT.IMFA=0;
; ** 旧コンテキスト保存 **
push.l er6
push.l er5
push.l er4
push.l er3
push.l er2
push.l er1
push.l er0
mov.w @_tasksw, r0 ;直前のタスク番号の取得
cmp.w #1, r0
beq RETURNEDFROMTASK1
RETURNEDFROMTASK0:
;直前がタスク0のときのタスクスイッチ作業
mov.w #1, r0 ;次のタスク番号は1
mov.w r0, @_tasksw ;次のタスク番号を変数taskswに保存
;スタックポインタをコンテキスト0のBer7に保存
mov.l er7, @_context0; (context0とcontext0.Ber7は同じアドレス)
;動作状態の表示
mov.l @_systemstackPtr, er7 ;スイッチャのスタックポインタの復帰
jsr @_printContext ;contextのスナップショット(確認用)
jsr @_blinkLED ;LED点滅 (動作点検用)ここでは何をしてもよいことの検証
mov.l er7, @_systemstackPtr ;自分用のスタックの保存
;コンテキスト1のBer7をスタックポインタにセット
mov.l @_context1, er7 ; (context1とcontext1.Ber7は同じアドレス)
bra GONEXTTASK
RETURNEDFROMTASK1:
;直前がタスク1のときのタスクスイッチ作業
mov.w #0, r0 ;次のタスク番号は0
mov.w r0, @_tasksw ;次のタスク番号を変数taskswに保存
;スタックポインタをコンテキスト1のBer7に保存
mov.l er7, @_context1; (context1とcontext1.Ber7は同じアドレス)
;動作状態の表示
mov.l @_systemstackPtr, er7 ;スイッチャのスタックポインタの復帰
jsr @_printContext ;contextのスナップショット(確認用)
jsr @_blinkLED ;LED点滅 (動作点検用)ここでは何をしてもよいことの検証
mov.l er7, @_systemstackPtr ;自分用のスタックの保存
;コンテキスト0のBer7をスタックポインタにセット
mov.l @_context0, er7 ; (context0とcontext0.Ber7は同じアドレス)
GONEXTTASK:
pop.l er0
pop.l er1
pop.l er2
pop.l er3
pop.l er4
pop.l er5
pop.l er6
rte
#pragma endasm
/*この作業はおまけ,インターバルタイマの動作が監視できる*/
void blinkLED()
{
static int tick=0;
if (tick==1) {
turnOnLed(0);
turnOffLed(1);
} else {
turnOffLed(0);
turnOnLed(1);
}
tick=1-tick;
}
実行結果
(ER7がスタックポインタ)*** task switcher demo ***
task0,stack,er7=00000E4E 00000e4e 000fefa8
task1,stack,er7=00000E7E 00000e7e 000ff04c
context for task0
ER0 00000000 ER4 00000000
ER1 00000000 ER5 00000000
ER2 00000000 ER6 00000000
ER3 00000000 ER7 000fefa8
tail of stack + 1 =fefc8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fefa0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fefc0 : 00 00 00 00 00 00 0e 4e 00 0f f0 4c 00 00 00 00
context for task1
ER0 00000000 ER4 00000000
ER1 00000000 ER5 00000000
ER2 00000000 ER6 00000000
ER3 00000000 ER7 000ff04c
tail of stack + 1 =ff06c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 00 00 00 00 00 00 0e 7e 00 00 00 00
task0 0
task0 1
task0 2
task0 3
task0 4
task0 5
task0 6
task0 7
task0 8
task0 9
task0 10
task0 11
task0 12
task0 13
task0 14
task0 15
task0 16
task0 17
task0 18
task0 19
task0 20
task0 21
task0 22
task0 23
task0 24
task0 25
task0 26
task0 27
task0 28
task0 29
task0 30
task0 31
task0 32
task0 33
task0 34
task0 35
task0 36
task0 37
task0 38
task0 39
task0 40
task0 41
task0 42
task0 43
task0 44
task0 45
task0 46
task0 47
task0 48
context for task0
ER0 03c00014 ER4 00000000
ER1 00000011 ER5 00000000
ER2 00000000 ER6 00000030
ER3 00000000 ER7 000fefa4
tail of stack + 1 =fefc8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 03 5a 00 2b 00 00 03 3c 00 0a 00 00 06 be 00 00
fefa0 : 00 00 00 00 03 be 00 14 00 00 00 11 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30
fefc0 : 21 00 0e 42 00 00 0e 72 00 0f f0 4c 00 00 00 00
context for task1
ER0 00000000 ER4 00000000
ER1 00000000 ER5 00000000
ER2 00000000 ER6 00000000
ER3 00000000 ER7 000ff04c
tail of stack + 1 =ff06c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 00 00 00 00 48 00 0e 7e 00 0f fe f8
task1 1000
task1 1001
task1 1002
task1 1003
task1 1004
task1 1005
task1 1006
task1 1007
task1 1008
task1 1009
task1 1010
task1 1011
task1 1012
task1 1013
task1 1014
task1 1015
task1 1016
task1 1017
task1 1018
task1 1019
task1 1020
task1 1021
task1 1022
task1 1023
task1 1024
task1 1025
task1 1026
context for task0
ER0 03be0014 ER4 00000000
ER1 00000011 ER5 00000000
ER2 00000000 ER6 00000030
ER3 00000000 ER7 000fefa4
tail of stack + 1 =fefc8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 03 5a 00 2b 00 00 03 3c 00 0a 00 00 06 be 00 00
fefa0 : 00 00 00 00 03 c0 00 14 00 00 00 11 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30
fefc0 : 21 00 0e 42 00 00 0e 72 00 0f f0 4c 00 00 00 00
context for task1
ER0 0000135f ER4 00000000
ER1 00000300 ER5 00000000
ER2 00000000 ER6 00000402
ER3 00000000 ER7 000ff04c
tail of stack + 1 =ff06c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 20 00 00 00 03 5a 00 2b 00 00 03 3c 00 0a 00 00
ff040 : 06 be 00 00 00 00 00 00 00 00 00 00 00 00 13 5f
ff050 : 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 00 00 04 02 00 00 0e 9e 00 0f ff 08
task0 49
task0 50
task0 51
task0 52
task0 53
task0 54
task0 55
task0 56
task0 57
task0 58
task0 59
task0 60
task0 61
task0 62
task0 63
task0 64
task0 65
task0 66
task0 67
task0 68
task0 69
task0 70
task0 71
task0 72
task0 73
task0 74
context for task0
ER0 03310014 ER4 00000000
ER1 0000000f ER5 00000000
ER2 00000000 ER6 0000004a
ER3 00000000 ER7 000fefa4
tail of stack + 1 =fefc8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 03 5a 00 2b 00 00 03 3c 00 0a 00 00 06 be 00 00
fefa0 : 00 00 00 00 03 31 00 14 00 00 00 0f 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4a
fefc0 : 21 00 0e 42 00 00 0e 72 00 0f f0 4c 00 00 00 00
context for task1
ER0 0000135f ER4 00000000
ER1 00000300 ER5 00000000
ER2 00000000 ER6 00000402
ER3 00000000 ER7 000ff04c
tail of stack + 1 =ff06c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 20 00 00 00 03 5a 00 2b 00 00 03 3c 00 0a 00 00
ff040 : 06 be 00 00 00 00 00 00 00 00 00 00 00 00 13 5f
ff050 : 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 00 00 04 02 40 00 0e 9e 00 0f ff 08
task1 1027
task1 1028
task1 1029
task1 1030
task1 1031
task1 1032
task1 1033
task1 1034
task1 1035
task1 1036
task1 1037
task1 1038
task1 1039
task1 1040
task1 1041
task1 1042
task1 1043
task1 1044
task1 1045
task1 1046
task1 1047
task1 1048
task1 1049
stackにはtask0の先頭アドレスe4eが見える
stackにはtask1の先頭アドレスe7eが見える
スタックの末尾アドレスはfefc7
タスク起動前のスタックの様子
fefa8から未使用のER0..ER6が保存
戻り番地として開始アドレスが設定
スタックの末尾アドレスはff06b
タスク起動前のスタックの様子
ff04cから未使用のER0..ER6が保存
戻り番地として開始アドレスが設定
タスク0が起動
タスク0が戻ってきた
ER0,ER1,ER5,ER6が使われている
スタックはfefa4からfefc7まで
使われている
fefa4からER0..ER6が順に保存されているのがわかる
61がCCR,000e42が戻り番地
スタックは後ろから前に向けて使用される
こちらはまだ使われていない
タスク1が起動
タスク1が戻ってきた
タスク0のコンテキストは変化していない
ER0,ER1,ER5,ER6が使われている
スタックはff04cからff06bまで
使われている
ff04cからER0..ER6が順に保存されているのがわかる
00がCCR,0009f0が戻り番地
タスク0が再開した
課題1 「2つのタスク切り替えのプログラム」を2つ紹介してあるが,
読み取って,自分の言葉でタスク切り替えの要点を説明しなさい。
(mtsk01.txt)
課題2 「2つのタスク切り替えのプログラム」を2つ紹介してあるが,どちらかを改造して3つのタスクが順番に
動作するプログラムを作成し,報告しなさい。
プログラミングは「4」のtaskSwitcherDemoB1.c,taskSwitcherDemoB2.cを使った方が容易かもしれない。ただし3つ目のタスクは
task2 10001
task2 10002
task2 10003
:のように先の2つの例(taskSwitcherDemo1.c,taskSwitcherDemo2.c)と同様に表示するものとする。
提出課題には2周分(task0→task1→task2→task0→task1→task2)の実行結果を貼付すること。ヒント 現行の2つのタスクに加え,3つ目のタスクを用意する。コンテキストバッファも3つ必要となる。
現在どのタスクを実行しているかを示す変数taskswは0,1,2を繰り返すようにする。
タスクスイッチャは起動時にtaskswを見て,コンテキストを対応するコンテキストバッファに退避させる。
(taskswが0なら,コンテキストバッファ0に,taskswが1なら,コンテキストバッファ1に,..)
taskswの値を変化させる。(0→1,1→2,2→0)
変化したtaskswを見ながら対応するコンテキストバッファの内容をコンテキストに戻す。
rteでタスクスイッチャ終了。(mtsk02.txt)