OS自作入門 -Advent29-

NO IMAGE

Step8 スレッドを実装する 後半戦

Step8 前半戦でスレッドのアレコレを学んだ。ビジーループでポーリング的な処理を行うと、処理が止まってしまったり、他のアプリケーションの動きを考慮した実装を行わないといけなくなるためよろしくない。そこで割込みドリブンでCPUが各処理に割り当てられて実行されるようにしていく。という話だった。この時の「各処理」に該当するのがスレッド。なので全アプリケーションがOSの管理下に置かれるようにスレッドに内包する形でアプリケーションを実行させられると良い。

OSの実装

今回は以下のファイルを追加・修正する

  • ブートローダ側
    • ld.scr(修正)
    • intr.S(修正)
    • startup.s(修正)
  • OS側
    • kozos.h, kozos.c(追加)
    • ssycall.h, syscall.c(追加)
    • test08_1.c(追加)
    • ls.scr(修正)
    • startup.s(修正)
    • defines.h(修正)
    • main.c(修正)
    • Makefile(修正)

Step8の成果物はGithubに置いておく。

これまではブートローダもOSもスタックを一つずつ持っていてそのまま特に変更せずに利用しており、割込み発生時にも現在利用しているスタックを利用していた。しかしスタックをスレッドごとに確保する必要性が出てきて、どの処理でどのスタックを利用するべきなのかを考えなくてはならない。

必要となるスタックは以下の3種類

  • 起動処理で利用されるスタック(ブート・スタック)
  • 割込み処理で利用されるスタック(割込みスタック)
  • スレッドごとに確保されるスタック(ユーザ・スタック)

ブートローダ側の修正

これらのスタックを明確化するためにブートローダ側のリンカスクリプトに対する修正を行う。bootstackはブートスタック、intrstackは割込みスタックの定義をしている。ブートスタックはstartup.sで参照され、割込みスタックはintr.Sからの割込み処理で利用される。もともとは、スタートアップで設定されたスタックをそのまま引き続いて利用していて、割込みが発生した場合にも同じスタックを利用していた。しかし、割込み処理やその延長線上にあるOSの動作がスレッドの持つユーザ・スタック上で行われるのは、スレッドのスタックに空きがない場合にユーザ・スレッドのせいでOSの動作に影響が出てしまうなど、いまいちな点があるため明確に分けることにする。

次は、intr.Sの修正を行う。ここの割込みハンドラ入口でスタック・ポインタを割込みスタックに切り替えるようにしている。この処理を追加することにより、割込み発生時には各種レジスタがスレッドのスタック上に積まれ、割込み処理自体も割込みスタックを利用して動作するようになる。

次に、startup.sの修正を行う。これはブート・スタックを利用するための修正である。本にはこの状態のブートローダでstep7のOSを動作させるとうまく動かなくなるという話が載っている。それは今回実装していくstep8のOSがスレッドベースで動作し、ユーザ・スタックを利用するためぶつからないという前提を元にしているからであり、具体的にはOS起動後( runコマンド実行後 )に処理がブートローダに戻ってしまうという状態になる。


OS側の修正

スレッド管理の実装、システム・コールの実装などが入ってきて大改造になるらしい。この辺り実装しつつデバッグしつつできないのがつらいな。

スタックの分離と明確化

まずはld.scrの修正を行う。ブートローダと同様に修正を加え、さらにユーザー・スタックであるuserstackを定義している。

スレッドのディスパッチ

ディスパッチ処理をstartup.sに追加する。スレッドの動作が中断されるときにはブートローダ側のintr.Sで記載の通り各汎用レジスタの値がスレッドのスタックに保存される。よってディスパッチ時にはスレッドのスタックに保存されている各汎用レジスタの値を復旧することで実現が可能となる。今回はレジスタ周りの操作はアセンブラで記載するのでstartup.sにおくことにしているらしい。

この修正により

dispatch(<スタックポインタのアドレス>);

とすれば呼び出すことができる。各汎用レジスタの値はスタック上に保存されているので、スタックポインタの値を得ることで、スタックを参照して各汎用レジスタの値を知ることができる。しかし、この動きを見る限りOSってブートローダがどういうフローで例外処理をしているのかを知らないといけないような気がするんだがどうなんだろう。ブートローダまで含めてOSと呼んでいるんだったか。

OSが提供するサービス

これはkozos.hkozos.cというファイルに置いておく。

各関数それぞれ

  • システムコール
    • kz_run
      スレッドの生成。返り値はスレッドID。
    • kz_exit
      スレッドを終了する。
  • ライブラリ関数
    • kz_start
      初期スレッドの生成。kz_runはシステムコールであるためスレッドからしか実行できない。
      そのため「スレッド生成用スレッド」として定義する。
    • kz_sysdown
      致命的なエラーが発生したときに呼ぶとメッセージを出力し停止する。
    • kz_syscall
      システムコールの呼び出しを行う共通関数。
  • test08_1_main
    ユーザースレッドのメイン関数。
    本来は別ファイルでプロトタイプ宣言するが、とりあえず今回はここで。

kozos.hに対する実装(OSの本体)がそれなり長くなりそうなので今日はここまで。メモリの扱いが値の話なのか、アドレスの話なのか、レジスタはどこを指しているのか辺りがまだふわっとした理解だ。読み直せば多少マシになるかな。