2.4. プロセス管理

4.4BSD はマルチタスク環境をサポートしています。 実行されたそれぞれのタスクまたはスレッドは プロセスと呼ばれています。 4.4BSD のプロセスのコンテキストは、 アドレス空間の内容とランタイム環境を含むユーザレベルの状態と、 スケジューリングのパラメータやリソース制御、識別情報を含む カーネルレベルの状態から構成されています。 コンテキストにはカーネルがプロセスにサービスを 提供する際に使用するすべてが含まれています。 ユーザはプロセスを生成し、その実行を制御し、 プロセスの実行状態が変化したときに通知を受け取ることができます。 すべてのプロセスにはプロセス ID (PID) と呼ばれる一意の値が割り当てられます。 この値はカーネルがユーザに実行状態の変化を報告するときにプロセスの身元を確認したり、 ユーザがシステムコールを実行するために参照する際に使用されます。

カーネルは他のプロセスのコンテキストを複製してプロセスを生成します。 新しく生成されたプロセスを 元の親プロセス子プロセスと呼びます。 プロセス生成時に複製されたコンテキストは、ユーザレベルのプロセスの実行状態と カーネルが管理しているプロセスのシステム状態の両方を含んでいます。 カーネルの状態に関する重要な構成要素については、4 章で解説しています。

Figure 2-1. プロセスのライフサイクル

Figure 2-1ではプロセスのライフサイクルを示しています。 プロセスは fork システムコールを用いて、 元のプロセスのコピーとして新しいプロセスを生成することができます。 fork は呼び出されると 二度戻ります。一方は親プロセスに子プロセスのプロセス ID を返し、 もう一方は子プロセスに 0 を返します。 プロセスの親子関係はシステム上のプロセスの組に階層構造をもたらします。 新しく生成されたプロセスはファイル記述子やシグナルハンドラの状態、 メモリレイアウトのような親が持っているリソースすべてを共有します。

親のコピーとして生成された新しいプロセスであっても、 別のプログラムをロードし実行することでより便利で特有の動作をすることもできます。 プロセスは execve システムコールを用いることで、 別のプログラムのメモリイメージで自分自身を上書きして、 新しい引数の組をその新しく作成したイメージに引き渡すことができます。 引数の一つは、システムで認識されるフォーマット (バイナリ実行ファイルや指定されたインタプリタプログラムの起動を促すファイル) をしたファイルの名前です。

プロセスは exit システムコールを実行することで、 親プロセスに 8 ビットの exit ステータスを送信して終了することができます。 もしプロセスが 1 バイト以上の情報を親プロセスに伝えたい場合には、 パイプやソケット、または仲介ファイルを用いて プロセス間通信チャネルをセットアップする必要があります。 プロセス間通信については 11 章で大きく取り上げています。

プロセスは、wait システムコールを用いて 子プロセスのいずれかが終了するまで実行を中断することができ、 wait システムコールは終了した子プロセスの PID と終了ステータスを返します。 親プロセスは、子プロセスが終了または異常終了したときのシグナルによる通知のされ方を調整できます。 wait4 システムコールを使用することで、 親プロセスは子プロセスの終了を引き起こしたイベントについての情報と、 子プロセスが生存期間の間に消費したリソースについての情報を取得することができます。 もし親プロセスが先に終了したためにリソースがオーファンド (親のない状態) になってしまった場合、カーネルは init という特別なプロセスにその子プロセスの 終了ステータスが渡されるよう調整します。これについては 3.1 節および 14.6 節を参照してください。

5 章では、カーネルがどのようにしてプロセスを生成し 消滅させるかについての詳細を述べています。

プロセスはプロセス優先度というパラメータに従って 実行をスケジュールされます。 この優先度はカーネルベースのスケジューリングアルゴリズムによって管理されています。 スケジューリングの優先度全体に重みづけする特別なパラメータ (nice) によって、ユーザはプロセスの実行優先度に影響を与えることができますが、 カーネルのスケジューリングポリシに従って、基本となる CPU リソースを共有する必要があります。

2.4.1. シグナル

システムはプロセスに送ることができる シグナルのセットを定義しています。 4.4BSD におけるシグナルはハードウェア割り込みをモデルとしています。 プロセスはユーザレベルのサブルーチンをシグナルが送られるべき ハンドラとして指定できます。 シグナルが発生して、それがハンドラによって捕捉されている間は さらなるシグナルの発生はブロックされます。 シグナルを捕捉することで、現在のプロセスのコンテキストを保存し、 ハンドラを実行するための新たなコンテキストを構築することになります。 シグナルがハンドラに伝わると、そのハンドラはプロセスをアボートさせたり、 (おそらく大域変数に値を設定した後で) 実行中のプロセスに戻ることもできます。 ハンドラから戻ると、そのシグナルはブロックされなくなり、 発生する (そして捕捉される) ことが再び可能になります。

また、プロセスはシグナルを無視することや、 カーネルで定義されているデフォルトの動作を行なうように指定することができます。 ある種のシグナルのデフォルトでの動作はプロセスを終了させることです。 このような場合の終了は、事後のデバッグに使用できるようにその時のプロセスのメモリイメージを含んだ コアファイルの生成を伴います。

いくつかのシグナルは捕捉することも無視することもできません。 そのシグナルは、暴走したプロセスを停止させる SIGKILL や、 ジョブコントロールシグナルである SIGSTOP です。

プロセスはシグナルを特別なスタックに伝達させることも選択できます。 これにより、洗練されたソフトウェアスタック操作が可能です。 たとえば、コルーチンをサポートしている言語では それぞれのコルーチンにスタックを提供する必要があります。 その言語の実行システムは、4.4BSD で提供される単一のスタックを分割することで、 これらのスタックを割り当てることができます。 もしカーネルが独立したシグナルスタックをサポートしていない場合、 それぞれのコルーチンに割り当てられた領域を シグナルの捕捉に必要な分だけ拡張しなければなりません。

すべてのシグナルは、同じ優先度を持っています。 もし複数のシグナルが同時に未処理となっている場合は、 シグナルの届く順序は実装に依存します。 シグナルハンドラは、そのシグナルがブロックされるようにして実行しますが、 他のシグナルは依然発生可能です。 このメカニズムにより、プロセスは コードのクリティカルな部分を特定のシグナルの発生に対して保護することができるのです。

シグナルの設計と実装の詳細は、4.7 節で解説しています。

2.4.2. プロセスグループとセッション

複数のプロセスを組織してプロセスグループが作られます。 プロセスグループは端末へのアクセスの制御や 関係プロセスの集合にシグナルを送る手段を提供するのに使用されます。 プロセスは親プロセスからプロセスグループを引き継ぎます。 プロセスが自分自身または自分の子孫のプロセスグループを変更できるようにする メカニズムをカーネルは提供しています。 新しいプロセスグループを作成することは簡単です。 新しいプロセスグループの値はたいてい 作成したプロセスのプロセス ID となります。

プロセスグループにおけるプロセスの集合は、ジョブと呼ばれることがあり、 シェルのような高レベルのシステムソフトウェアで操作されます。 シェルによって生成されるよくある類のジョブは、いくつかのプロセスをパイプでつないだ パイプラインで、最初のプロセスの出力が 2 番目の入力となり、 2 番目の出力が 3 番目の入力となり、4 番目も同様に… というものです。 シェルはパイプラインの各段階においてプロセスを fork して、 これらすべてのプロセスを別個のプロセスグループにおくことで このようなジョブを生成します。

ユーザプロセスは、単独のプロセスに送る場合と同様に プロセスグループのそれぞれのプロセスにまとめてシグナルを送ることができます。 指定されたプロセスグループに属するプロセスが そのプロセスグループに影響するソフトウェア割り込みを受け取ると、 それによってプロセスグループは実行を中断や再開をしたり、 割り込みを受けたり、終了させられたりします。

端末にはプロセスグループ ID が割り当てられています。 この ID は、端末に関連づけられたプロセスグループの ID が通常セットされます。 ジョブコントロール機能を持つシェルは、同じ端末に関連づけされた プロセスグループを多数作成することができます。 その端末は、これらのプロセスグループに属するプロセスの制御端末となります。 プロセスは、端末のプロセスグループ ID とそのプロセスのプロセスグループ ID が一致したときのみ、 制御端末を記述子から読むことができます。 もしプロセスグループ ID が一致していなければ、 プロセスがその端末から読み込もうとする際にブロックされます。 端末のプロセスグループ ID を変更することで、 シェルはいくつかの異なるジョブの間で端末を調停することができます。 この調停はジョブコントロールと呼ばれ、 プロセスグループとともに 4.8 節で解説しています。

関連するプロセスの集合をプロセスグループとしてまとめることができるのと同じように、 プロセスグループの集合をセッションとしてまとめることができます。 セッションのおもな用途は、デーモンプロセスとその子プロセスに対して隔離した環境を作り出したり、 ユーザのログインシェルとそのシェルが作り出すジョブをひとまとめにすることです。