Shell自作に必要なOSの知識
プロセスについて
プロセスとは,実行されるプログラムとそれをOSを管理するための情報のセットのようなものです.
プロセス構造体(PCB)という構造体で管理されています.
PCBの保持するデータは以下のようなものです.
PID:自身のプロセスID
state:プロセスの状態.-1なら実行不可,0なら実行可能,正なら停止していることを示す
real_parent:親プロセス
children:子プロセスのリスト
その他OSが管理する情報
余談:Linuxではtask_struct構造体という名前で定義されています.
OSは数あるプロセスの状態を常に管理しており,適切にCPUやメモリなどの資源を割り当てたり実行するプロセスを交代したりすることで疑似的なマルチタスクを可能にしています.
余談:プロセスの状態
「待ち状態」「実行可能状態」「実行状態」の3つがあり,OSは実行可能状態のプロセスに対してリソースを割り当てることで実行状態へと遷移します.実行状態のプロセスは入出力などのイベントによって自ら待ち状態に遷移できます.待ち状態のプロセスは何らかのイベントを待っている状態なのでリソースを割り当てられても動作することができません.
以下,プロセスの状態遷移に関するn回見た図(FEの過去問解説から引用)
https://gyazo.com/f42d4900626d4fff36cae8f1bae96a05
プロセスの生成
プロセスを新しく生成する方法はただ一つ,あるプロセスから子プロセスを発行する(fork)のみです.
原初のプロセスはOSの起動時にinitという名前のプロセスがカーネルによって立ち上げられます.他のプロセスは全て親を辿っていくとinitに到着します.(pstreeコマンドを実行してみるとプロセスの木構造が表示できます)
また,プロセスの発行を行うことができるのはOSのみです.
ユーザプログラムがOSの機能を使いたいときはシステムコールを行う必要があります.
余談: initプロセスにも様々なプログラムが存在し,ユーザは自由に他のinitプログラムをインストールし,設定することができます.現在多くのLinuxディストリビューションはsystemdを採用しています.
親プロセスは子プロセスが終了したら終了ステータスを受け取り資源を解放する(「看取る」と表現される)義務があります.子プロセスは終了するとSIGCHILDシグナルを親プロセスに送信します.
親プロセスはwaitシステムコールを発行することでSIGCHILDを受け取るまで処理を中断して待つことができます.
また,waitpidシステムコールを使うこともでき,オプションを適切に設定することで処理を中断せずに他の処理を実行しつつSIGCHILDを受け取り,そのときに特定の処理を行うといったことも可能です.
signalシステムコールでSIGCHILDを受け取った時の処理を設定(SIG_IGNでも可)することでも処理を中断せずにSIGCHILDを受け取ることができます.
注:シグナルはOSがプロセスに対してイベントの発生を伝える機構です.OSは送信対象のプロセスの処理に割り込み,シグナルを伝えます.例えばCLIでプログラムの実行中にC-cを入力するとプログラムは終了しますが,これはC-cがSIGINTというシグナルを発生させるキーになっており,そのデフォルトの挙動がシグナルを受け取ったプロセスの終了と決まっているからです.
子プロセスが終了したのに親が看取ってくれない場合子プロセスは死んだままメモリに残り続けるゾンビプロセスとなってしまいます.
子プロセスが存在しているまま親プロセスが終了してしまった場合,その子プロセスは孤児プロセスと呼ばれ,initが里親となります.initは定期的にwaitシステムコールを実行しているので全てのゾンビを刈り取ってくれます.
ForkとExec
あるプロセスがforkシステムコールを発行するとOSはそのプロセスの状態(C言語で言えば変数や実行している関数の番地などを含むメモリ状況全て)をコピーして新しいPCBを用意します.子プロセスと親プロセスの違いはPIDのみです.
forkしただけでは子プロセスは親プロセスと全く同じ処理を実行してしまいます.
プロセスはexecシステムコールを使うことで現在の実行状態を破棄し,新しくプログラムを開始することができます.