
Unix では、新しいプロセスを作成するときは常に、現在のプロセスをフォークして、親プロセスとまったく同じ新しい子プロセスを作成します。次に、exec システム コールを実行して、親プロセスのすべてのデータを新しいプロセスのデータに置き換えます。
そもそも親プロセスのコピーを作成し、新しいプロセスを直接作成しないのはなぜでしょうか?
答え1
簡単に答えると、fork
Unixは当時の既存のシステムに簡単に適合し、バークレーの前身システムフォークの概念を使用していました。
からUnix タイムシェアリング システムの進化(関連テキストは強調表示):
プロセス制御の現代的な形態は、数日で設計され、実装されました。既存のシステムに簡単に適合したことは驚くべきことです。同時に、デザインの少し変わった特徴のいくつかは、既存のものに対する小さくて簡単にコード化できる変更を表しているために存在する。良い例としては、fork 関数と exec 関数の分離が挙げられます。新しいプロセスを作成する最も一般的なモデルは、プロセスが実行するプログラムを指定することです。Unix では、フォークされたプロセスは、明示的な exec を実行するまで、親と同じプログラムを実行し続けます。関数の分離は Unix に固有のものではなく、実際、それはトンプソンがよく知っていたバークレーのタイムシェアリングシステムに存在していた。それでも、Unixに存在する主な理由は、フォークが他の部分をあまり変更せずに簡単に実装できるためである。システムはすでに複数(つまり2つ)のプロセスを処理しており、プロセステーブルがあり、プロセスはメインメモリとディスク間でスワップされていました。フォークの初期の実装では、
1) プロセステーブルの拡張
2) 既存のスワップ IO プリミティブを使用して現在のプロセスをディスク スワップ領域にコピーするフォーク呼び出しを追加し、プロセス テーブルにいくつかの調整を加えました。
実際、PDP-7のフォーク呼び出しには、正確に27行のアセンブリコードが必要でした。もちろん、オペレーティングシステムとユーザープログラムには他の変更が必要であり、その中にはかなり興味深く予想外のものもありました。しかし、フォークと実行を組み合わせた場合、かなり複雑になるexec 自体が存在しなかったという理由だけで、その機能は、明示的な IO を使用して、シェルによってすでに実行されていました。
その論文以来、Unix は進化してきました。fork
に続くのは、exec
もはやプログラムを実行する唯一の方法ではありません。
vフォーク新しいプロセスがフォーク直後に exec を実行する場合の、より効率的なフォークとなるように作成されました。vfork を実行すると、親プロセスと子プロセスは同じデータ領域を共有し、子プロセスがプログラムを実行するか終了するまで親プロセスは中断されます。
posix_spawn単一のシステム コールで新しいプロセスを作成し、ファイルを実行します。一連のパラメーターを使用して、呼び出し元の開いているファイルを選択的に共有し、そのシグナル処理やその他の属性を新しいプロセスにコピーできます。
答え2
[私の回答の一部を再度繰り返しますここ。
最初から新しいプロセスを作成するコマンドを用意するだけでは不十分でしょうか? すぐに交換されるものをコピーするのは不合理で非効率的ではないでしょうか?
実際のところ、それはおそらくいくつかの理由からそれほど効率的ではないでしょう。
によって生成される「コピー」は、
fork()
カーネルが使用するため、少し抽象化されています。コピーオンライトシステム; 実際に作成する必要があるのは仮想メモリ マップだけです。コピーがすぐに を呼び出す場合exec()
、プロセスのアクティビティによって変更されていた場合にコピーされるデータのほとんどは、プロセスがその使用を必要とする操作を行わないため、実際にはコピー/作成する必要はありません。子プロセスのさまざまな重要な側面 (たとえば、その環境) は、コンテキストなどの複雑な分析に基づいて個別に複製または設定する必要はありません。呼び出し元プロセスと同じであると想定されるだけであり、これは私たちがよく知っている非常に直感的なシステムです。
1 についてもう少し詳しく説明すると、「コピー」されたがその後アクセスされないメモリは、少なくともほとんどの場合、実際にはコピーされません。このコンテキストでは例外です。かもしれないプロセスをフォークし、親プロセスが終了してから子プロセスが自分自身を置き換える場合、exec()
私はこう言いますかもしれない十分な空きメモリがあれば親の大部分をキャッシュできる可能性があり、これがどの程度まで利用されるかは不明です (OS の実装によって異なります)。
もちろん、表面的にはコピーを使うことはもっと空白のスレートを使用するよりも効率的です。ただし、「空白のスレート」は文字通り何もないわけではなく、割り当てを伴う必要があります。システムは、同じ方法でコピーする汎用の空白/新しいプロセス テンプレートを持つことができますが、1では、コピーオンライト フォークと比較して実際には何も保存されません。したがって、#1 は、「新しい」空のプロセスを使用することが効率的ではないことを示しています。
ポイント 2 では、フォークを使用する方が効率的である理由を説明しています。子の環境は、親がまったく異なる実行可能ファイルであっても、親から継承されます。たとえば、親プロセスがシェルで、子が Web ブラウザーである場合、環境は$HOME
両方のプロセスで同じままですが、どちらかが後で環境を変更する可能性があるため、これらは 2 つの別々のコピーである必要があります。子の環境は、元の環境によって生成されますfork()
。
1. 文字通りにはあまり意味をなさない戦略ですが、私が言いたいのは、プロセスの作成には、そのイメージをディスクからメモリにコピーする以上のことが含まれるということです。
答え3
fork
Unixに新しいプロセスを作成する機能しかなかった理由は、Unix哲学
1 つのことをうまく実行する 1 つの関数を構築します。これにより、子プロセスが作成されます。
新しいプロセスで何をするかはプログラマー次第です。関数の 1 つを使用しexec*
て別のプログラムを起動することもできますし、exec を使用せずに同じプログラムの 2 つのインスタンスを使用することもできます。これは便利です。
より自由度が増すので、
- exec なしで fork*
- exec*でforkするか、
- fork なしで exec* のみ
さらに、fork
とexec*
関数呼び出しを記憶するだけで済みます。これは 1970 年代に必須だったことです。
答え4
fork() 関数は、父プロセスをコピーするだけでなく、プロセスが父プロセスか子プロセスかを示す値を返します。以下の図は、fork() を父プロセスと子プロセスとしてどのように使用できるかを説明しています。
示されているように、プロセスが父である場合、fork()は子プロセスIDを返し、PID
そうでない場合は0
たとえば、リクエストを受信し、リクエストごとにそのson process
リクエストを処理するプロセス (Web サーバー) がある場合にこれを利用できます。ここでは、親プロセスとその子プロセスが異なるジョブを担当します。
したがって、プロセスのコピーを実行することは、fork() とまったく同じではありません。