メインコンテンツまでスキップ

2. タイムシェアリングシステム内のプロセス間通信システム

本節では、タイムシェアリングシステム内でのプロセス間通信を可能にする一連の操作を述べる。[10] の記法に従い、このプロセス間通信機能を IPC と呼ぶ。この IPC の説明の助けとして、タイムシェアリングシステムのモデルを述べ、そのモデルを用いてプロセス間通信操作の使い方を例示する。

モデルとなるタイムシェアリングシステムは二つの部分から成る: モニタ (monitor) とプロセスである。モニタは、あるプロセスが「十分な」時間を使ったときに制御をあるプロセスから別のプロセスへ切り替え、ハードウェア割り込みを処理し、コアとスワップ媒体を管理し、あるプロセスから別のプロセスへの制御の受け渡し (すなわち保護機構) を制御し、プロセスを生成し、休眠中のプロセスを世話し、さらにプロセスに一連の機械拡張操作 (しばしば Supervisor または Monitor Call と呼ばれる) を提供するといった機能を果たす。プロセスは、通常の利用者機能 (利用者プロセス) を果たすほか、タイムシェアリングシステムでは通常スーパバイザ機能と考えられるが本モデルではモニタが果たさない機能 (システムプロセス) も果たす。典型的なシステムプロセスはディスクハンドラまたはファイルシステムである。システムプロセスはディスクハンドラまたはファイルシステムである。システムプロセスはおそらくスーパバイザモード (supervisor mode) で実行を許されており、実際に I/O 命令を実行し、利用者プロセスには許されない他の特権操作を行う。その他の点ではすべて、利用者プロセスとシステムプロセスは同一である。効率上の理由から、システムプロセスはコアに固定されていると考えると役に立つことがある。

保護についての考察は本研究の後半で問題になるが、ここでの関心事ではない。代わりに、すべてのプロセスは決して誤りを犯さない「良い」プロセスであると仮定する。本ノートを読む際に心に留めておく保護構造が必要なら、[1][3][7][8] で開発された capability システムが満足のいくものであろう。

プロセスがモニタに呼び出して実行させることができる操作のうち、六つがプロセス間通信の能力を提供するうえでとくに重要である。

RECEIVE。この操作は、指定されたプロセスが、RECEIVE を実行しているプロセスにメッセージを送ることを可能にする。この操作には四つのパラメータがある: メッセージを待っているポート (定義は後述) —— RECEIVE ポート; メッセージを受け付ける相手側のポート —— SEND ポート; メッセージを受け取るために利用できるバッファの指定; そして転送が完了したときに移る位置 —— 再開位置 (restart location) である。

SEND。この操作は、SEND を実行しているプロセスから指定されたプロセスへメッセージを送る。四つのパラメータがある: メッセージを送る先のポート —— RECEIVE ポート; メッセージを送り出す側のポート —— SEND ポート; 送るメッセージを含むバッファの指定; そして再開位置である。

RECEIVE ANY。この操作は、任意のプロセスが、RECEIVE ANY を実行しているプロセスにメッセージを送ることを可能にする。この操作には四つのパラメータがある: メッセージを待っているポート —— RECEIVE ポート; メッセージを受け取るために利用できるバッファの指定; 再開位置; そしてメッセージを送ったポートを記録できる位置である。

SEND FROM ANY。この操作は、任意のプロセスからメッセージを受け取ることのできるプロセスへ、メッセージを送ることを可能にする。SEND と同じ四つのパラメータを持つ。(この操作が必要となる理由は後ではるかに詳しく説明する。)

SLEEP。この操作は、現在実行中のプロセスが、ある事象の完了を待って自身を休眠させることを可能にする。この操作には任意パラメータが一つあり、待つべき事象を指定する。事象の一例はハードウェア割り込みの到着である。モニタが、上記四つの操作の一つをプロセスが実行した結果として一方的にプロセスを休眠させることは決してない。ただし、上記四つの操作の一つが満たされたときにプロセスが休眠中であれば、そのプロセスは起こされる。

UNIQUE。この操作はモニタから一意番号を得る。

ポートとは、あるプロセスへの (RECEIVE ポート)、またはあるプロセスからの (SEND ポート) 特定のデータ経路であり、すべてのポートにはそれを識別するために用いられる一意のポート番号が対応している。ポートは、あるプロセスから別のプロセスへメッセージを伝送する際に次のように用いられる。通信を望む二つのプロセス A と B を考える。プロセス A はポート M からポート N への RECEIVE を実行する。プロセス B はポート M からポート N への SEND を実行する。モニタはポート番号を突き合わせ、メッセージをプロセス B からプロセス A へ転送する。バッファがプロセス B から完全に送り出されるとすぐに、プロセス B は SEND 操作で指定された位置から再開される。メッセージがプロセス A で完全に受信されるとすぐに、プロセス A は RECEIVE 操作で指定された位置から再開される。プロセスが、他のプロセスと通信するための正しいポート番号をどのようにして手に入れるかは、モニタの関知するところではない —— この問題はプロセス側に委ねられる。

SEND が実行されると、対応する RECEIVE が実行されるまで何も起こらない。モニタ内のどこかに、プロセスと再開位置に対応するポート番号のテーブルがなければならない。テーブルの項目は SEND/RECEIVE の対応が取られるたびに消去される。相応の RECEIVE がしばらく実行されなければ、SEND はしばらくしてタイムアウトし、SEND を実行したプロセスに通知される。RECEIVE が実行されたのに対応する SEND が長い間起こらなければ、RECEIVE はタイムアウトし、RECEIVE を実行したプロセスに通知される。

「未使用の」テーブル項目をタイムアウトさせる仕組みは根本的には重要ではなく、テーブルをゴミ集め (garbage collect) する便利な方法を提供するにすぎない。項目が早すぎるタイミングでタイムアウトしても問題はない。プロセスはいつでも操作を再実行できるからである。ただし、タイムアウト間隔は、操作の再実行を繰り返してもわずかなオーバーヘッドしか生じない程度に長くあるべきである。

RECEIVE ANY は決してタイムアウトしないが、スーパバイザ呼び出し (supervisor call) を使って取り戻すことができる。SEND FROM ANY の結果として生じたメッセージは常に即座に送られ、適切な受信者が存在しなければ破棄される。エラーメッセージは返されず、確認応答が必要ならそれはプロセス側の責任である。SEND と RECEIVE を突き合わせるテーブルがあふれた場合、さらなる SEND と RECEIVE を発したプロセスは、その SEND または RECEIVE がタイムアウトしたのとまったく同じように通知される。

再開位置は、再開位置を指定する操作を実行しているプロセスに対して局所的な疑似割り込み (pseudo interrupt) に対応する割り込み入口である。疑似割り込みを引き起こす事象が起きたときにプロセスが実行中であれば (たとえば、未処理の RECEIVE を満たすメッセージが到着した場合)、その効果は、ちょうどハードウェアがプロセスに割り込みをかけ、制御を再開位置へ移したのとまったく同じである。割り込みが処理された後、プロセスが割り込まれた時点から実行を続けられるだけの情報が保存される。プロセスが休眠中であれば、それは就緒状態にされ、疑似割り込みはプロセスが再び実行されるまで保存され、そのときになって割り込みが許可される。したがって、任意の RECEIVE または RECEIVE ANY メッセージポートを、プロセス割り込み、事象チャネル (event channel)、プロセス同期、メッセージ転送などのために用いることができる。何を望むかは利用者プログラムが決める。

自分が押しつけられているモニタを、上記六つの操作を提供するよう仕向けられることを確信するのは、読者への演習として残しておく —— これらは単なる追加のスーパバイザ呼び出しにすぎないので、ほとんどのモニタで可能である。

一例。モデルとなるタイムシェアリングシステムが、常に実行されているいくつかのプロセスを持つよう初期化されていると仮定する。さらに、これらの常駐プロセスは、普遍的に知られ恒久的に割り当てられたポート<2>をいくつか持っている。常駐して実行されるプロセスのうち二つがロガープロセス (logger-process) とテレタイプ走査プロセス (teletype-scanner-process) であると仮定する。テレタイプ走査プロセスは最初に実行を開始したとき、ハードウェアのテレタイプ走査装置からの割り込みを待って自身を休眠させる。ロガープロセスは当初、よく知られた恒久的な SEND ポートと RECEIVE ポートを介してテレタイプ走査プロセスからメッセージが来るのを待って自身を休眠させる。テレタイプ走査プロセスは、テレタイプ番号で索引付けられたテーブルを保持し、各項目には、そのテレタイプからあるプロセスへ文字を送るために使う一対のポート番号と、あるプロセスからそのテレタイプ向けの文字を受け取るために使う一対のポート番号が入っている。文字が到着し (テレタイプ走査プロセスが起こされ)、そのプロセスがそのテレタイプの項目を一つも持っていなければ、モニタから (UNIQUE を用いて) 一対の一意番号を得て、ロガープロセスが未処理の RECEIVE を持っていると分かっているポートを用いて、この一対の番号を含むメッセージをロガープロセスへ送る。走査プロセスはまた、その一対の番号をテレタイプテーブルに登録し、その文字および今後このテレタイプから来るすべての文字を、二番目の番号のポートから一番目の番号のポートへ送る。走査プロセスはさらに、テレタイプ出力のためにロガープロセスが使う二番目の一対の一意番号をロガープロセスに渡し、これらのポート番号を用いて RECEIVE を行わなければならない。ロガープロセスが走査プロセスからメッセージを受け取ると、SDS 940 TSS [6] の利用者が executive<3> と呼ぶものの複製を起動し、その executive の複製にポート番号を渡して、この executive プロセスもこれらのポートを用いてテレタイプへの入出力を行えるようにする。ロガープロセスが利用者からジョブ番号とパスワードを得たいのであれば、ポート番号を executive に渡す前に、一時的にそれらを用いて利用者と通信することができる。走査プロセスは、番号がある時点で executive の一つの複製にしか渡されない限り、特定のテレタイプに対して常に同じポート番号を使うことができる。

あるプロセスから別のプロセスへポートを渡す行為と、あるプロセスから別のプロセスへポート番号を渡す行為とを区別することが重要である。先の例で、特定のテレタイプから来た文字がテレタイプ走査プロセスによってロガープロセスまたは executive プロセスのいずれかへ送られる場合、SEND ポートは常にテレタイプ走査プロセスに留まり、RECEIVE ポートはロガープロセスから executive プロセスへ移動する。一方、SEND ポート番号はロガープロセスと executive プロセスの間で渡され、RECEIVE を行うプロセスが正しい SEND ポートから RECEIVE を行えるようにする。決定的に重要なのは、あるプロセスがポートを他のプロセスへ譲渡したなら、最初のプロセスはもはやそのポートを使ってはならないということである。これを強制する仕組みを追加することもできよう。[9] の保護オブジェクトシステム (protected object system) がそのような仕組みの一つである。この仕組みを用いれば、SEND を実行するプロセスは SEND ポートの capability を必要とし、この SEND ポートの capability はシステム内の任意の時点でただ一つしか存在しないことになる。RECEIVE を実行するプロセスは RECEIVE ポートの capability を持つことを要求され、この RECEIVE ポートの capability も任意の時点でただ一つしか存在しないことになる。このような保護機構がなければ、ポート番号が明示的に渡されることがなくても、単に各プロセスが重ならない時間にそのポートを使うことによって、ポートは暗黙のうちにあるプロセスから別のプロセスへ移動してしまう。

もちろん、保護オブジェクトシステムが利用できるのであれば、伝送が行われる前に二つのポート番号を指定する必要は実際のところまったくない。あるプロセスが既存の RECEIVE ポート番号を知っているという事実は、そのプロセスがそのポートへ送る権利を持つことの一応の証拠 (prima facie evidence) と見なせよう。そうなると、RECEIVE ポートと RECEIVE ANY ポートの違いは、特定のポート番号が何部だけ配られたかだけに依存することになる。ネットワーク内のすべての自律的タイムシェアリングシステムがこの保護機構を採用すると仮定できるなら、この考え方に基づくシステムは明らかに本稿で述べるものより望ましいであろう。この仮定が置けないのであれば、二つのポート番号の双方を要求するほうが実際的と思われる。

ここで述べているプロセス間通信システム (IPC) では、二つのプロセスが通信を望むとき、接続は彼ら自身が設定し、双方に都合のよい方法で自由に行ってよいことに注意されたい。たとえば、ポート番号を交換してもよいし、一方のプロセスがすべてのポート番号を選び、もう一方にどれを使うかを指示してもよい。ただし、タイムシェアリングシステムの特定の実装においては、システムの構築者がプロセスによる SEND と RECEIVE の実行を制限し、ポートとポート番号の恣意的な受け渡しを禁じ、代わりにモニタ (または何か他の特別なプログラム) を呼び出してこれらの機能を行わせることを要求するかもしれない。

本 IPC のフロー制御は、受信側が RECEIVE を実行するまで、あるプロセスからの SEND に起因するデータ伝送を決して開始しないという単純な方法で提供される。もちろん、プロセスがある送信を止めるべきであるとか、空間を割り当てるべきであるといったことを示唆するメッセージをプロセス間でやり取りしてもよい。

一般に、よく知られ恒久的に割り当てられたポートは、RECEIVE ANY と SEND FROM ANY を介して用いられる。恒久的なポートはたいていプロセスの起動に使われるので、それらを介して送られるデータはわずかである。プロセスが実行中 (おそらくは休眠中) で未処理の RECEIVE ANY を持っていれば、その受信ポート番号を知っている任意のプロセスが、ロガーを経由することなくそのプロセスと話せる。これはローカルのタイムシェアリングシステム内では明らかに不可欠であり、資源共有という理想を達成しようとするなら、より一般的なネットワークでも非常に有用と思われる。たとえば資源共有ネットワークでは、すべてのサイトのサブルーチンライブラリ内のプログラムが、恒久的に割り当てられポート番号のよく知られたポート上に常に未処理の RECEIVE ANY を持っているかもしれない。そうすれば、行列操作ハードウェアのような特定のネットワーク資源を使うために、ネットワーク内のどこかで実行中のプロセスが、逆行列を求めるサブルーチンへ、反転すべき行列と結果を返すために使うポート番号を含むメッセージを送ることができる。

さらに別の例で FORTRAN コンパイラの利用を示す。利用者が自分のテレタイプの前に座り executive に接続される仕組みはすでに説明した。そこから先へ進む。利用者は executive に対して入力と出力を行い、executive は SEND と RECEIVE を行っている。やがて利用者が RUN FORTRAN とタイプすると、executive はモニタに FORTRAN コンパイラの複製の起動を求め、executive がテレタイプと話すために用いていたポート番号を起動パラメータとして FORTRAN に渡す。(少なくとも概念上は、FORTRAN にはテレタイプから文字を受け取るためのポートと、テレタイプへ文字を送るためのポートが渡される。) FORTRAN はもちろんこれらのパラメータを期待しており、指示されたポートを介して SEND と RECEIVE を行い、利用者がどの入力ファイルと出力ファイルを使いたいのかを利用者から探り出す。FORTRAN は利用者に INPUT FILE? と表示し、利用者は F001 と応答する。次に FORTRAN は、何かすることを待って休眠しているファイルシステムプロセスへメッセージを送る。メッセージはよく知られたポートを介して送られ、ファイルシステムに F001 を入力用に開くよう求める。メッセージにはまた、ファイルシステムプロセスが返信に使える一対のポート番号が含まれている。ファイルシステムは F001 を調べ、入力用に開き、オープンファイルテーブルにいくつか項目を登録し、FORTRAN がそのファイルを読むために使えるポート番号を含むメッセージを FORTRAN へ送り返す。出力ファイルについても同じ手順を踏む。コンパイルが完了すると、FORTRAN はテレタイプのポート番号 (およびポート) を、FORTRAN からのメッセージを待って休眠していた executive へ返し、その後 FORTRAN は自身を停止する。ファイルシステムプロセスは、なすべきことが他になければ休眠に戻る<4>。

繰り返すが、ファイルシステムプロセスは、ファイルシステムの利用者に使い終わったポート番号を返してもらえるなら、繰り返し使う少数のポート番号を保持しておくことができる。もちろん、このポート番号の集まりがやがて底を突いたときには、ファイルシステムはモニタから新しい一意番号をいくつか得ればよい。