えーと,前回はKOZOSの動作の流れについて説明しただけで サンプルのアプリの動作説明をしてませんでした. ということで今回は,サンプルアプリの説明.
まず,前回紹介した main1.c をもう一度.
こちらは実行結果.% koz main start thread 1 started thread 2 started mainfunc loop 0 mainfunc loop 1 mainfunc end func1 start 1 ./koz func1 loop 0 func2 start 1 ./koz func2 loop 0 func1 loop 1 func2 loop 1 func1 end func2 end %では,main1.c の動作を順に説明しよう.
まず,C言語の常識に違わず main() が実行されるのだけど, kz_start() によっていきなり KOZOS が起動され, スレッド "main" が生成される. スレッド "main" は,mainfunc() をメイン関数として実行開始する.
int main(int argc, char *argv[]) { kz_start(mainfunc, "main", 1, argc, argv); return 0; }で,mainfunc() はこんなふうになっている.
int mainfunc(int argc, char *argv[]) { int i; int id1, id2; fprintf(stderr, "main start\n"); id1 = kz_run(func1, "func1", 2, argc, argv); fprintf(stderr, "thread 1 started\n"); id2 = kz_run(func2, "func2", 2, argc, argv); fprintf(stderr, "thread 2 started\n"); for (i = 0; i < 2; i++) { fprintf(stderr, "mainfunc loop %d\n", i); kz_wait(); } fprintf(stderr, "mainfunc end\n"); return 0; }最初に kz_run() によって "func1" スレッドが生成される.これは func1() を メイン関数として実行開始する.で,kz_run() の呼び出し後にはどうなるかというと, なんだか func1() がそのまま呼ばれそうな気がするのだけど, ここで注意しなければならないのはスレッドの優先度だ.
kz_start() でスレッド "main" を起動したときの優先度(kz_start()の第3引数) は「1」となっている. これに対して,kz_run()によってスレッド "func1" を起動したときの優先度 (kz_run)の第3引数)は「2」だ.つまり kz_run() 実行後のディスパッチ処理によって カレントスレッドになるのは,優先度の高い(=優先度の数値の小さい) スレッド "main" ということになる.なので,mainfunc() 内の処理がそのまま 継続される.
mainfunc()で次に行われるのは,kz_run() によるスレッド "func2" の生成だ. これに関しても,優先度「2」で生成されるので, スレッド "main" はスレッド "func2" を生成したままさらに処理を進めることになる.
ここで,func1(),func2() の先頭と実行結果を見てほしい.
int func1(int argc, char *argv[]) { int i; fprintf(stderr, "func1 start %d %s\n", argc, argv[0]); ...
int func2(int argc, char *argv[]) { int i; fprintf(stderr, "func2 start %d %s\n", argc, argv[0]); ...
% koz main start thread 1 started thread 2 started mainfunc loop 0 ...func1(), func2() とも,先頭で fprintf() によりメッセージを出力しているのに, 実際の実行結果では mainfunc() 内部のメッセージ出力が行われている. つまり,スレッド "func1","func2" は実際には動作開始せず,スレッド "main" が そのまま動作していることがわかる.
スレッド "main" はさらに for ループに入り,メッセージを出力する.
for (i = 0; i < 2; i++) { fprintf(stderr, "mainfunc loop %d\n", i); kz_wait(); } fprintf(stderr, "mainfunc end\n");実行結果をもう一度,今度は全体を見てみよう.
% koz main start thread 1 started thread 2 started mainfunc loop 0 mainfunc loop 1 mainfunc end func1 start 1 ./koz func1 loop 0 func2 start 1 ./koz func2 loop 0 func1 loop 1 func2 loop 1 func1 end func2 end %mainfunc() のループが終了した後で,ようやく func1(),func2() 先頭の メッセージ出力が行われている.mainfunc()のループ内では kz_wait() による ディスパッチが行われているのだが,結局のところスレッド "main" の優先度が一番 高いので,"main" が動作している限りはカレントスレッドは "main" のままとなり, "main" が終了してからはじめて "func1","func2" が動き出していることになる.
func1()とfunc2()ではどちらもループしながらメッセージ出力しているが, 実際にはメッセージは交互に出力されている.これはどちらもループ内で kz_wait() により処理を相手に渡しあっているからだ.
int func1(int argc, char *argv[]) { int i; fprintf(stderr, "func1 start %d %s\n", argc, argv[0]); for (i = 0; i < 2; i++) { fprintf(stderr, "func1 loop %d\n", i); kz_wait(); } fprintf(stderr, "func1 end\n"); return 0; }
int func2(int argc, char *argv[]) { int i; fprintf(stderr, "func2 start %d %s\n", argc, argv[0]); for (i = 0; i < 2; i++) { fprintf(stderr, "func2 loop %d\n", i); kz_wait(); } fprintf(stderr, "func2 end\n"); return 0; }このように,アプリの動作は優先度に左右される. この「優先度」というのがイマイチピンとこないひとも多いと思うし, それってそんなに重要なの? といったように思うひとも多いと思うのだが, すっげー重要です.この優先度の設計によって, 動作ががらりと変わったり,おかしなバグが出たり解決したり, 排他の問題が出たりやんだり,リアルタイム性があったりなくなったりという, とにかく組み込みOSのスレッド設計の根幹となると言ってもいいくらいのものです.
優先度を理解するために,ちょっと優先度を変更して実行してみましょう. main1.c の kz_start() 実行時の優先度を1→3に変更してみます.
--- main1.c Fri Oct 19 00:49:30 2007 +++ main2.c Fri Oct 19 00:49:36 2007 @@ -60,6 +60,6 @@ int main(int argc, char *argv[]) { - kz_start(mainfunc, "main", 1, argc, argv); + kz_start(mainfunc, "main", 3, argc, argv); return 0; }上記の main2.c を main.c にリネームして make することで, スレッド "main" の優先度を1→3に変更した実行形式を作成できます. で,実行してみます.
% koz main start func1 start 1 ./koz func1 loop 0 func1 loop 1 func1 end thread 1 started func2 start 1 ./koz func2 loop 0 func2 loop 1 func2 end thread 2 started mainfunc loop 0 mainfunc loop 1 mainfunc end %どうです? ぜんぜん違うでしょ?
注目すべきは,スレッド "func1" がまっさきに動作してループ処理して終了して しまっている点です.スレッド "main" の優先度は3ですが,スレッド "func1" の 優先度は2です.なので kz_run() によりスレッド "func1" が生成されると, その後のディスパッチによってスレッド "func1" が動き出してしまいます. この間,スレッド "main" の動作は待たされます. func1() のループの内部では kz_wait() によるディスパッチが行われますが, スレッド "main" が待たされているためにスレッド "func2" がまだ起動していません. よってスレッド "func1" がそのまま動き続け,終了します. スレッド "func1" が終了すると,ようやくスレッド "main" が動き出します. ここで,ようやく「thread 1 started」という,スレッド "func1" を開始した旨の メッセージが出力されます.さらに kz_run() により今度はスレッド "func2" が 起動されるのですが,スレッド "func1" と同様に優先度がスレッド "main" よりも 高いので,そのままループに入り,終了するまで突き進みます. スレッド "func2" も終了した後に,ようやくスレッド "main" がループ処理を進めて 終了することになります.
今度は,main1.c に対してスレッド "func2" の優先度を変えてみましょう.
--- main1.c Fri Oct 19 00:49:30 2007 +++ main3.c Fri Oct 19 00:49:43 2007 @@ -45,7 +45,7 @@ id1 = kz_run(func1, "func1", 2, argc, argv); fprintf(stderr, "thread 1 started\n"); - id2 = kz_run(func2, "func2", 2, argc, argv); + id2 = kz_run(func2, "func2", 3, argc, argv); fprintf(stderr, "thread 2 started\n"); for (i = 0; i < 2; i++) {
% koz main start thread 1 started thread 2 started mainfunc loop 0 mainfunc loop 1 mainfunc end func1 start 1 ./koz func1 loop 0 func1 loop 1 func1 end func2 start 1 ./koz func2 loop 0 func2 loop 1 func2 end %スレッド "main" は優先度1, スレッド "func1" は優先度2, スレッド "func2" は優先度3になっています. まずスレッド "main" が走りきり, 次にスレッド "func1",最後にスレッド "func2" が走っていますね.
ここまではスレッドの優先順位による単純な処理でしたが, main2.c に対して,スレッドのスリープと再起動(wakeup)を入れてみましょう.
--- main2.c Fri Oct 19 00:49:36 2007 +++ main4.c Fri Oct 19 00:49:56 2007 @@ -8,6 +8,8 @@ int i; fprintf(stderr, "func1 start %d %s\n", argc, argv[0]); + kz_sleep(); + fprintf(stderr, "func1 wakeup\n"); for (i = 0; i < 2; i++) { fprintf(stderr, "func1 loop %d\n", i); @@ -52,6 +54,9 @@ fprintf(stderr, "mainfunc loop %d\n", i); kz_wait(); } + + fprintf(stderr, "thread 1 wakeup\n"); + kz_wakeup(id1); fprintf(stderr, "mainfunc end\n");実行結果は次のようになります.
% koz main start func1 start 1 ./koz thread 1 started func2 start 1 ./koz func2 loop 0 func2 loop 1 func2 end thread 2 started mainfunc loop 0 mainfunc loop 1 thread 1 wakeup func1 wakeup func1 loop 0 func1 loop 1 func1 end mainfunc end %さて,main2.c の実行結果に対してどのように変化しているでしょうか? main2.c ではスレッド "main" の優先度が最も低かったために スレッド "func1" が生成されるとまずは "func1" が走りきり, 次にスレッド "func2" が生成されると今度は "func2" が走りきり, 最後に "main" が走り終っていました.
しかし今回の結果では,同様に "func1" が走ろうとしますが, 起動直後にループの手前で "func2" に切り替わっています. これは func1() の先頭で kz_sleep() によりスレッドがスリープしているためです. スレッド "func1" はスリープしてしまうために今度は "func2" が動作を始め, 止まる部分が無いためにそのまま終了まで突き進んでいます.
"func2" が終了した後にはどうなるでしょうか? "func1" は相変わらずスリープ中 であるために動作できません.よって優先度は低いのですが,"main" が動作を 始めます."main" は mainfunc() 内部でのループ処理を行った後, kz_wakeup() によってスレッド "func1" を起こします. これによりスレッド "func1" が動作を再開し,優先度の高い "func1" のほうが, 今度は終了まで突き進みます. "func1" の終了後は再び "main" が動作を始め,終了しています.
優先度をいろいろ変えたり,sleep と wakeup の位置を変えたりしていろいろ 試してみてください.数値をひとつ変えるだけで,こんなにも動作が変わるもの なのかということが実感できると思います.