10.プロセス管理

UNIX や windows のような、ほとんどの一般OS にとってプロセスはアドレス空間やその他のリソースを取り扱うリソースコンテナとして見ることができる。
この章ではまず ACE_Process ラッパークラスを用いてプロセスを作る方法と、子プロセスの終了管理について説明する。続いて、プロセスレベルのロックを取り扱うミューテックスを使った、並列プロセスでの共有システムリソースの保護方法について学ぶ。そして最後にリアクタフレームワークと統合された、高度なプロセス管理クラスについてみていく。

10.1 新規プロセスの生成

ACE は ACE_Process ラッパークラスによって、プロセスの生成とその終了管理についての詳細を隠蔽する。一つの ACE_Process オブジェクトが一つの子プロセスを表し、いくつかのオプションを提供する。
UNIX を使ってきた人にとっては spawn() メソッドは fork() システムコールよりも system() 関数に似ていると思えるだろう。ACE_Process に fork() を使わせることもできるが、多くの場合は ACE_OS::fork() の方が単純なプロセスのフォークには向いている。
ACE_Process クラスに子プロセスを生成させるには次の2ステップが必要となる。
  1. 子プロセスのプロパティを設定する ACE_Process_Options オブジェクトを生成する。
  2. ACE_Process::spawn() メソッドを使って子プロセスを生成する。
ここで例に挙げるプログラムは、マスター側(Manager クラスを使用)が子プロセスを生成し、スレーブ側(Slave クラスを使用)の作業が終了するまで待機するものである。これは同じプログラムを実行し、コマンドラインオプションによってどちらのモードで動作するかを判別する。
Manager クラスはパブリックメソッドを一つだけ持ち、その中で子プロセスの設定を行い、プロセスを生成し、その終了を待つ。コードは p.221 のようになる。
ACE_Process_Options オブジェクトが新プロセスのオプションを管理する。これは複数のプロセス生成の時に一つの ACE_Process_Options オブジェクトを使い回すこともできるということだ。
spawn() メソッドは UNIX では execvp() を使い、windows では CreateProcess() を使う。
子プロセスが無事に生成されると、親プロセスは wait() を使ってその終了を待つ。wait() は子プロセスの終了コードを収集し、UNIX ではプロセスのゾンビ化を防ぐ。windows では CreateProcess() で作られたプロセスおよびスレッドのハンドルをクローズする。
それでは子プロセスのオプションを設定するところを詳しく見ていこう。p.222 にコードがある。
ACE_Process::spawn() メソッドは子プロセスの生成前に ACE_Process::prepare() フックメソッドを呼び出す。このメソッド内では新プロセスへ与えるオプションの調査と変更が可能となっている。また、ここはプラットフォーム依存のオプションを設定するのに丁度良い場所である。
まずはコマンドラインにプログラム名と一つの引数(スレーブ側で動作させるため)を設定する。そして子プロセスの標準入力、標準出力、標準エラー出力を設定する。これらのうち前者二つは両プロセスで共有するが、標準エラー出力のみは output.dat というファイルになる。また、ここで子プロセスの環境変数をどうやって設定するのかを見ることができる。
windows 以外の場合には、さらに実効ユーザIDを設定する必要がある。これについては後で詳しく見ていく。
prepare() フックメソッドが 0 を返すと、ACE_Process::spawn() はプロセスの生成を継続する。一方、メソッドが -1 を返した場合にはプロセスの生成は中断される。
p.223-224 には Slave クラスの内容が載っている。スレーブプロセスが生成されると、その doWork() メソッドが呼び出される。このメソッドでは
スレーブが終了すると、マスターはスレーブの出力ログを画面へ出力する。スレーブ側のエラー出力は output.dat に入っているが、マスターはそのファイルハンドルを持っているので、それを利用してダンプを行う。ファイルハンドルオブジェクトは両プロセス間で共有されている。これを利用し、ファイルの先頭まで巻き戻してから内容を取得してディスプレイに表示している。

10.1.1 セキュリティパラメータ

先に述べた通り、ACE_Process_Options によって、実効、実ユーザIDおよびグループIDを設定することができる。p.225 にあるのが、実効ユーザID を nobody ユーザのものに設定している例である。もちろん、読者のシステムで nobody が存在しない場合には何か別の有効なユーザ名を指定する必要がある。
このコードは UNIX ライクなシステムでしか有効でないのは注意を要する点である。windows システムの場合には、これらの代わりに SECURITY_ATTRIBUTES を取り扱うために set_process_attributes() および get_process_attributes() メソッドが利用できる。

10.1.2 その他の ACE_Process フックメソッド

prepare() フックメソッドの他に、ACE_Process は二つのフックメソッドを持っている。これらをオーバーライドすることでプロセスの処理をカスタマイズすることができる。
  1. parent(pid_t child) は親プロセスが fork() あるいは CreateProcess() した直後に呼ばれる。
  2. child(pid_t parent) は fork() が成功した後、exec() が呼ばれる直前に呼ばれる。この時点では環境変数、標準ハンドル、作業フォルダは設定されていない。このメソッドは windows プラットフォームでは機能しない。なぜならば、その場合には fork() して exec() するという機構ではないからだ。

10.2 ACE_Process_Manager の利用

比較的シンプルな ACE_Process ラッパーによって、ACE は呼び出し一つでプロセスの生成や複数プロセスの終了待ちをしてくれる理想的なプロセスマネージャ ACE_Process_Manager を持っている。
また、イベントハンドラを登録しておくことで、プロセスの終了時にコールバックしてくれるようにもできる。

10.2.1 プロセスの生成と終了

ACE_Process_Managerクラスの spawn() メソッドは、ACE_Process と同様に利用できる。 ACE_Process_Options オブジェクトを作成し、spawn() メソッドに渡せばよい。 さらに ACE_Process_Managerでは、複数のプロセスを spawn_n() メソッドによって一度に生成できる。 また、これらのプロセス全てが終了し、リソースを解放するまで待つ指示も可能である。 それに加えて、生成したプロセスを強制的に終了させることもできる。
p.227-228の例はこのプロセスマネージャの機能を使ったものだ。
ACE_Process_Manager は NCHILDREN 個の子プロセスを生成する。 (再度付記しておくが、生成するのは自分自身のプログラムだ。) 子プロセスは起動されると、即座にスリープに入る。 親プロセスは明示的に最初の子プロセスを teminate() メソッドを使って終了させる。 これによって該当の子プロセスはすぐに中断される。 この時の引数は、終了させたいプロセスのプロセスIDを指定する。 もしプロセスIDに0を指定した場合は管理している全てのプロセスを終了させる。 UNIXプラットフォームではこの処理は期待した通りに動かないかもしれず、プロセスマネージャが管理していないプロセスの終了コードまで集めなければいけなくなる場合もある。(詳しくはリファレンスドキュメントを参照してほしい。) この子プロセス終了命令呼び出しの後、親プロセスは wait() によって該当プロセスが終了処理を完了するまでブロックする。 子プロセスが完全に終了すると、wait() 呼び出しから戻り、子プロセスの終了コードを得る。
UNIXシステムにおいて、ACE_Process_Manager は terminate() による子プロセスの終了にシグナルを利用する。プロセスの終了状態を監視することで、このことを確かめられる。
最初の子プロセスの終了の後で、親プロセスはプロセスマネージャの新たな wait() 呼び出しにより、残り全ての子プロセスの終了を待つ。 このことを示すために wait() メソッドのタイムアウト値に 0 を渡している。 ここで、ブロックから戻るためのタイムアウト値を指定することもできる。 もし待機が成功せず、タイムアウトになった場合にはメソッドは 0 を返す。

10.2.2 イベントハンドリング

前の例では、親プロセスがどのようにブロックして子プロセスの終了を待つかについて見た。 実際のほとんどの場合には、子プロセスの終了を待ちながら親プロセスが他の仕事をしている場合も多い。 特に、子プロセスを生成してネットワークリクエストを処理させるような伝統的なネットワークサーバではよく見られる。 このような場合、子プロセスの終了コードを集めながら、他のリクエストが処理できるように親プロセスをフリーにしておきたいだろう。
このケースの対策として ACE_Process_Manager は ACE リアクタフレームワークと協働するための終了ハンドリングメソッドを持つようにデザインされている。 p.229-230 の例がプロセスの終了時に終了コールバック関数を呼ぶようにしたものだ。 このプログラムでは DeathHandler と呼ばれる ACE_Event_Handler クラスのサブクラスを作成し、プロセスマネージャが管理する NCHILDREN個のプロセスの終了を取り扱う。 プロセスが終了する際、リアクタは同期的にハンドラの handle_exit() メソッドに終了処理中のプロセスの ACE_Process オブジェクトを渡して呼び出す。 ACE_Process_Manager が子プロセスの終了を通知されると、ハンドラの handle_exit() メソッドが呼び出される。

10.3 ACE_Process_Mutex を利用した同期

スレッドを同期化するためには、ミューテックスやセマフォのような同期プリミティブが必要になる。 (これらのプリミティブについては 12.2 節で詳しく説明する。) 個別のプロセスで実行される場合、それぞれのスレッドは異なるアドレス空間で走る。 このような場合の同期化はさらに難しくなる。この場合、次の手法が取り得る。 ACE は、14章で説明するスレッドスコープのラッパーに似た、プロセススコープでの同期化プリミティブをいくつも提供している。 この節では別プロセスで動作しているスレッド間で同期を取るための ACE_Process_Mutex の使い方を解説する。
ACE は ACE_Process_Mutex クラスで、異なるアドレス空間同士で利用できる名前付きミューテックスを提供している。 これは名前付きであるため、同じ名前を ACE_Process_Mutex のコンストラクタに渡して生成することで、同じミューテックスオブジェクトを得られる。
次の p.232-233 の例では GlobalMutex という名前のミューテックスを生成している。 その後、仮想的なグローバルリソースを共有する二つのプロセスを生成し、このミューテックスを利用して、そのリソースへのアクセスを制御する。 両方のプロセスは共に GResourceUser クラスのオブジェクトを利用する。このオブジェクトは共有されたグローバルなリソースを示し、断続的に利用される。
前の例で用いた引数によるモード切り替え技法を、ここでも利用する。 親として実行されたプロセスは二つの子プロセスを生成する。 子プロセスとしてプログラムが実行されると、これらは ACE_Process_Mutex オブジェクトをインスタンス化することで GlobalMutex という名前付きミューテックスをOSから取得する。 はじめに実行された方は新しい名前付きミューテックスを生成し、後から実行された方は既存のミューテックスを取得する。 このミューテックスはグローバル共有リソースへのアクセスを保護するために、リソース獲得オブジェクトへ渡される。
再度明記しておくが、二つの別々な ACE_Process_Mutex オブジェクトの作成は、共有された同じミューテックスを指す。 このミューテックス自身は OS に管理されており、どちらも GlobalMutex という名前のインスタンスとして参照される。
GResourceUser クラスはミューテックスによって保護されたグローバルリソースを表す。 このクラスの run() メソッドが呼ばれると、断続的に GlobalMutex を獲得し、リソースを使い、ミューテックスを解放する。 実行中にリソースを解放するため、もう片方のプロセスにも獲得のチャンスができる。
このプログラムの実行結果を見ると、二つのプロセスが交互に共有リソースを獲得していることが分かる。(実行結果は p.233-234 に記載)
UNIX ユーザにとっては、ACE_Process_Mutex は SystemV 共有セマフォと同じものである。 他のほとんどのリソースとは違い、これらのセマフォは全ての参照が消えても、自動的に解放されることはない。 そのため、このクラスを利用する時には注意が必要であり、異状終了時であっても ACE_Process_Mutex のデストラクタが適切に呼ばれるようにしなければならない。 UNIX ユーザには、参照をカウントして利用しているプロセスが無くなると自動的に解放される ACE_SV_Semaphore_Complex を利用するオプションもある。 この自動参照カウント・解放処理は、意図的であろうとなかろうと、ミューテックスをクローズせずにプロセスが終了した場合でも実行される。

Index
Top