UNIX 6th code reading - sched - /etc/init実行まで

初めての方はこちらを参照 http://d.hatena.ne.jp/takahirox/20101024/1287923014

はじめに

6章の続きです。proc#0がproc#1を生成した後の話です。プロセスを切り替えた後にブートストラッププログラムを起動します。
Lions本に従い、初回起動時にどういう動作をするかという観点でメモを書いています。(一部例外あり)

sched

スケジューリングを行う。永久ループとなっている。

  • 1958 : プロセッサ優先度を6に設定。クロックなどの割り込みを防ぐ。プロセス優先度一覧は336P参照
  • 1959 - 1965 : 状態がSRUNで、ロードされておらず(スワップアウトされており)、一番長くロードされていない(一番長くスワップ状態にある)プロセスを選択する。初回起動時はproc#0, 1ともスワップインされている状態なので、対象となるプロセスは見つからない
  • 1966 - 1970 : ↑のようなプロセスが見つからなかった場合。runoutフラグをインクリメントして、現在のプロセスを優先度-100(緊急)でsleepさせる。runoutフラグについては後の章で確認する予定

プロセッサ優先度6のままsleepを呼び出すんだなぁ。

sleep

現在のプロセスを眠らせておく。眠らせたあとにswtchを呼び出して、実行プロセスを切り替える。

  • 2070 : 現在のPSをsに保存しておく
  • 2072 - 2086 : 優先度が0以上の場合。シグナルを処理する。
  • 2087 - 2094 : 優先度が負の場合。シグナルを無視する。状態をSSLEEPにしてswtchを呼び出す。

sleepから起きたときのロジックは、8章で確認する予定。

swtch

優先度の最も高いプロセスに切り替える。

  • 2184 - 2185 : pはstatic変数でproc#0のアドレスを記憶しておく。これって何のため?
  • 2189 : savuを呼び出し、現在のプロセスのr5, r6を保存しておく
  • 2193 : retuを呼び出し、proc#0にプロセスを切りかえ(proc#0に対応したr5, r6に切り替える)。proc#0はスケジューリング用のプロセス
  • 2204 - 2214 : proc構造体の配列を線形探索して、最も優先度の高いプロセスを選択する。初回起動時は、ここでproc#1が選択される
  • 2228 : retuを呼び出し、proc#1に切り替える(proc#1に対応したr5, r6に切り替える)
  • 2229 : suregを呼び出し、proc#1に対応したユーザ用のAPRを設定する
  • 2240 - 2243 : スワップ関係の処理。スワップを扱う章のときに、ロジックを追いなおす予定
  • 2247 : returnする先は、2228で実行したretuによって決まる。初回起動時はmainに戻る。ここの詳細は後日まとめる予定

sureg

ユーザ用のAPRを設定する。現在のプロセスに対応したAPRをUISA->r, UISD->rが差すようにする。

  • 1750 - 1751 : PARの設定
    • 1751 : aというのは、現在のプロセス(retuで指定したプロセス)のアドレス。これを、up(たぶんユーザ用APRの基底アドレス)に足す事で、現在のプロセスに対応した適切なAPRをrpが差すようにする。
  • 1760 - 1764 : PDRの設定
    • 1762 - 1763 : write onlyのPageに対する処理。内容はよくわかっていないので、今度振り返る予定

suregロジックの内容を鑑みるに、retuでプロセスを切り替えた後には必ずsuregを呼び出さないといけなそう?

main

  • 1627 : 返り値が1だったので、if文の中を実行
    • 1628 : expandを実行してprocess#1用の領域を確保? expandは8章で扱うので、そのときロジックの詳細を確認する予定
    • 1629 : estaburを実行してu.u_uisa, u.u_uisdを初期化する? estaburは7章で扱うので、そのときロジックの詳細を確認する予定。u.u_uisa, u.u_uisdはsuregで使用されるUISA->r, UISD->rの基底となるアドレス
    • 1630 : icodeをユーザ空間のアドレス0にコピー。icodeはブートストラッププログラム
    • 1635 : returnで670行目に移動

start

  • 670 - 671 : スタックにPS, PCを積む
    • 670 : PSとして170000を積む。これは2bit表記に直すと[15:13]=1111で、それ以外0となる。つまり、現モード, 前モードをユーザモードに設定する。PSについては259P参照
    • 671 : clr命令を使ってPC用の0を積む
  • 672 : rtt命令でicode(ブートストラッププログラム)を実行する
    • rtt命令は、PC, PSを再ロードし割り込み・トラップからリターンする命令(262P参照). つまり、670-671で積んだ値をPS, PCとしてロードするのだと思う(要確認)
    • 1630行目でicodeをユーザ空間のアドレス0にコピーしておいた。PSの設定により現モード, 前モードがユーザモードとなっている。PCは0が入っている。この状態でrttを実行することで、ユーザ空間のアドレス0にコピーされたicodeが実行される

これで/etc/initが実行され、無事UNIXの起動です。長かった……
icodeの詳細や、/etc/initの中身は今度確認することにして、今はとりあえず置いておきます。

終わりに

長くなったので、一旦ここで切ります。
ロジック自体は6章の内容一通り追いましたが、内容が多岐にわたりごちゃごちゃしてきました。
そこで、次回のエントリでは「ブートストラップ起動までに一体何をしているのか」を整理してみようと思います。

ソースコードを読んでいて思うのですが「配列に対して線形探索する」というロジックがいたるところで見受けられます。
「OSといえども意外とわかりやすいアルゴリズムを採用しているのだな」と思うと同時に「当時は色々な規模が小さいから線形探索を採用するのが適切だったのだろうな」とも思います。
扱う規模が大きくなった最近のUNIX, Linuxではどういうロジックになっているのか気になるところです。