- Perl ›
- プロセス間通信
プロセス間通信
Perlで「プロセス間通信」を行う方法を解説します。
プロセスとは
プロセスとは、OSから見たリソースの管理の単位のことです。プロセスには、OSに対して一意なプロセスIDが割り当てられます、リソースには、ファイルハンドル、メモリ空間、CPUががあります。ユーザーから見れば、プロセスとは、実行されているひとつのプログラムです。
OSのプロセス管理のイメージ
出来事 OSのプロセス管理のイメージ |-------------------------------------------------| | プロセスIDを決定( 3452 ) | |------------------| | | |プログラム実行開始|------->| メモリ割り当て管理 | |------------------| | プロセスのために、一定のメモリ空間を確保し、 | | プロセスIDで管理する | | ( 確保したメモリ領域を, | | 他のプロセスには、干渉させない ) | | | | 実行コードをメモリ上に読み込む。 | | 静的なデータ領域をメモリ上に確保する。 | | | | CPU割り当て管理 | | プロセスIDを、CPUの割り当てのスケジュールに | | 登録する。 | |-------------------------------------------------| |-------------------------------------------------| |------------------| | プロセスのファイルオープン要求に応じて | |ファイルをオープン|------->| ファイルをオープン | |------------------| | プロセスIDで、オープンしたファイルを管理する | |-------------------------------------------------| プロセスとは、OSから見た、メモリ、ファイル、 CPUのスケジューリングの管理の単位です。
$$ - プロセスIDを取得する
プロセスIDを取得するには特殊変数「$$」を使用します。
$$
プロセスIDとは、OSがプロセスを一意に識別するために、プロセス起動時に割り当てる識別子のことです。
$^T - プロセスの開始時刻を取得する
プロセスの開始時刻を取得するには特殊変数「$^T」を使用します。
$^T
時刻は、エポック秒(1970年1月1日0時0分0秒からの秒数) で取得されます。
fork関数
fork関数を使用するとプロセスをふたつに分岐することができます。分岐したひとつのプロセスは親プロセス、もうひとつのプロセスは子プロセスになります。
fork関数は引数をとりません。戻り値は分岐したプロセスが親プロセスの場合は、分岐した子プロセスのプロセスIDになります。子プロセスの場合は、0になります。forkが失敗した場合はundefが返ります。
my $pid = fork;
fork関数については「fork関数」をご覧ください。
wait関数
forkで分岐させた場合は親プロセスと子プロセスはどちらが先に終了するかはわかりません。今回は親プロセスが子プロセスの終了を待つ方法を解説します。
子プロセスの終了を待つにはwait関数を使用します。wait関数はひとつの子プロセスが終了するまで待機し続けます。戻り値は終了した子プロセスのプロセスIDです。子プロセスが何らかの理由で自動的に回収されていた場合は-1が返却されます。
my $pid = wait;
wait関数については「wait関数」をご覧ください。
$? - 子プロセスの終了ステータス
waitで子プロセスの終了を待った場合は、特殊変数「$?」に子プロセスの終了ステータスを含めた複数の値が格納されます。またsystem関数を使って子プロセスを実行した場合にも$?が設定されます。
子プロセスの終了ステータス***
$?の解釈の方法はやや複雑です。$?には16ビットの値が設定されます。上位8ビットに子プロセスの終了ステータスが設定されます。下位8ビットの8ビット目には、コアダンプが生成されたかどうかを表します。下位8ビットの7ビット目までは、もしあればプロセスを終了させたシグナルの番号を表します。
特殊変数「$?」についての詳細は「Perlの「特殊変数」の一覧を見る」をご覧ください。
子プロセスのリソースを解放処理を記述する
子プロセスのリソースを解放処理するにはwaitpid関数を使って次のような記述を行います。
use POSIX 'WNOHANG'; $SIG{CHLD} = sub { while(waitpid(-1, WNOHANG) > 0) { リソースの解放処理 } }
この記述の意味を理解するのはひと目見ただけでは難しいと思いますので解説します。
CHLDシグナル
CHLDシグナルは子プロセスが終了したときに親プロセスに送信されるシグナルです。つまり親プロセスの側でCHLDシグナルをを監視することで、子プロセスが終了した場合に何らかの処理を行うことができます。
$SIG{CHLD} = sub { 子プロセスが終了した場合の処理 }
waitpid関数
waitpid関数は、指定したプロセスIDの子プロセスが状態変化を起こすまで待機します。状態変化というのは次の3つです。
- 子プロセスが終了した
- 子プロセスが停止した
- 子プロセスが再開した
状態変化があった場合は、watipid関数はその子プロセスのプロセスIDを返却します。また子プロセスが終了した場合は、ゾンビ状態にあるプロセスを解放してくれます。ゾンビ状態とは終了はしたがOSのプロセステーブルから取り除かれていない状態のことを言います。
waitpid関数の第一引数は子プロセスのIDですが、-1を指定すると任意の子プロセスの状態変化をひとつ調べることができます。WNOHANGは状態変化のある子プロセスがいなかった場合に、ブロッキングしないようにするオプションです。
waitpidは成功すると、状態が変化した子プロセスのプロセスIDを返します。WNOHANG が指定されていて、指定した子プロセスが一つ以上存在するが、どの子プロセスでも状態変化が起こっていない場合は0を返します。エラーの場合は-1を返します。
waitpidの呼び出し部分だけを取り出してみました。
waitpid(-1, WNOHANG)
これを要約してみます。
- 子プロセスのどれかに状態変化が起こっていればプロセスID(0より大きい)を返却し、さらにもし子プロセスが終了していれば、ゾンビ状態のプロセスを解放する。(今回はCHLDシグナルのハンドラとして設定しているので、意味としては「子プロセスのどれかが終了していればプロセスIDを返却しゾンビ状態のプロセスを解放する」考えてもよいと思います)
- 子プロセスが存在しない場合は、エラーとなり-1を返します。
- 指定した子プロセスが一つ以上存在するが、どの子プロセスでも状態変化が起こっていない場合は0を返します。
それで以下のように戻り値が0より大きいという条件を記述すると、子プロセスのどれかが終了している場合に実行する処理を記述することができます。
if (waitpid(-1, WNOHANG) > 0) { # ... }
whileによるループ
while(waitpid(-1, WNOHANG) > 0) { リソースの解放処理 }
この部分を見てなぜwhile文が必要なのだろうかと思うかもしれません。ひとつの子プロセスが終了したときにひとつのCHLDシグナルが送信されるのであれば、while文はいらないのではないかと思うのは自然なことです。けれどもシグナルにはひとつの制約があって、ごく短い間に複数の子プロセスが終了してしまった場合には、シグナルは上書きされてしまい、複数の子プロセスが終了しているのにもかかわらず、ひとつのシグナルしか送られないということがありえます。
このためにwhile文を使って、終了したすべての子プロセスに対して処理を行う必要があります。
特定の処理が必要ではなく単にゾンビを解放したい場合
特定のリソース解放の処理が必要ではなく単にゾンビを解放したい場合はシグナルハンドらからwaitpidを呼ばなくても「IGNORE」を設定すれば自動的に行ってくれます。
$SIG{CHLD} = 'IGNORE';
他コマンドの出力をパイプを使って読み込む
他コマンドの出力を読み込むにはパイプという仕組みを利用します。
use strict; use warnings; # (1) 他コマンドの出力を読み込むためのパイプをオープン open my $pipe, '-|', 'dir' or die qq/Can't open pipe: $!/; # (2) パイプから読み込み while (my $line = <$pipe>) { print $line; } close $pipe;
(1) 他コマンドの出力を読み込むためのパイプをオープン
open my $pipe, '-|', 'dir' or die qq/Can't open pipe: $!/;
open関数には特別な利用方法があります。他のコマンドの出力を受け取るパイプをオープンするには、第二引数に「-|」を指定し、第三引数にコマンドを指定します。今回はディレクトリの内容を出力するコマンドである「dir」を指定しています。
第二引数の「-|」のハイフンの部分をコマンドに置き換えて、他コマンドの出力がパイプを通って流れてくるところを想像してみてください。
dir | perl
(2) パイプから読み込み
while (my $line = <$pipe>) { print $line; }
パイプから1行づつ読み込んでいます。プログラムからみると、まるでファイルから読み込んでいるように見えることに注目してみてください。
パイプを使って出力を他コマンドへ渡す
他コマンドへ出力を渡すにはパイプという仕組みを利用します。
use strict; use warnings; # (1) 他コマンドへ出力を流し込むためのパイプをオープン open my $pipe, '|-', 'more' or die qq/Can't open pipe: $!/; # (2) パイプへ書き込み print $pipe "Hello world"; close $pipe;
(1) 他コマンドへ出力を流し込むためのパイプをオープン
open my $pipe, '|-', 'more' or die qq/Can't open pipe: $!/;
open関数には特別な利用方法があります。他のコマンドの出力を受け取るパイプをオープンするには、第二引数に「|-」を指定し、第三引数にコマンドを指定します。今回はファイルの内容を出力するコマンドである「more」を指定しています。
第二引数の「|-」のハイフンの部分をコマンドに置き換えて、出力がコマンドに流れ込むところをイメージしてみてください。
出力 | more
(2) パイプへ書き込み
print $pipe "Hello world";
パイプに書き込むと出力結果が他コマンドに渡されます。プログラムからみると、まるでファイルに書き込んでいるように見えることに注目してみてください。
getpwuid関数 - ユーザー情報(ユーザー名)を取得する
ユーザー名を取得するにはgetpwuid関数と特殊変数$>を使って以下のようにします。
my $user = getpwuid($>);
getpwuidの引数にユーザーIDを指定するとユーザー名を取得できます。
getpwnam関数 - ユーザー名からユーザーIDを取得する
ユーザー名からユーザーIDを取得するにはgetpwnam関数を使って、次のようにします。
# ユーザー名からユーザーIDを取得 my $uid = (getpwnam 'ken')[2];
getgrnam関数 - グループ名からグループIDを取得する
グループ名からグループIDを取得するにはgetgrnam関数を使って、次のようにします。
# グループ名からグループIDを取得 my $gid = (getgrnam 'devel')[2];