目次
perlfork - Perl の fork エミュレーション
NOTE: As of the 5.8.0 release, fork() emulation has considerably
matured. However, there are still a few known bugs and differences
from real fork() that might affect you. See the "BUGS" and
"CAVEATS AND LIMITATIONS" sections below.
注意: 5.8.0 のリリースと共に, fork() エミュレーションはかなり成熟し
ています. しかしながら, またいくつかのバグや実際の fork() との差違
が知られています. 後述の "バグ" 及び "警告と制限" の章も参照してく
ださい.
Perl は同名の Unix システムコートに対応するキーワード fork() を 提供しています. fork() システムコールが存在する大抵の Unix-like プラットフォームでは Perl の fork() は単純にそれを呼ぶだけです.
Windows といった fork() システムコールを持っていないいくつかの プラットフォームでは, インタプリタレベルで fork() のエミュレーション を構築します. エミュレーションは Perl プログラムのレベルに於いて 本物の fork() とできる限り互換がとれるように設計されていますが, この方法で生成される全ての仮想的な子"プロセス" はオペレーティング システムが関与する限りでは同じ実プロセスとして存在するために いくらかの重要な違いが存在します.
このドキュメントでは fork() エミュレーションの能力と限界の概要を 提供します. ここで述べられていることは本物の fork() が存在して Perl がそれを使うように設定されているプラットフォームには 当てはまりません.
fork() エミュレーションは Perl インタプリタのレベルで実装されて います. これの意味するところはおおざっぱに言うと fork() の 実行は実際には実行しているインタプリタとその状態の全てを複製し, 複製されたインタプリタを別のスレッドで, 親で fork() が呼び出された すぐ後から実行を始めることです. 仮想的なプロセスとしてこの 子"プロセス"を実装しているスレッドに着目します.
fork() を呼び出した Perl プログラムにとって, この全ては透過的で
あるように設計されています. 親プロセスは fork() からその後の
プロセス操作関数で使うことのできる仮想プロセスIDを伴って戻り,
子プロセスでは子仮想プロセスであることを示す値 0 を伴って
戻ります.
大抵の Perl の機能は仮想プロセスでも自然に振る舞います.
この特殊変数は適切に仮想プロセスIDに設定されます. これは特定のセッションに於いて仮想プロセスを識別するために 使うことができます. この値は wait() された後に起動された 仮想プロセスでは再利用されることに注意して下さい.
各仮想プロセスはそれぞれの仮想環境を持っています. %ENV の変更は仮想環境に作用し, その仮想プロセスと, そこから起動した 全てのプロセス(及び仮想プロセス)でのみ見ることができます.
各仮想プロセスはそれぞれに仮想的なカレントディレクトリの主題(idea)を 持っています. chdir() を使ったカレントディレクトリの変更はその 仮想プロセスと, そこから起動した全てのプロセス(及び仮想プロセス)でのみ 見ることができます. 仮想プロセスからの全てのファイル及びディレクトリ アクセスは仮想作業ディレクトリから実作業ディレクトリへと適切に 正しく変換されます.
wait() 及び waitpid() に fork() から返される仮想プロセスIDを渡す ことができます. これらの呼び出しは仮想プロセスの終了を適切に待ち, その状態を返します.
kill() は fork() から返されたIDを渡すことで仮想プロセスを停止することが できます. しかしこれは悲惨な状況下以外では使うべきではありません, なぜならオペレーティングシステムは実行しているスレッドが終了した 時ではプロセスリソースの完全性を保証しないかもしれないからです. 仮想プロセスに対して kill() を使うと大抵メモリリークを引き起こします, これは仮想プロセスを実装しているスレッドにはそのリソースを解放する タイミングをとれないためです.
仮想プロセスでの exec() 呼び出しは実際には要求された実行形式を 別にのプロセス空間で呼び出し, そのプロセスの終了ステータスと 同じステータスで終了するように待機します. これは実行している 実行形式が持っているプロセスIDがそれに先立つ Perl の fork() で 返されたプロセスIDは異なることを意味します. 同じように, fork() によって返されたIDを渡すプロセス操作関数は exec() の後で 待っている実プロセスではなく, exec() を呼び出した仮想プロセスに 対して作用します.
exit() はいつでも, 起動中の子仮想プロセスを自動的に wait() してから, 実行している仮想プロセスを終了させます. これはそのプロセスは全ての 実行中の仮想プロセスが終了するまでしばらくの間終了しないことを 意味します.
全ての開いているハンドルは子プロセスで dup() されます, その為 どこかのプロセスでハンドルを閉じても他には影響しません. いくつかの制限については続きを見て下さい.
オペレーシングシステムから見ると, fork() エミュレーションから生成された 仮想プロセスは単なる同じプロセス内のスレッドです.これは オペレーシングシステムによって科せられた全てのプロセスレベルの制限は 全ての仮想プロセスで一緒に割り当てられます. これには開いているファイル, ディレクトリ, ソケットの数の制限, ディスク使用量の制限, メモリサイズの制限, CPU使用量の制限等が含まれます.
親プロセスが kill (Perl の kill() 組み込み関数若しくは外部の同等の 物で)されると, 全ての仮想プロセスも同様に kill され, プロセス全体が 終了します.
通常のイベントの進み方であれば, 親プロセスとそこから起動された それぞれの仮想プロセスは終了する前に各自の仮想子プロセスを待つでしょう. これは親プロセスとそこから起動されたそれぞれのそれがまた仮想 親プロセスである仮想子プロセスはそれらの仮想子プロセスが終了した後で のみ終了するdせほう.
仮想プロセスがその親プロセスから detach して実行している (つまり親は必要がないのならwait()する必要がない)とマークする方法は 今後提供されるでしょう.
fork() エミュレーションは BEGIN ブロックで呼ばれた時には完全には 正しく動作しません. fork された複製は BEGIN ブロックの内容を 実行しますが, BEGIN ブロックの後のソースストリームのパースを 継続しません. 例えば, 次のコードを考えてみます:
BEGIN {
fork and exit; # fork child and exit the parent
print "inner\n";
}
print "outer\n";
これは次のように出力します:
inner
本来は次のようであるはずです:
inner
outer
この制限はパース途中の Perl パーサによって使われるスタックの 複製と再開における基礎技術の複雑さに起因しています.
fork() した時点で開いている全てのファイルハンドルは dup() されます. つまり, ファイルは親とことで独立して閉じることができます, しかし dup() されたハンドルはまだ同じシークポインタを共有していることに 注意して下さい. 親でシーク位置を変更するとそれは子にも波及し, その逆も同様です. これは子供と分離したシークポインタが必要な ファイルを開くことで無効にできます.
open(FOO, "|-") 及び open(BAR, "-|") 構成子は実装されていません.
この制限は明示的にパイプを作る新しいコードで簡単に取り除けます.
以下の例で fork された子に書き出す方法を示します:
# simulate open(FOO, "|-")
sub pipe_to_fork ($) {
my $parent = shift;
pipe my $child, $parent or die;
my $pid = fork();
die "fork() failed: $!" unless defined $pid;
if ($pid) {
close $child;
}
else {
close $parent;
open(STDIN, "<&=" . fileno($child)) or die;
}
$pid;
}
if (pipe_to_fork('FOO')) {
# parent
print FOO "pipe_to_fork\n";
close FOO;
}
else {
# child
while (<STDIN>) { print; }
exit(0);
}
そしてこちらは子から読む時です:
# simulate open(FOO, "-|")
sub pipe_from_fork ($) {
my $parent = shift;
pipe $parent, my $child or die;
my $pid = fork();
die "fork() failed: $!" unless defined $pid;
if ($pid) {
close $child;
}
else {
close $parent;
open(STDOUT, ">&=" . fileno($child)) or die;
}
$pid;
}
if (pipe_from_fork('BAR')) {
# parent
while (<BAR>) { print; }
close BAR;
}
else {
# child
print "pipe_from_fork\n";
exit(0);
}
pipe open() の fork は今後実装されるでしょう.
それ自身でグローバル状態を保持している外部関数(XSUBs; external subroutines)は正しく動作しないでしょう. そのような XSUB は 異なる仮想プロセスからグローバルデータに対して同時にアクセスするのを 防ぐためのロックも保持するが, その全ての状態を fork() 時に 自然と複製される Perl シンボルテーブル上に置くかする必要が あるでしょう. 拡張に対して複製するタイミングを提供するコールバック 機構は近い将来提供されるでしょう.
fork() エミュレーションは Perl インタプリタを埋め込んでいて Perl コードを評価(eval)する Perl API を少しだけ呼び出すような アプリケーションの内部で実行されている時には, 予期したように 振る舞わないかもしれません. これは, エミュレーションは Perl インタプリタ自身の持っているデータ構造 しかしらず, (Perl インタプリタを?)格納しているアプリケーションの 状態に関しては何も知らないために生じます. 例えば, アプリケーションの自分のコールスタックで継続している状態は 手の届かないところにあります.
fork() エミュレーションはコードを複数のスレッドで実行するために, スレッドセーフでないライブラリを呼び出す拡張は fork() を呼び出すと 正しく動作しないかもしれません. Perl のスレッドサポートは徐々に ネイティブな fork() を持っているプラットフォームにも広く導入 されてきているので, そのような拡張はスレッドセーフに修正するように 期待されています.
仮想プロセスIDを負の整数値とすることは整数 -1 を破壊します,
なぜなら wait() や waitpid() といった関数はその値を
特殊な物として扱うためです. 現在の実装においては, システムは
ユーザスレッドに対してスレッドID 1 を割り当てることは
ないと暗黙に仮定しています. よりよい仮想プロセスIDの表現は
今後実装されるでしょう.
特定のケースに置いて, pipe(), socket(), そして accept() 演算子 によって生成された OS レベルのハンドルは仮想プロセスできちんと 複製されないことがあるようです. これは特定の状況でのみ発生します が, これが発生する場所では, パイプハンドルの読み書き間での デッドロックやソケットハンドルに対する送受信ができないといったことが 起こるようです.
このドキュメントは何カ所か不完全かもしれません.
並列インタプリタと fork() エミュレーションのサポートは Microsoft Corporation の資金援助で ActiveState によって実装されました.
このドキュメントは Gurusamy Sarathy <gsar@activestate.com> によって書かれ, メンテナンスされています.
"fork" in perlfunc [CPAN], perlipc [CPAN]
山科 氷魚 (YAMASHINA Hio) <hio@hio.jp>
原典: perl VERSION 5.8.8. 翻訳日: 2007-03-08.