Fork

forkとは、プロセスのコピーを生成するものである。UNIXおよびUnix系OSではシステムコールのひとつで、新たに作り出されたプロセスを子プロセス、forkを呼び出したプロセスを親プロセスと呼び、forkシステムコールの戻り値によって親と子の処理を区別する。子プロセスではforkの戻り値は0であり、親プロセスの戻り値は新たに生成された子プロセスのプロセス識別子、エラーが起きた場合は-1である。また、マルチスレッド環境でスレッドのコピーを作ることもforkと呼ぶことがある。

forkが呼び出されると、子プロセスのためのアドレス空間が新たに作成される。子プロセスのアドレス空間には親プロセスが持っていた全セグメントのコピーがあるが、コピーオンライト機能によって実際の物理メモリの確保は遅延される。親プロセスと子プロセスは同じコードセグメントを持つが、独立して実行される。

1. Unixにおけるforkの重要性
-------------------

Unixにとってforkは重要な機構概念であり、フィルタの開発を奨励している設計哲学の重要な部分を担っている。 Unixでのフィルタは標準入力を入力とし標準出力を出力とするプログラムである。シェルがフィルタをパイプで連結することで、複雑な処理を実現できる。例えば次のようにfindコマンドの出力をwcコマンドの入力に連結すると、拡張子が ".cpp" のファイルをカレントディレクトリ配下で探し、見つかったファイル数を表示できる。 $ find . -name "*.cpp" -print | wc -l このコマンド行を入力すると、シェルは自分自身をforkし、プロセス間通信の1つであるパイプを使って find コマンドの出力を wc コマンドの入力に結びつける。パイプは2つの新たなファイル識別子を生成し、2つの子プロセスを生成する。2つの子プロセスはまず dup2 で対応するパイプのファイル識別子を複製して標準入力と標準出力に置き換える。そしてそれぞれの子プロセスがexecファミリのシステムコールを使って、実行すべきコマンドのプログラムで自身をオーバーレイする。

より一般的に、シェルはユーザーがコマンド行を入力するたびにforkを行っている。子プロセスはシェルがforkを行うことで生成され、子プロセスがexecでオーバーレイを行い、実行すべきプログラムのコードをマッピングする。

2. プロセスのアドレス空間
--------------

実行ファイルを実行しようとすると、プロセスが生成される。実行ファイルはセグメントと呼ばれるブロックにグループ化されたバイナリコードを含んでいる。各セグメントは特定の種類のデータを格納するのに使われる。典型的なELF形式の実行ファイルには、以下のようなセグメントが存在する。

コードセグメント — 実行可能なコードを格納したセグメント
.bss — 初期値がゼロのデータを格納したセグメント
データセグメント — 初期値のあるデータを格納したセグメント
symtab — プログラムのシンボル群を格納したセグメント
interp — 使用すべきインタプリタの名前を格納したセグメントreadelf コマンドを使えば、ELF形式のファイルの詳細を表示できる。そのようなファイルを実行するためにメモリ上にロードすると、セグメント群がメモリにロードされることになる。実行ファイル全体を連続なメモリ位置にロードする必要はない。メモリはページと呼ばれる同じ大きさの部分に分けられている。したがって実行ファイルをメモリにロードする際、実行ファイル内のそれぞれの位置がそれぞれ異なるページに置かれる。大きさが10KBのELF形式の実行ファイルがあるとする。そのOSのサポートするページサイズが4KBなら、そのファイルは4KB、4KB、2KBという3つの部分に分けてロードされる。この3つのフレームはメモリ中の任意のフリーなページに置くことができる。

3. forkとページ共有
-------------

forkシステムコールを実行すると、本来ならばOSが子プロセスのために親プロセスの持つ全ページを物理メモリ上の別の位置にコピーすることになる。しかし、場合によってはその必要はない。子プロセスがfork直後に "exec" システムコールを実行する場合や終了する場合である。親プロセスが何らかのコマンドを実行するためだけに子プロセスを生成した場合、子プロセスのアドレス空間は実行すべきコマンドですぐに置換されるので、親プロセスのページ群をコピーする必要はない。

そのため、コピーオンライト  という技法が使われる。COWでは、fork時に親プロセスのページ群を子プロセスにコピーしない。その代わりページ群は親プロセスと子プロセスの間で共有される。親子いずれかのプロセスがページの内容を更新しようとしたとき、そのページだけコピーを作成し、書き込もうとしたプロセスの当該ページだけが更新される。書き込んだプロセスはその後その新たにコピーしたページを使用する。もう一方のプロセスは、コピー元のページを使用し続ける。何らかのプロセスが書き込もうとしたときにページがコピーされるので、この技法をコピーオンライトと呼ぶ。

4. vforkとページ共有
--------------

vfork はもう1つのプロセス生成用UNIXシステムコールである。vforkシステムコールで子プロセスを生成すると、子プロセスが終了するかexecveファミリのシステムコールで新たな実行イメージに切り換えるまでそのシステムコールの延長上で親プロセスが待ち合わせる。vforkでも親プロセスと子プロセスでページ群を共有するが、コピーオンライトは必要としない。子プロセスが共有ページに更新を加えてもコピーは作成せず、親プロセスからもその更新が見える。ページを全くコピーしないので、子プロセスでコマンドを実行する場合には非常に効率的である。

実装によっては vfork は fork と同じである。

vforkとforkの唯一の違いは、親プロセスと子プロセスがコードとデータを共有できる点である。これによって複製は劇的に高速化されるが、使い方を誤ると親プロセスの一貫性が壊れる危険性がある。

直後に exec または _exit を呼び出す以外のvforkの使用は推奨されない。特にLinuxのmanページではvforkそのものの使用が推奨されていない。

vforkで生成した子プロセスがvforkを呼び出したルーチンからさらに外側に復帰した場合、親プロセスのコールスタックを書き変えてしまうので、vforkから親プロセスが戻ったときの動作を保証できない。また、execせずに子プロセスを終了する場合、_exitではなくexitを使用すると、標準I/Oチャネルをフラッシュしてクローズするため親プロセスの標準I/O構造にダメージを与える危険性がある。なお、forkの場合でも子プロセスがexitを呼び出すのは危険である。

vfork後に子プロセスでシグナルハンドラが呼び出された場合、子プロセスの他のコードと同じ規則に従う必要がある。

5. MMUのないシステム
-------------

組み込みシステムではメモリ管理ユニット  が存在しない場合があり、fork でのコピーオンライトの実装ができないことがある。システムがプロセス毎のアドレス空間を他の何らかの機構でサポートする場合、プロセスの使用する全メモリをコピーすれば実質的には同じである。しかしそれは非常に時間がかかるし、多くの場合すぐに別の実行ファイルのイメージで書き換えられるので無駄である。

全プロセスが単一のアドレス空間を共有している場合、例えば全メモリページをスワップしてコンテキストスイッチするというforkの実装も考えられる。μClinuxベースのOSなどの組み込みOSが採用しているのは、fork を廃して vfork だけを実装するという方法である。そのため、forkを使っているところを全てvforkで動作できるように書き換えている。

6. Unix以外でのフォーク
---------------

Unix系やLinuxでのfork機構は、基盤となっているハードウェアにある種の前提を課している。リニアなメモリ空間とページング機構を持ち、連続なアドレス範囲のメモリコピーを効率的に行えるという前提である。VMSの当初の設計では、コピー操作後に少数の具体的なアドレスの内容書き換えを行うフォークは危険だとみなされていた。現在のプロセス状態におけるエラーが子プロセスにコピーされるかもしれない。そこでプロセスのスポーン というメタファーが使われた。すなわち、新しいプロセスのメモリレイアウトの各コンポーネントを一から新たに構築する。ソフトウェア工学的観点からすれば後者の手法の方がきれいで安全だが、フォークの方が効率的なのでよく使われている。スポーン方式は後にマイクロソフトのOSで採用された。

7. fork を使ったコード例
----------------

7.1. C の例
---------

include /* printf, stderr, fprintf */
include <sys/types.h> /* pid_t */
include /* _exit, fork */
include /* exit */
include /* errno */int main { pid_t pid;

/* 子プロセスと親プロセスの両方の出力が

標準出力に書かれる。
両者は同時に動作する。
/pid = fork; if  { /* エラー:

forkが-1を返す場合、エラーが起きたことを示す。
例えばプロセス数が制限に達した場合など。
/fprintf; exit; }

if  { /* 子プロセス:

forkが0を返す場合、子プロセスである。
1秒に1ずつ、10まで数える。
/int j; for  { printf; sleep; } _exit; /* exit を使わない点に注意 */ } else {

/* fork が正の数を返す場合、親プロセスである。

その値は生成した子プロセスのPIDである。
ここでも10まで数える。
/int i; for  { printf; sleep; } exit; } return 0; }<WtXmlEndTag />

7.2. Perl の例
------------

!/usr/bin/env perl -wuse strict;

if  { # 親プロセス foreach my $i  { print "Parent: $i "; sleep 1; } } else { # 子プロセス foreach my $i  { print "Child: $i "; sleep 1; } exit; # forkした子プロセスの終了 }

exit; # 親プロセスの終了

<WtXmlEndTag />

7.3. Python の例
--------------

!/usr/bin/env python3import os import sys import time

def doTask: "This function create a task that will be a daemon" with open as log_file: while True: log_file.write)) log_file.flush time.sleep

def createDaemon: "This function create a service/Daemon that will execute a det. task" try: pid = os.fork

if pid > 0: print) sys.exit

except OSError as error: print".format) sys.exit

else: doTask

if __name__ == "__main__": createDaemon<WtXmlEndTag />

8. Fork-Exec
------------

Fork–execは、UNIXで一般的に使われる手法であり、新たなプログラムをプロセスとして実行する。forkは親プロセスを2つの同一内容のプロセスに分岐させるシステムコールである。forkによって子プロセスが親プロセスのコピーとして生成され、子プロセスがexecシステムコールを呼び出すことで自身の内容を置き換える。

親プロセスが子プロセスの終了を待ち合わせる場合、子プロセスの終了コードexit code}}を受け取ることができる。子プロセスがゾンビプロセスとなるのを防ぐには、親プロセスがWait システムコールを使用する必要があり、そのタイミングは定期的でもよいし、SIGCHLDシグナルを受け取った際でもよい。

子プロセスがexecを呼び出すと、そのアドレス空間の内容は全て失われ、指定されたプログラムを実行するためのアドレス空間のマッピングが新しく設定される。これをオーバーレイと呼ぶ。アドレス空間は全て置き換えられるが、オープン済みファイルのファイル記述子群は //close-on-exec// が指定されたときだけ exec時に自動的にクローズされる。この特徴を利用して、forkを呼び出す前にパイプを作成しておき、execで指定された新しいプログラムとの通信を行うというUNIX特有の手法が実現されている。

なお、Microsoft Windows では fork 単独に相当するシステムコールがなく、fork-exec モデルを採用していない。代わりにスポーン ファミリ関数が process.h で定義されており、fork-exec に相当する働きをする。

9. 脚注
-----

10. 参考文献
--------

[1], Operating Systems , Franco Callari, Electrical Engineering Department, McGill University
//Advanced Programming in the UNIX Environment//, W. Richard Stevens, Addison-Wesley ISBN 0-201-56317-7
//Unix Power Tools//, Jerry Peek, Tim O'Reilly, Mike Loukides, O'Reilly, ISBN 1-56592-260-3

11. 関連項目
--------

子プロセス
Fork爆弾

12. 外部リンク
---------

[2], A library that implements thread forking for Java
[3]
[4] ケンブリッジ大学 Computing Help
[5] 1.1.3 forkによる子プロセスを終了するときにexitよりも_exitを使うのはなぜですか?