【学習メモ#8th】12ステップで作る組込みOS自作入門

1,881 views

Published on

12ステップで作る組込みOS自作入門
http://www.amazon.co.jp/dp/4877832394/
坂井 弘亮(著)
カットシステム

Published in: Technology
  • Be the first to comment

  • Be the first to like this

【学習メモ#8th】12ステップで作る組込みOS自作入門

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

×