OS自作入門 -Advent34-
Step9 優先度スケジューリング 延長戦
kozosファイルの修正が終わったのでシステムコールの追加を行なっていく。
システム・コールの追加
syscall.hの修正では、kozosファイルで利用していた構造体の定義を追加したり、kz_run()
のパラメータに優先度を渡せるように修正を行う。
syscall.cの修正では、システム・コールのAPIとなる関数の実装を行なっている。
サンプル・プログラム
スレッド動作のサンプルとして、3種類のスレッドを動作させる。
これを実装。
アイドル・スレッド
main.cに対しての修正を行う。
kz_start()
で初期スレッドが開始され、start_threads()
でtest09_1_main()
,test09_2_main()
,test09_3_main()
が別々のスレッドとして起動するようになっている。起動した後は、sleep
で省電力モードに移行して、割込み待ち状態を作る。なので初期スレッドはアイドル・スレッドとなる。
最後にMakefileに対して修正を行いプログラムの修正は終わり。
プログラムの実行
完成したプログラムをmake
して、xmodem
で転送してrun
する。(今回からブートローダに手を入れる必要がなくなったので、load
だけすれば良くなった)
実はずっとELFファイルを転送していたが、今回くらいから8KBを超え初めて転送が上手くいかなくなる。公式サイトの方に
■ ステップ9付近でXMODEMでの転送がうまくいかなくなった場合
XMODEMでのOS転送が,当初はうまくできていたのにステップ9あたりでうまく いかなくなる,という現象に遭遇するかたがたまにいます.この場合,転送ファイルに「kozos」でなく「kozos.elf」を指定していないかどうか, 確認してみてください.
これらのファイルはどちらもELF形式のためKOZOSのブートローダーで解釈できますが, 「kozos.elf」はstripによるシンボル情報削除前のファイルのため,ファイルサイズが 大きめになっています.そしてKOZOSのブートローダーが扱えるファイルのサイズは (書籍中にもありますが)8KB程度までです.
で,OSの開発を書籍通りに進めていくとて9ステップあたりでkozos.elfが8KBを 越えてしまうため,転送ができなくなる,ということになるようです.
書籍中では「kozos.elf」でなく「kozos」を指定することになっていますが 「kozos.elf」でも最初のうちはサイズが小さくうまく転送できるため, 気付かずにそのまま「kozos.elf」を利用し続けてしまう場合があるようです.
ただし「kozos」にはシンボル情報等は残っていないため,以下のように使い分ける ことになります.
「kozos」…XMODEMでマイコンボードに転送するファイルはこちら
「kozos.elf」…readelfで解析したり(将来的に)デバッガの解析に利用したり するのはこちら
とあるので、kozos
ファイルの方を転送すれば良い。
転送して実行する。
../../tools/bin/kz_xmodem kozos /dev/tty.usbserial-FT0BTH3I
# =================================================
# XMODEM for KOZOS H8/3069F (Version 0.0.2)
# Copyright(C) 2012 Shinichiro Nakamura
# =================================================
# Flushing serial port.
# Wait.
# Setup load condition.
# Wait a NAK.
# ..........
# Transmit the target ELF file.
# File(kozos): 37 blocks + 100 bytes
# ......................................
# Wait a message from the target.
# Complete.
sudo cu -l /dev/tty.usbserial-XXXXXXX -s 9600
# Connected.
#
# unknown.
# kzload> run
# starting from entry point: ffc020
# kozos boot succeed!
# test09_1 started.
# test09_1 sleep in.
# test09_2 started.
# test09_2 sleep in.
# test09_3 started.
# test09_3 wakeup in (test09_1).
# test09_1 sleep out.
# test09_1 chpri in.
# test09_3 wakeup out.
# test09_3 wakeup in (test09_2).
# test09_2 sleep out.
# test09_2 chpri in.
# test09_1 chpri out.
# test09_1 wait in.
# test09_3 wakeup out.
# test09_3 wait in.
# test09_2 chpri out.
# test09_2 wait in.
# test09_1 wait out.
# test09_1 trap in.
# test09_1 DOWN.
# test09_1 EXIT.
# test09_3 chpri out.
# test09_3 exit in.
# test09_3 EXIT.
# test09_2 wait out.
# test09_2 exit in.
# test09_2 EXIT.
解説
-
kz_start()
によって優先度0でstart_threads()
が起動する// main.c kz_start(start_threads, "idle", 0, 0x100, 0, NULL);
-
test09_1
,test09_2
,test09_3
のスレッドが起動する// main.c test09_1_id = kz_run(test09_1_main, "test09_1", 1, 0x100, 0, NULL); test09_2_id = kz_run(test09_2_main, "test09_2", 2, 0x100, 0, NULL); test09_3_id = kz_run(test09_3_main, "test09_3", 3, 0x100, 0, NULL);
- この時点で優先度が0〜3までのスレッドが起動しているが、すぐに動作を始めるわけではなく
kz_chpri()
で優先度を15(最低)に変更する。// main.c kz_chpri(15);
- 最も優先度の高い優先度1の
test09_1
が起動する// test09_1.c puts("test09_1 started.\n"); puts("test09_1 sleep in.\n");
-
kz_sleep()
によりスリープ状態になる// test09_1.c kz_sleep();
- 次にレディー状態で次に優先度の高い(優先度2)のスレッドである
test09_2
が動作する// test09_2.c puts("test09_2 started.\n"); puts("test09_2 sleep in.\n");
-
kz_sleep()
によりスリープ状態になる// test09_2.c kz_sleep();
- 次にレディー状態で次に優先度の高い(優先度3)のスレッドである
test09_3
が動作する// test09_3.c puts("test09_3 started.\n"); puts("test09_3 wakeup in (test09_1).\n");
-
kz_wakeup
によりtest09_1
がレディー状態に戻る// test09_3.c kz_wakeup(test09_1_id);
-
test09_1
のスレッドが再開する// test09_1.c puts("test09_1 sleep out.\n"); puts("test09_1 chpri in.\n");
- 優先度を1 => 3に変更する。これにより
test09_1
とtest09_3
は同じ優先度となり、この二つはラウンドロビン方式でスケジューリングされる。// test09_1.c kz_chpri(3);
- 優先度3のスレッドのレディー状態でFirstInされている
test09_3
が再開する(test09_1
は終端に接続されているため)// test09_3.c puts("test09_3 wakeup out.\n"); puts("test09_3 wakeup in (test09_2).\n");
-
test09_1
に対して行なった処理と同様にtest09_2
もwakeupして優先度を変更する// test09_3.c kz_wakeup(test09_2_id);
// test09_2.c puts("test09_2 sleep out.\n"); puts("test09_2 chpri in.\n"); kz_chpri(3);
- 上記wakeupの時点で優先度3のキューでは
test09_1
,test09_3
の順番になっているのでkz_chpri(3)
の後はtest09_1
の処理が再開する// test09_1.c puts("test09_1 chpri out.\n"); puts("test09_1 wait in.\n"); kz_wait();
-
test09_1
=>test09_3
=>test09_2
でキューイングされているので次はtest09_3
// test09_3.c puts("test09_3 wakeup out.\n"); puts("test09_3 wait in.\n"); kz_wait();
- 次は
test09_2
// test09_2.c puts("test09_2 chpri out.\n"); puts("test09_2 wait in.\n"); kz_wait();
- 一周して
test09_1
が再開。トラップ命令を実行して割込みを発生させる。// test09_1.c puts("test09_1 wait out.\n"); puts("test09_1 trap in.\n"); asm volatile ("trapa #1");
割込みベクタのうち、トラップ命令要因の場合は割込み番号が8〜11でベクタ・アドレスが
0x000020
〜0x00002c
である。
今回は#1
のオペランドを渡しているので9番の割込みが発生する(#0
8番の割込みはシステム・コールの実装で利用している)vectors[]
の定義を見ると割込みベクタ9番にはintr_softerr
が設定されておりSOFTVEC_TYPE_SOFTERR
の割込みが発生することになる。このハンドラはsofterr_intr()
が設定されているのでDOWN
というメッセージを出力し、thread_exit()
によってスレッドを終了する挙動となる。そのためDOWN
,EXIT
の出力を行い、それ以降のメッセージが表示されていないのが確認できる。# test09_1 trap in. # test09_1 DOWN. # test09_1 EXIT. // ...以後 test09_1に関するメッセージなし
このようにして、不正アドレス・アクセスがあった場合には、割込みハンドラに
intr_softerr
を設定しスレッドの終了処理を行えば、何かしらバグ等があった場合にもシステム全体を落とすのではなく、特定のサービスのみ停止するような動作をさせることができるようになる。 - 次に動くのは
test09_3
でkz_exit()
を呼んでスレッドを終了している。// test09_3.c puts("test09_3 chpri out.\n"); puts("test09_3 exit in.\n"); kz_exit();
- 最後に
test09_2
が再開する。// test09_2.c puts("test09_2 wait out.\n"); puts("test09_2 exit in.\n"); return 0;
スレッドの動作開始時には
thread_run()
により、thread_init()
が最初に起動する関数として設定される。具体的にはスレッドがディスパッチされた時にプログラム・カウンタにthread_init()
のアドレスが格納されるようになっている。なので、スレッドの動作開始時にはスレッドのメイン関数ではなくtread_init()
である。// kozos.c static void thread_init(kz_thread *thp) { thp->init.func(thp->init.argc, thp->init.argv); thread_end(); }
なので、最終的には
thread_end()
が呼ばれてスレッドが終了するという流れになる。# test09_2 wait out. # test09_2 exit in. # test09_2 EXIT.
スレッドに優先度をつけて実行することができるようになった。処理するものがない時にはスリープ状態となり割込み待ちとする実装も追加した。ちなみにこの「優先度をベースとしたスケジューリング」は組込みOS特有とかなんとか。。。汎用OSでは「一定時間毎のスケジューリング」になるらしい。んー深いな。
汎用OSではユーザーの手によって様々なアプリがインストールされるため、それらが システム全体が致命的におかしな状態になることを避ける が至上命題。なので今回のような優先度の高いものが無限ループで先に進まなくなったりすると大問題。そのため各アプリ(スレッド)が満遍なく動作するように設計されているためだそうだ。
組込みOSの場合は、ユーザーがアプリを入れるのではなく製品の一部として開発されているため、こちらの手のひらの上でアプリを組むことができ、全体のバランスを見ながら開発元が調整することができる。逆にハードウェア制御などが多く処理の内容に優先度を持たせたい場合が多くあるためこのような設計思想になっている。
とのことだった。
動きの流れは追いやすいが、処理の細かいところとかがなかなか難しい。。。あとやっぱデバッグが辛い。動かないとそのstepで書いてた全部を読み直さないと思ったように出力してくれないのがつらみ。
- 前の記事
OS自作入門 -Advent33- 2018.01.22
- 次の記事
【フィリピン】マニラレポート(到着直後) 2018.01.29