プロセスとシグナル

■ 子プロセスを起動する(fork, exec)

◆ 子プロセスの生成

system() や `...` によるコマンド起動がサポートされているので、あまり使用されることはありませんが、低レベルのコマンド起動手順がサポートされています。

fork() は、親プロセス から 子プロセス を生成します。fork() を実行すると親プロセスと子プロセスに分岐し、双方が独立して動き始めます。子プロセスの場合は 0、親プロセスの場合は子プロセスの プロセスID が戻り値として返されます。

exec() は、指定したコマンドに処理を渡します。プロセスが新しいコマンドに変身するようなものです。fork() と exec() を組み合わせることで、コマンドを子プロセスとして実行することが可能になります。

wait() は、UNIX 環境で終了した子プロセスを成仏させるのに必要です。この処理を怠ると、子プロセスは ゾンビプロセス と呼ばれる状態でさまよいつづけることになります。特定の子プロセスを成仏させるには waitpid($pid) を用います。

$pid = fork();
if ($pid == -1) {
    exit(1);                            # エラー処理
} elsif ($pid == 0) {
    sleep(3);                           # 子プロセスの処理
    exec("netstat -a");
} else {
    for ($i = 0; $i < 6; $i++) {        # 親プロセスの処理
        print "===PARENT($i)\n";
        sleep(1);
    }
    wait();
}

実行結果は次のようになります。

===PARENT(0)
===PARENT(1)
===PARENT(2)
Active Connections
  Proto  Local Address          Foreign Address        State
  TCP    pc2:135                PC2:0                  LISTENING
  TCP    pc2:6543               PC2:0                  LISTENING
===PARENT(3)
===PARENT(4)
===PARENT(5)

■ プロセスにシグナルを送る(kill)

◆ プロセスにシグナルを送る

kill(sig, pid) は、pid で指定したプロセスに sig で指定したシグナルを送信します。指定可能なシグナルは「主なシグナル一覧」を参照してください。下記の例では、親プロセスから子プロセスを起動し、3秒後に親プロセスから子プロセスに SIGINT(2) シグナルを送信しています。SIGINT(2) シグナルを受け取った子プロセスは、処理の途中でプロセスを中断して終了します。

$| = 1;               # バッファリングをオフにしておく

$pid = fork();        # 子プロセスに分岐する
if ($pid == 0) {
    # 子プロセス
    for ($i = 0; $i < 10; $i++) {
        print "===CHILD($i)\n";
        sleep(1);
    }
} else {
    # 親プロセス
    sleep(3);
    kill(2, $pid);
    wait();
}

実行結果は次のようになります。

===CHILD(0)
===CHILD(1)
===CHILD(2)

■ シグナル受信時の処理を指定する($SIG{...})

◆ シグナルハンドラを指定する

$SIG{...} に関数名を代入することで、シグナル受信時の処理(シグナルハンドラ)を指定することができます。... の部分には INT、TERM、ALRM など、シグナル名から SIG を取り除いた名前を指定します。シグナル名は「主なシグナル一覧」を参照してください。

$| = 1;                    # バッファリングしないモード
$SIG{'INT'} = "sig";       # シグナルハンドラの設定

for ($i = 0; $i < 40; $i++) {
    print ".";                           # 0.1秒毎に . を書き出す
    select(undef, undef, undef, 0.1);    # 0.1秒間ウェイトする
}
print "\n";

# SIGINT シグナル受信時に呼ばれる
sub sig {
    local($type) = @_;
    $SIG{'INT'} = "sig";   # シグナルハンドらの再設定
    print "<SIG$type>";
}

これをコマンドプロンプトから実行し、ドット(.)が書き出されている間にキーボードから Ctrl-C(Ctrl キーを押しながら C)を押すとプロセスに SIGINT シグナルが送信されます。プロセスはこれを受信し、$SIG{'INT'} の値に従って sig サブルーチンを呼び出します。実行例は次のようになります。

....<SIGINT>.............<SIGINT>..........<SIGINT>.............
◆ プロセス中断時の処理を指定する

CGI がタイムアウトしてしまったり、プロセスが強制終了させられるときには、プロセスに SIGINT、SIGTERM、SIGHUP、SIGQUIT などのシグナルが送信されます。これらのシグナルをキャプチャして、シグナルハンドラを呼び出し、強制終了時の後処理を実行することができます。

$SIG{'INT'} = $SIG{'TERM'} = $SIG{'HUP'} = $SIG{'QUIT'} = "sigexit";

sub sigexit {
    # ロックフォルダの削除や、重要データの緊急退避などの後処理
}

■ アラームシグナルを使う

◆ n秒後にアラームシグナルを送信する

alarm() を呼び出すと、指定した秒数後に自分自身に対して SIGALRM シグナルを送信します。プロセスはこれを $SIG{'ALRM'} でキャプチャしてシグナルハンドラを呼び出すことができます。

$| = 1;                  # バッファリングをオフにする

$SIG{'ALRM'} = "sig";    # SIGALRM 受信時のハンドラを指定
alarm(1);                # 1秒後に SIGALRM を発生させる

for ($i = 0; $i < 100; $i++) {
    print ".";
    select(undef, undef, undef, 0.1);       # 0.1秒間ウェイトする
}
print "\n";

sub sig {
    local($type) = @_;
    alarm(1);                # アラームを再設定する
    print "<SIG$type>";
}

これを実行すると次のように、1秒毎に <SIGALRM> が割り込み出力されます。

..........<SIGALRM>..........<SIGALRM>..........<SIGALRM>..........<SIGA
LRM>..........<SIGALRM>..........<SIGALRM>..........<SIGALRM>..........<
SIGALRM>..........<SIGALRM>..........<SIGALRM>

alarm() は、Windows XP ではまだサポートされていません。


Copyright (C) 2002 杜甫々