UNIX 7th code reading - main

はじめに

今回は起動時に実行されるmain( )を見ていきます。

あまり細かいところは追っていかないつもりです。処理を関数に切り出しているところとか、cの文法が変わって変更されているところとかには触れません。論理的・設計的に変更のある箇所だけ注目していきます。

main( )を呼び出すstart( )はハード依存が高く、また、アセンブラで記述されているので、また別の機会に……(ぱっと見た感じ6thと大きく変わりはないようですが)

main( )

main( )のコードはこちらを参照。

http://www.tamacom.com/tour/kernel/unix/S/88.html#L30

まずはコメントを見てみます。

  14 /*
  15  * Initialization code.
  16  * Called from cold start routine as
  17  * soon as a stack and segmentation
  18  * have been established.
  19  * Functions:
  20  *      clear and free user core
  21  *      turn on clock
  22  *      hand craft 0th process
  23  *      call all initialization routines
  24  *      fork - process 0 to schedule
  25  *           - process 1 execute bootstrap
  26  *
  27  * loop at low address in user mode -- /etc/init
  28  *      cannot be executed.
  29  */
  1. coreをクリア
  2. clockをオン
  3. proc[0](for scheduler)の設定
  4. 初期化関数の呼び出し(cinit( ), binit( ), iinit( ))
  5. proc[0]をforkしてproc[1](bootstrap)を生成


と、6thのときとやっていることは同じようです。

nice値の初期値

core, clockの初期化の次にproc[0]の設定を行います。

  38         proc[0].p_addr = ka6->r[0];
  39         proc[0].p_size = USIZE;
  40         proc[0].p_stat = SRUN;
  41         proc[0].p_flag |= SLOAD|SSYS;
  42         proc[0].p_nice = NZERO;
  43         u.u_procp = &proc[0];
  44         u.u_cmask = CMASK;

ここで気になるのがプロセスの優先度を決定するのに使用するNice値の設定。6thでは初期化を行っていない(=0. 直前にcoreを0クリアしているから)のですが、7thでは20(=NZERO)を設定しています。

newproc( )を見ると、Nice値は親プロセスの値を継承するようになっています。

 466         rpp->p_nice = rip->p_nice;

この20という値が7thのデフォルト値なのでしょうか?

7thのNice値についてはまだわかっていないことが多いので、とりあえずはツイートをメモ代わりに貼っておくことでお茶をにごします。


あと、"nice"という名前の由来について教えてもらいましたので、併せて紹介します。教えていただいた方々ありがとうございます。


追記:20について教えていただきました。ありがとうございます。

umask

u.u_cmaskは7thから入ったu構造体の要素で、プロセスごとにファイルを新規作成したときのデフォルトパーミッションを設定できます。この値はumaskシステムコールで設定できます。最近のLinuxでもumaskコマンドでおなじみですよね。

プロセス毎にルートディレクトリの設定

proc[0]の設定を行った後にclockのスタートと、デバイスの初期設定を行っています。

  51         clkstart();
  52         cinit();
  53         binit();
  54         iinit();
  55         rootdir = iget(rootdev, (ino_t)ROOTINO);
  56         rootdir->i_flag &= ~ILOCK;
  57         u.u_cdir = iget(rootdev, (ino_t)ROOTINO);
  58         u.u_cdir->i_flag &= ~ILOCK;
  59         u.u_rdir = NULL;

気になるのはu.u_rdir. 7thからプロセスごとにルートディレクトリを設定できるようになっています。

ファイルパスとinodeをマッピングするnamei( )を見ると、探索の最初にルートディレクトリを設定している箇所で、u.u_rdirが非NULLならばその値をルートディレクトリとしていることがわかります。

  37         dp = u.u_cdir;
  38         if((c=(*func)()) == '/')
  39                 if ((dp = u.u_rdir) == NULL)
  40                         dp = rootdir;

また、newproc( )を見るとu.u_rdirが設定されている場合、そのinodeの参照カウンタをインクリメントしています。ルートディレクトリにしているプロセスが存在するのに、そのinodeを削除してしまうのは問題ですからね。

 485         u.u_cdir->i_count++;
 486         if (u.u_rdir)
 487                 u.u_rdir->i_count++;

ところで、この「プロセスごとにルートディレクトリを設定できる」ことの恩恵がよくわかっていないのですが……

後、細かい話ですがデフォルトのルートディレクトリinode numberが、6thでは1だったのが2(=ROOTINO)に変更になっています。7thでは1が何に使われるようになったかはまだ理解していません。

newproc( )

初期設定が終わるとproc[0]はnewproc( )を呼び出して子プロセス(proc[1] = boot program用プロセス兼親を亡くしたZOMBIEプロセスを埋葬するプロセス)を生成します。

newproc( )はこちらを参照。

http://www.tamacom.com/tour/kernel/unix/S/96.html#L426

ルートディレクトリの参照カウントインクメントをしている話は上で書いたので省略。

process swtichの仕様変更?

process switch(context switch)の仕様が6thから変わっているような気がします。

 503         if (save(u.u_ssav)) {
 504                 sureg();
 505                 return(1);
 506         }

6thではswtch( )によって選択されたプロセスは、savu( )(6thでいうsavu( ))を実行した関数の呼び出し元にreturnするのですが、7thでは上記コードを見るとsave( )関数を呼び出したところに戻るように変更された気がします。

swtch( )で選択されたプロセスの流れを絵で描くと、6thと7thではこのような違いがあるのではないかと思っています。

save( )を実行したプロセスはsave( )から0が返ってくる(save( )の最後にclr r0している)ので、newproc( )の処理を継続します。一方swtch( )で選択されたプロセスは、swtch( )からsave( )実行した箇所にreturn 1で戻ってくるのでsureg( )(PAR, PDRの再設定)をしてからnewproc( )呼び出し元にreturn 1で戻るようです。

save( )とresume( )(6thでいうretu( ))はアセンブラで書かれているので、真面目に追うのは別の機会にします。少し眺めたところ、6thではr5とr6しか保存していなかったのが、7thではr1からr6までを保存するようになったということだけはわかりました。

run queue

新規プロセスの初期設定が終わるとsetrq( )が実行されています。

 527         setrq(rpp);

7thからrun queueが追加されたようです。setrqはrun queueの後尾にプロセスを追加する関数です。

run queueは実行可能状態なプロセスのリストです。swtch( )で実行可能なプロセスの中から一番優先度の高いものを選ぶときにもrun queue(runq)を使用しているのがわかります。

 388         for(p=runq; p!=NULL; p=p->p_link) {
 389                 if((p->p_stat==SRUN) && (p->p_flag&SLOAD)) {
 390                         if(p->p_pri < n) {
 391                                 pp = p;
 392                                 pq = q;
 393                                 n = p->p_pri;
 394                         }
 395                 }
 396                 q = p;
 397         }

6thでは全プロセスを一律proc[ ]で管理していて、上記箇所でもproc[ ]を順に辿って実行可能なもののみを対象として処理をしています。

run queueを使うようになったのはprocess swtich処理速度を向上させたかったのと、総プロセス数に対してscalabilityを上げたかったからではないかと思います。

6thのようなやり方は、総プロセス数が少ないときには問題がないのですが、その数の桁が1つ2つと上がっていくと無駄な処理が増えて性能ネックになっていくと思われます。

疑問:上記コードの389行目で、なぜstateがSRUNかどうかをチェックしている? SRUNのもののみがrun queueに入るのではないのか?

SSWAPフラグの設定

run queueへの追加が終わると、最後に新たに生成したプロセスにSSWAPフラグをセットして0でreturnしています。

 528         rpp->p_flag |= SSWAP;
 529         return(0);

SSWAPフラグを設定しているのは、6thのときと同じで(Lions本の374P参照)データセグメントがコアにあってテキストセグメントがスワップアウトされている場合、テキストセグメントをスワップインする術がない(sched( )が、常にデータセグメントよりも先にテキストセグメントをスワップインするという仕様のため)ので、一旦スワップアウトしてしまおう、ということではないかと思います。

ただ、6thではmalloc( )に成功してコア内の領域を確保できた場合は、このタイミングではSSWAPフラグをセットしておらず……

また、7thでもどのタイミングでスワップアウトされるのかはまだ確認できておらず……

もう少しこの周りの調査が必要そうです。

コード中のコメントにも書かれていますが、newproc( )を実行したプロセスには0がreturnされて、新たに生成されたプロセスには1がreturnされるというのは6thと同じ仕様のようです。ただし、main( )のところで書いたように、新規プロセスがnewproc( )呼び出し元までに戻るルートが若干異なっているようです。

終わりに

6thと7thの差分が小さいということは前回調べてわかっていたのですが、真面目に変更点を追っていくと結構労力がかかるものですね。

「ここの変更見落としているよ!」なんてツッコミがありましたらぜひご指摘をお願いします。

次回はprocess swtichを調査するかfile system周りを見ようと思います。