こんにちわ、んだです。
前回までは、計算機システム概論をみながら学習メモを書いていました。
ざっとOSの概観については理解できたので、続いては詳細について書籍を読みつつ、理解を深めて行きたいと思います。
今回は、プロセス管理の基礎編と代して、読書メモをとっていこうと思います。
プロセスID
まずは、プロセスID(pid)から整理していきます。
プロセスには、一意な識別子としてプロセスIDが与えられています。
しかし、DBのidのような一意ではなく「プロセスIDは一意」というのは、ある時点での一意。
つまり、pid999はある時点では一つしかシステムに存在しませんが、時間が経てば再利用されて別のプロセスに付与されます。
しかし、プロセスが消滅したらすぐに別のプロセスに即行で付与するようには設計されていないです。
安全な設計ですな。
initプロセス
initプロセスというプロセスがあり、システムを起動した時に最初に起動するプロセス。
initプロセスのpidは1。
このinitプロセスが、子プロセスを起動して、さらにそのプロセスが子プロセスを起動するという流れでシステムが起動していく。
プロセス階層と親プロセスID
プロセスは、親プロセス、子プロセスという階層構造を持っています。
initプロセスを例外として、すべてのプロセスは他のプロセスから起動されるので、必ず親プロセスが存在します。
自身のプロセスID(pid)と親プロセスID(ppid)を保持しています。
プロセスのライフサイクル
では、続いてプロセスのライフサイクルを見ていきましょう。
↑はじめてのOSコードリーディングを模写
ざっくりと流れを追うと、
- 親プロセスがfork()を発行し、子プロセスを生成
- 子プロセスに制御が移り、子プロセスでexec()
- 最後に制御が親プロセスに戻る
という流れですね。
では、順に詳細を見ていきましょう。
fork
ソースコード
unix-v6/ken/sys1.c at master · hephaex/unix-v6 · GitHub
ざっくりとやっていることを把握すると
① 空きエントリを見つける
for(p2 = &proc[0]; p2 < &proc[NPROC]; p2++) if(p2->p_stat == NULL) goto found;
見つけたら新しいプロセスを生成
found: if(newproc()) { u.u_ar0[R0] = p1->p_pid; u.u_cstime[0] = 0; u.u_cstime[1] = 0; u.u_stime = 0; u.u_cutime[0] = 0; u.u_cutime[1] = 0; u.u_utime = 0; return; }
的な感じですね。
メモリイメージをコピーしている、現在のプロセスを分身させているなど書籍によって、説明の仕方はそれぞれですが、やっていることは単純ですね。
exec()
execはforkに比べると、ソースコード上では色々やっていてムズカシイ。ので、ざっくりと雰囲気だけを理解します
execとは
execでは
fork するだけでは同じプログラムコードを持つ複数のプロセスができるだけであり,別のプログラムコードを持つプロセスを作ることはできない。そこで exec システムコール群を用いて,現プロセスを別のプログラムコードに置き換えることができる
forkでは、プロセス生成したわけでしたが、内容的には単に複製しただけでしたね。
execでブロックデバイスから子プロセスのプログラムをメモリにロードし、実行するという感じです。
forkがプロセスを分身されているとしたら、execは変身させている
execファミリー
ちなみに、引用文の中で「exec システムコール群」と書いているのは、exec機能には、execlやexecveなどファミリーがいるからです。
wait
順番が前後しましたが、waitシステムコールを見ていきましょう。
ソースコード
unix-v6/ken/sys1.c at master · hephaex/unix-v6 · GitHub
まずは、procの配列の中から、子プロセスを見つけます。
for(p = &proc[0]; p < &proc[NPROC]; p++) if(p->p_ppid == u.u_procp->p_pid) {
次に子プロセスの状態がゾンビ状態かどうかを確認します。
if(p->p_stat == SZOMB) {
ゾンビ状態だった場合には、proc構造体を初期化
p->p_stat = NULL; p->p_pid = 0; p->p_ppid = 0; p->p_sig = 0; p->p_ttyp = 0; p->p_flag = 0;
孤児プロセス
ちなみに、もし親のプロセスがwaitを発行する前に終了した場合ですが、この場合ゾンビのままにシステムのメモリに居続けてしまいます。
この問題については、initプロセスが定期的にwaitを発行することでゾンビだらけになるのを避けています。
exit
では最後に、exitを見ていきましょう。
unix-v6/ken/sys1.c at master · hephaex/unix-v6 · GitHub
exitでは、だいたい以下のようなことをやっています。
そのプロセスが開いていたファイルをクローズ
for(q = &u.u_ofile[0]; q < &u.u_ofile[NOFILE]; q++) if(a = *q) { *q = NULL; closef(a); }
テキストセグメントの解放
xfree();
ゾンビ化!
q->p_stat = SZOMB;
で、↓ここの処理が、先ほどの孤児プロセスが適切に処理される所以ですね。
for(p = &proc[0]; p < &proc[NPROC]; p++) if(q->p_ppid == p->p_pid) { wakeup(&proc[1]); wakeup(p); for(p = &proc[0]; p < &proc[NPROC]; p++) if(q->p_pid == p->p_ppid) { p->p_ppid = 1; if (p->p_stat == SSTOP) setrun(p); } swtch(); /* no return */ } q->p_ppid = 1; goto loop;
proc[1]、pidの1番目はinitプロセスです。
ここで、initプロセスと親プロセスをどちらも起こします。もし、親プロセスがいなかったら、q->p_ppid = 1;
とinitプロセスを親にしていることがわかります。
まとめ
さて、今回はプロセス管理の基礎編ということで、fork,exec,wait,exit のことなどをざっと追っかけてみました。
親なしのプロセスについては、initプロセスを親にしてシステムを安定稼働させているところなど、美しいですね。
OSまわりは、こういった美しさを感じれるところが、かなり楽しいです。
ということで、次回は、プロセスについてもうちょい周辺知識を整理していきたいと思います。
僕から以上。あったかくして寝ろよー
参考