Fork

fork(フォーク)とは



forkとは、プロセスを複製する操作のことです。特に、UNIXおよびUnix系オペレーティングシステム(OS)において、システムコールの一つとして重要な役割を果たします。fork()システムコールは、呼び出したプロセス(親プロセス)のコピーである新しいプロセス(子プロセス)を生成します。

fork()の動作



fork()システムコールが呼び出されると、以下の処理が行われます。

1. プロセスの生成:プロセスの実行コード、データ、スタックなどの状態をコピーした子プロセスが生成されます。
2. 戻り値: fork()システムコールは、親プロセスには子プロセスプロセスIDを返し、子プロセスには0を返します。エラーが発生した場合は、-1を返します。この戻り値によって、親プロセスと子プロセスは異なる処理を実行できます。
3. アドレス空間:プロセスは、親プロセスアドレス空間のコピーを持ちますが、実際には物理メモリのコピーは遅延され、コピーオンライト(COW)という仕組みによって、共有されたメモリ領域を一時的に使用します。これにより、fork()の実行効率が向上します。

Unixにおけるforkの重要性



Unix系OSでは、fork()は重要な概念であり、シェルによるパイプ処理など、多くの機能の基盤となっています。以下は、fork()がどのように利用されているかの例です。

パイプ処理



シェルでパイプを使用する場合、シェルはfork()を呼び出して複数の子プロセスを生成します。これらの子プロセスは、パイプを通じてデータのやり取りを行い、複雑な処理を連携して実行します。例えば、`find . -name ".cpp" | wc -l`というコマンドでは、findコマンドの出力がwcコマンドの入力にパイプで接続され、cppファイルの数が表示されます。

コマンド実行



シェルは、ユーザーがコマンドを入力すると、自分自身をfork()して子プロセスを生成します。子プロセスは、`exec()`ファミリのシステムコールを使って、実行したいプログラムで自身を上書きします。これにより、新たなプログラムを実行するプロセスが生成されます。

プロセスアドレス空間



プロセスアドレス空間は、実行ファイルに含まれるセグメントと呼ばれる領域に分割されています。各セグメントは異なる種類のデータを格納します。

`.text`: 実行可能なコード
`.bss`: 初期値がゼロのデータ
`.data`: 初期値のあるデータ
`.symtab`: シンボルテーブル(関数名、変数名など)
`.interp`: インタプリタの名前

これらのセグメントは、実行時にメモリにロードされ、プロセスアドレス空間を構成します。

メモリのページング



メモリはページと呼ばれる固定サイズのブロックに分割されます。実行ファイルがメモリにロードされる際、実行ファイル内のデータは複数のページに分散して格納されます。これにより、メモリの効率的な利用が可能になります。

forkとページ共有



fork()システムコールでは、子プロセスは親プロセスの全ページをコピーする必要があるように思えます。しかし、多くの場合、子プロセスはすぐに`exec()`を呼び出して自身を上書きするか、終了します。このため、メモリのコピーは無駄になることがあります。

コピーオンライト (COW)



[コピーオンライト]技術では、fork()時に物理メモリのコピーは行わず、親プロセスと子プロセスでメモリページを共有します。どちらかのプロセスが共有ページを書き込もうとした場合にのみ、そのページのコピーを作成します。これにより、fork()のコストを大幅に削減できます。

vforkとページ共有



vfork()はfork()に似たシステムコールですが、動作が異なります。

vfork()の動作



vfork()では、子プロセスが終了するか、`exec()`ファミリのシステムコールで別のプログラムに切り替わるまで、親プロセスが一時停止します。また、vfork()ではメモリのコピーオンライトは行わず、親子間でメモリが共有されます。そのため、子プロセスがメモリを更新すると、親プロセスからもその変更が見えます。

vfork()の危険性



vfork()は高速ですが、誤った使い方をすると親プロセスを破壊する危険性があります。例えば、子プロセスでスタックを操作したり、共有メモリを不用意に書き換えると、親プロセスが予期しない動作をすることがあります。そのため、vfork()の利用は推奨されず、特にLinuxではvfork()の使用自体が推奨されていません。

MMUのないシステム



組み込みシステムなど、メモリ管理ユニット(MMU)のないシステムでは、コピーオンライトは実装できない場合があります。この場合、fork()の代わりにvfork()のみが実装されることがあります。MMUがない場合、プロセスは単一のアドレス空間を共有することがあり、コンテキストスイッチにはメモリ全体のコピーが必要になることがあります。

Unix以外でのフォーク



UNIX系OSのfork()は、リニアなメモリ空間やページング機構など、特定のハードウェアの前提に依存しています。VMSなどのOSでは、プロセスの状態がコピーされることでエラーが発生する可能性があるため、fork()の代わりにプロセスのスポーンという概念が使われます。スポーンは、新しいプロセスアドレス空間をゼロから構築するもので、より安全ですが、フォークよりも効率が劣ります。

fork を使ったコード例



以下に、C、Perl、Pythonでのfork()の使用例を示します。

Cの例


c

include


include


include



int main() {
pid_t pid = fork();

if (pid == 0) {
printf("子プロセス: PID = %d
", getpid());
} else if (pid > 0) {
printf("親プロセス: PID = %d, 子プロセスのPID = %d
", getpid(), pid);
} else {
perror("fork failed");
return 1;
}

return 0;
}


Perlの例


perl
my $pid = fork();

if ($pid == 0) {
print "子プロセス: PID = $$
";
} elsif ($pid > 0) {
print "親プロセス: PID = $$", 子プロセスのPID = $pid
";
} else {
die "fork failed: $!";
}


Pythonの例


python
import os

pid = os.fork()

if pid == 0:
print(f"子プロセス: PID = {os.getpid()}")
elif pid > 0:
print(f"親プロセス: PID = {os.getpid()}, 子プロセスのPID = {pid}")
else:
print("fork failed")


Fork-Exec



Fork-Execは、UNIXで新たなプログラムを実行する一般的な方法です。`fork()`で子プロセスを生成した後、`exec()`システムコールを呼び出して子プロセス自身を新たなプログラムで上書きします。これにより、独立したプロセスでプログラムを実行できます。

プロセスの終了



プロセスは、子プロセスの終了を`wait()`システムコールで待ち合わせることができます。これにより、子プロセスの終了コードを受け取り、ゾンビプロセス化を防ぎます。子プロセスが`exec()`を呼び出すと、アドレス空間は完全に新しいプログラムで上書きされますが、ファイル記述子などは`close-on-exec`フラグが設定されていない限り、親プロセスから継承されます。

Microsoft Windows



Microsoft Windowsでは、UNIXのような`fork()`システムコールは提供されていません。代わりに、`spawn()`ファミリの関数が使用され、同様のプロセス生成とプログラム実行を実現します。

まとめ



fork()は、UNIX系OSにおけるプロセスの複製と並列処理の基礎となる重要なメカニズムです。コピーオンライトやvfork()などの技術により、効率的なプロセス生成を実現しています。fork-execモデルは、UNIXシステムの柔軟性と強力さを支える重要な要素であり、様々なアプリケーションの開発に不可欠です。

もう一度検索

【記事の利用について】

タイトルと記事文章は、記事のあるページにリンクを張っていただければ、無料で利用できます。
※画像は、利用できませんのでご注意ください。

【リンクついて】

リンクフリーです。