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 */
- coreをクリア
- clockをオン
- proc[0](for scheduler)の設定
- 初期化関数の呼び出し(cinit( ), binit( ), iinit( ))
- 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周りを見ようと思います。