More Related Content
Similar to 【学習メモ#8th】12ステップで作る組込みOS自作入門 (20)
【学習メモ#8th】12ステップで作る組込みOS自作入門
- 5. ポーリングによるサービスの実行
● 割込みを利用せず一定時間ごとにサービスを実
行する場合、ビジーループで行う方法がある
int main()
{
.
.
while(1) {
if(0.1秒が経過したか) {
led_main();
}
}
.
.
}
int led_main()
{
(LEDを点滅させる処理)
}
- 6. 複数のサービスを切り替えて実行
● 今度は一定時間ごとに複数のサービスを切り替
えて実行するケース
int main()
{
.
.
while(1) {
if(0.1秒が経過したか) {
led_main();
}
if (1秒が経過したか?) {
write_time();
}
if (シリアル受信したか?) {
command_exec();
}
}
.
.
}
- 7. メイン・ループ
● 前のページのように、while(1)のような処理の
中心となるループをメイン・ループと呼ぶ
● それぞれ一定時間ごとにチェックしながらサー
ビスを実行する場合、このビジー・ループの方
法だと重い処理を行うときに時間がずれる
● そういった重い処理をビジー・ループで正常に
回したいなら、一定のタイミングごとにメイ
ン・ループに戻るという方法が考えられる
– 処理状態を保存して(サンプルの場合はstaticの変
数に格納している)戻る感じだね
● とはいえ、サービスの増加や処理が複雑になっ
ていけば、このような方法ではもたない
- 8. 処理の共通化
● サービスに対する処理を共通化する
– 定期的な処理の切り替え
– 処理状態の中断、保存、再開
● 処理状態の保存ならstaticの変数に退避させず
にスタックとレジスタの状態を保存すればいい
– 各々の関数で行う必要はない
● このためには、実行する処理をサービスの単位
で区切る必要がある
● このサービスの単位をタスクと呼ぶ
- 9. タスクをスレッドとして実装
● スレッドとして実装することでプログラムをタ
スクごとに独立して動作できる
– 下記のようなイメージで書ける
int main()
{
kz_start(start_threads, …);
return 0;
}
ini start_threads(int argc, char *argv[])
{
kz_run(led_main, “led”, 1, ...);
kz_run(timer_main, “timer”, 1, ...);
kz_run(command_main, “command”, 1, ...);
while(1) {
asm volatile(“sleep”);
}
}
.
.
- 10. スレッドの起動とシステム・コール
● kz_run()にタスクにあたる関数を渡すことで、
それをOSがメイン関数としてスレッドを起動す
る
– こうすることで各タスクはスレッドとしてひとつの
処理単位で動作する
● kz_run()のようにOSに対するサービス要求をす
る関数をシステム・コールと呼ぶ
● タスクの切り替えは割込みが発生したときかシ
ステム・コールが呼ばれたときに行われる
- 11. スケジューリングとディスパッチ
● 割込みかシステム・コールが呼ばれたとき、次
に動作すべきスレッドの選択とその処理再開が
行われる
– 前者をスケジューリングと呼び、後者をディスパッ
チと呼ぶ
● 動作を中断したスレッドをディスパッチによっ
て再開するには、中断時の処理状態を保存して
おく必要がある
– その情報をコンテキスト情報、あるいは単にコンテ
キストと呼ぶ
- 12. コンテキスト情報
● 一般的にはSPやPCも含む各種レジスタ値がス
レッドのコンテキストにあたる
● スレッドのディスパッチにはそのスレッドのコ
ンテキストを新たに読み込む必要がある
– この動作をコンテキスト切り替え、コンテキスト・
チェンジと呼ぶ
● スレッドはタスクがそれぞれ独立して実行して
いるようにみせかけているだけで、実際は細か
くタスクを切り替えながら並列に動作させてい
るに過ぎない
- 13. アプリケーション・プログラム
● スレッドを実装することで各種サービスをタス
クに分割してスレッド化し、別々のプログラム
のように動作させることができる
● OS上でタスクとして動作するプログラムを一般
にアプリケーション・プログラムと呼ぶ
– 日本語では応用プログラムと言う
– スレッドとして動作するならそのスレッドはユー
ザ・スレッドと呼ぶことができる
- 14. カーネル
● OSの中核のことをカーネル、またはコアと呼ぶ
● もともと備わっているアプリケーション・プロ
グラムも含めた全体をOSと呼ぶ場合と、カーネ
ル部分のみをOSと呼ぶ場合がある
● たとえばWindowsでいうところのエクスプロー
ラはアプリケーション・プログラムだし、Mac
でいえばFinderがそれにあたる
- 15. 割込みでスレッドの切り替え
● スレッドの切り替えはOSが適当なタイミングで
行う。具体的には割込み処理で行う
● 割込み処理を応用すればレジスタ値の保存と復
旧の処理をスムーズに行える
– たとえば、割込み復帰命令を応用すればディスパッ
チすることができるなど
● スレッドの切り替えは割込み処理でなくてもで
きるけど、割込みの「処理を強制的に中断す
る」性質を利用した方が正確な動作が期待でき
る
- 16. システム・コール
● OSは割込み処理の延長として動作する
– このためスレッドからOSの機能を利用するには、ス
レッド側から明示的に割込みを発生させるのが適切
● このために多くのCPUはソフトウェア的に割込
みを発生させることができるシステム・コール
命令を持っている
– 命令を実行するとシステム・コール割込みという内
部割り込みが発生
– あとは外部割り込みの要領で割込みハンドラを用意
してそれを実行させる流れ
● こういう手順で実装されるOSのサービスを一般
的にシステム・コールと呼ぶ
- 18. プログラムの追加
● ブートローダ
– 無し
● OS
– kozos.h,kozos.c...スレッドの実装
– syscall.h,syscall.c...システムコール利用のため
の関数
– test08_1.c...テスト・プログラム
- 19. プログラムの修正
● ブートローダ
– ld.scr...割込みとブートのスタックを明確化
– intr.S...割込みスタックへの切り替えを追加
– startup.s...ブートスタックを利用するように変更
● OS
– ld.scr...スタックを分離、明確化
– startup.s...スレッドのディスパッチを追加
– defines.h...型の定義を追加
– main.c...OSを利用するように変更
– Makefile
- 20. ブートローダの修正
● スタックをスレッドごとに確保するように変え
る
● スタックを3種類に分ける
– 起動処理で利用(ブートスタック)
– 割込み処理で利用(割込みスタック)
– スレッドごとに確保(ユーザ・スタック)
● ブートローダにユーザ・スタックは必要ないの
で上2つだけ
- 21. 割込みスタック
● 割込み処理のタイミングでスタックを切り替え
● ld.scrでスタックのアドレスを取得し、intr.S
とstartup.sにてそれぞれ切り替えている
– ここではintr.Sのsyscallだけだけ表示
_intr_syscall:
.
.
.
mov.l er0, @-er7
mov.l er7, er1
mov.l #_intrstack, sp ←割込みスタックを利用
mov.l er1, @-er7
mov.w #SOFTVEC_TYPE_SYSCALL, r0
jsr @_interrupt
mov.l @er7+, er1
mov.l er1, er7a
- 22. OSの修正と追加
● 修正と追加内容
– スタックの分離
– ディスパッチの実装
– スレッドの実装
– システムコールの実装
– アプリケーションの実装
● スタックについてはブートローダと同じ
- 23. ディスパッチ(startup.s)
● @er0, er7でスタックポインタをユーザ・ス
タックの位置に切り替えていると思われる
– 以降はthread_run()で用意したユーザ・スタックを
使っていく
– あとは書籍の説明にある通り
_dispatch:
mov.l @er0, er7
mov.l @er7+, er0
mov.l @er7+, er1
mov.l @er7+, er2
mov.l @er7+, er3
mov.l @er7+, er4
mov.l @er7+, er5
mov.l @er7+, er6
rte
- 24. スレッド(kozos.c)
● 大まかな流れとしては、kz_start()で初期ス
レッドを生成し、それからkz_run()でcommand
スレッドを生成しているという流れ
● この際に実装したシステム・コールを利用して
いる
● 処理があっちこっち飛んでいるうえに構造体が
あるので理解しにくい部分だと思う
● コードの量が多いので、最後のまとめで細かい
部分を記述する
- 25. システムコール(syscall.c)
● kozos.cで定義しているシステム・コールを呼
び出すためのサービス関数(API)
● アプリ側からOSの機能を利用するときに使うイ
ンタフェース
● 今はkz_run()とkz_exit()を持ってい
て、kozos.cのsyscall()を呼び出す機能を持っ
ている
● その先にsyscall()からthread_intr()に渡すた
めのパラメータを構造体に設定して持っている
- 26. アプリケーション
(test08_1_main.c)
● コンソールからの文字を入力された通りに返す
アプリ
● ここは特に難しくないので特に触れない
- 28. ビルドの失敗(kozos.c)
● ビルドに失敗した
● どうもプロトタイプ宣言は関数の外にする必要
があるらしい。(gcc4.7.1ではこうしなければ
ならない)
static void thread_intr(softvec_type_t type, unsigned long sp);
static int setintr(softvec_type_t type, kz_handler_t handler)
{
softvec_setintr(type, thread_intr);
handlers[type] = handler;
return 0;
}
- 29. もういっこビルドの失敗(kozos.c)
● どうも構造体の大きさに問題があるらしい。そ
こで64bitの構造体へと大きさを調節
● アライメントだったけなこれ
typedef struct _kz_thread {
.
.
} syscall;
kz_context context;
char dummy[16]; ←ここを追加
} kz_thread;
- 30. プログラムの実行
/Users/sandai/12step/src/08/os% sudo cu -l /dev/tty.usbserial-FTG6PQ4H
Connected.
kzload (kozos boot loader) started.
kzload> load
~+lsx kozos
Sending kozos, 27 blocks: Give your local XMODEM receive command now.
Bytes Sent: 3584 BPS:282
Transfer complete
XMODEM receive succeeded!
kzload> run
starting from entry point: ffc020
kozos boot succeed!
start EXIT.
test08_1 started.
> echo eeee
eeee
> exit
test8_1 exit.
command EXIT.
system error.
- 32. まとめ1
● 8ステップが今までで一番難しい気がする
● いくつかメモを取りながら構造体のパラメータ
を把握して、流れを読み取る必要があるだろう
● 細かい説明は書籍にあるのでいいとして、ここ
では全体的な流れを細かく記述しておく
- 33. まとめ2
● main関数からkz_start()呼び出し
● kz_start()
– データの初期化
– setintrによる割込みハンドラの設定
– thread_run()の直接呼び出しによる初期スレッドの
生成
– 作成したスレッドをdispatch()により起動
● thread_init()が呼び出される
– thp->init.func()によりstart_threads()を
呼び出し
- 34. まとめ3
● start_threads()でkz_run()が呼び出される
● kz_run()
– 受け取ったパラメータを構造体に設定
– それからkz_syscallでシステム・コール発行
● kz_syscall()
– 受け取ったシステムコールの種類(type)と、設定し
た構造体をcurrent->syscall.xxxに退避
– その後asm volatile(“trapa #0”)でトラッ
プ命令の割込みを自発的に発生させる
- 35. まとめ3
● トラップ命令が発生するとsetintr()で設定し
たthread_intr()が呼び出される
● thread_intr()
– spのコンテキストをcurrent->contextに保存
– handlers[type]()でsetintr()のときに登録したハ
ンドラのうち、syscall_intr()を呼び出す
● syscall_intr()からsyscall_proc()を呼び出す
- 36. まとめ4
● syscall_proc()
– getcurrent()により、カレント・スレッドをキュー
から外す。この段階ではキューには何も入っていな
い状態になる
– call_functions()を呼び出す
● call_functions()
– KZ_SYSCALL_TYPE_RUNが今回のシステムコールの種
類なので、thread_run()が実行される
– パラメータはkz_syscall()のときに退避させておい
たcurrent->syscall_param
- 37. まとめ5
● thread_run()が実行されたら、処理がさっきの
thread_intr()まで戻る
– handler[type]()ってやってたところね
– たぶんここの流れがわかりにくいが、割込み処理が
終了したのだから戻るのは当たり前
● thread_intr()に戻ると、schedule()が実行さ
れ、キューの先頭がカレントスレッドになる
– カレントスレッドは最初に起動したstartスレッ
ド。このスレッドの流れもつかみにくい部分だけ
ど、きっちり処理を追えば分かるはず
● そしてdispatch()で割込みで中断していた
startスレッドが再開される
- 38. まとめ6
● startスレッドの再開は、thp->init.func()が
丁度終えたところから
– thread_end()の呼び出し。実体はkz_exit()
● kz_exit()
– kz_syscall()でシステムコールが発行される
– 種類はKZ_SYSCALL_TYPE_EXIT
● kz_syscall()
– パラメータはNULLなので何もなし
– asm volatile(“torapa #0”)のトラップ命令で割
込みが発生し、thread_intr()が呼び出される
- 39. まとめ7
● thread_intr()
– handlers[type]()が呼び出される。これは
syscall_intr()にあたる
● syscall_intr()
– ここでsyscall_proc()が呼び出される
● syscall_proc()
– getcurrent()により、カレントスレッドである
startスレッドがキューから外れる
– call_functions()の呼び出し
- 40. まとめ8
● call_functions()
– typeはKZ_SYSCALL_TYPE_EXITなの
で、thread_exit()が呼び出される
– thread_exit()が呼び出され、start EXITの文字が
出力され、memset()でスレッドがクリアされ消滅
● 処理はthread_intr()に戻り、schedule()の呼
び出しによって、commandスレッドがキューの
先頭に来る
● それからdispatch()によりcommandスレッドが
起動
- 41. まとめ9
● dispatch()によりthread_init()が呼び出さ
れ、thp->init.func()によりtest08_1_mainが
呼び出されるという流れ
● ながいーおわりー
- 42. まとめまとめまとめー
● kz_start()の存在があるからちょっとわかりに
くいけれど、これは結局は、スレッドを起動す
るための「スレッド」を作ってる
● でまあスレッドを作ったらkz_exit()でシステ
ムコールを発行して、次のスレッドを起動させ
ているってわけね
● thread_run()をシステムコールから発行してい
ないので、ちょっと違和感がある