Subject: How do I get rid of zombie processes that persevere?
>From: jik@pit-manager.MIT.Edu (Jonathan I. Kamens)
>From: casper@fwi.uva.nl (Casper Dik)
Date: Thu, 09 Sep 93 16:39:58 +0200

3.13) しつこいゾンビプロセスを退治する方法は。

残念ながら、子プロセスの死がどのようにしてなされるかを一般的に述べるこ とはできません。様々な味付けをされた様々な機構を持った UNIX があるから です。

まず最初に、デフォルトとして、どのような UNIX においても子プロセスをき ちんと wait() するべきです。というのは、何も言わなくても自動で子プロセ スを終了時にフラッシュしてくれるような UNIX は、私の知る限り存在しない からです。

次に SysV 系のいくつかのシステムでは、"signal(SIGCHLD, SIG_IGN)"(えー 事実上は SIGCLD が SIGCHLD の代わりに使われるが、新しい SysV システム のほとんどは "#define SIGCHLD SIGCLD" とヘッダファイルにあります)を使 えば、子プロセスは副作用もなく自動的に消されます。あなたのサイトでこれ が働くかを調べるには、試してみるのが一番でしょう。しかし、どちらにせよ 移植性のあるコードを書く場合は、これに頼るのは良い方法ではありません。 残念ながら POSIX ではこのようになっていません。POSIX では SIG_IGN に SIGCLD をセットした時の振舞いは規定されていないので、POSIX 系をサポー トするようなプログラムには使うことができません。

じゃあ POSIX ではどうすればいいのか、上記のようにシグナルハンドラと wait を準備する必要があります。POSIX の下ではシグナルハンドラは sigaction によって準備されます。「停止中 (stopped)]の子プロセスは関係 なく、終了した子プロセスにのみ興味があるため、SA_NOCLDSTOPを sa_flags に追加します。ブロックなしの wait は waitpid() によってなされます。 waitpid の第1引数は -1 (任意の pid)、第3引数は WNOHANG でしょう。こ れは最も移植性の高い方法で、今後さらに移植性は高まるでしょう。

もしあなたのシステムが POSIX をサポートしていない場合も、いくつか方法 はあります。いちばん早い方法は、signal(SIGCHLD, SIG_IGN) が使えるなら ば、それを使う方法です。強制自動消去に SIG_IGN が使えないなら、そのよ うな働きをするシグナルハンドラを書くことになります。全ての様々な UNIX で正しく働くようなシグナルハンドラを書くっていうのは、決して易しいこと ではありません。というのは次のような矛盾があるからです。

いくつかの UNIX では、一つ *以上* の子プロセスが死んだ時に SIGCHLD シ グナルハンドラが、呼出されます。これは、シグナルハンドラで一回だけ wait()を呼出しても、全ての子どもたちが消去されないということです。幸運 にも、このような場合が考えられるならば、どの UNIX でも wait3() か waitpid() が使える (ものと信じています) ため、これの WNOHANG オプショ ンで消去されるのを待っている子どもたちがいるかどうかを調べることができ ます。ですから、wait3() を持っているシステムなら、シグナルハンドラは、 WNOHANG オプション付きの wait3() を消えるべき子供がなくなるまでコール し続ければよいでしょう。waitpid() は POSIX の中では好ましいインターフェ イスです。

SysV 系のシステムでは、もし SIGCHLD シグナルハンドラを抜けた後に、まだ 消去されるのを待っている子プロセスが残っていれば、SIGCHLD シグナルが再 生成されます。ですから、ほとんどの SysV 系のシステムでは、シグナルハン ドラが呼出された時に一つのシグナルを消せばいいだけであり、終了後にまだ 残りがいたとしてもハンドラは再び呼出される、と仮定しても安全でしょう。

古いシステムでは、シグナルハンドラが呼ばれた時に自動的に SIG_DFL をリ セットするのを防ぐ方法はありません。このようなシステムではハンドラ内で 最初に "signal(SIGCHILD, catcher_func)" ("catcher_func" はハンドラ関数 の名前) を出力する必要があり、そうすることで SIG_DFL リセットされます。

幸い、新しい実装ではシグナルハンドラはハンドル関数が呼ばれた際に SIG_DFL をリセットしないよう準備することができます。この問題に関しては wait3() を持たず、SIGCLD を持つシステムでは、シグナルハンドラが呼出さ れる毎に、少なくとも1回 wait() を実行した後で signal() を実行してリセッ トする必要があります。過去との互換性のため、System V は signal() の (呼ばれたときにハンドラをリセットする) 古いセマンティクスを保っていま す。貼り付けるシグナルハンドラは sigaction() か sigset() によって準備 されます。

以上をまとめると次のようになります。waitpid() (POSIX) か wait3() を持 つシステムでは、それを使い、シグナルハンドラはループしなければなりませ ん。そうでないシステムでは wait() を1回、シグナルハンドラが起動するた びにコールします。

もう一つだけ。このような問題はごめんだというなら、少々効率は悪いですが、 この問題を回避する移植性のある方法 があります。親プロセスは forkし、そ こで正しく wait し、子プロセスが死ぬのを待ちます。子プロセスはさらに fork し、孫プロセスを作ります。そこで子プロセスは直ちに終了し(すると親 は、その死の知らせを待っていたので、実行を続ける)、もともと子がやるは ずだった仕事を孫が行ないます。親が死んでいるので、孫は init に継承され、 必要な wait などを行なってくれます。この方法は余分な fork を行なうので 非効率ですが、移植性は完璧です。


UNIX FAQ LIST / Copyright(c)1994,Ted Timar / tmatimar@isgtec.com


Maintainer: あさだ たくや