新しいプロセスを作成するために分岐する必要があるのはなぜですか?


95

Unixでは、新しいプロセスを作成するたびに、現在のプロセスを分岐し、親プロセスとまったく同じ新しい子プロセスを作成します。次に、execシステムコールを実行して、親プロセスのすべてのデータを新しいプロセスのデータに置き換えます。

なぜ最初に親プロセスのコピーを作成し、新しいプロセスを直接作成しないのですか?


回答:


61

簡単な答えは、fork当時の既存のシステムに簡単に適合することができ、バークレーの前身システムがフォークの概念を使用していたためです。

以下からのUnixタイムシェアリングシステムの進化(該当するテキストがされた強調表示):

現代的な形のプロセス制御は、数日で設計および実装されました。既存のシステムにどれほど簡単に適合したかは驚くべきことです。同時に、デザインのわずかに珍しい機能のいくつかが、存在するものに対する小さな、簡単にコード化された変更を表しているため、正確に存在することを簡単に確認できます。良い例は、fork関数とexec関数の分離です。新しいプロセスを作成するための最も一般的なモデルには、実行するプロセスのプログラムを指定することが含まれます。Unixでは、分岐されたプロセスは、明示的なexecを実行するまで親と同じプログラムを実行し続けます。機能の分離は確かにUnixに固有のものではなく、実際にはトンプソンによく知られているバークレーのタイムシェアリングシステムに存在していました。。それでも、主に他の多くを変更せずにforkを簡単に実装できるため、Unixに存在すると想定するのは理にかなっているようです。システムはすでに複数(2つ)のプロセスを処理しました。プロセステーブルがあり、メインメモリとディスクの間でプロセスが交換されました。forkの初期実装のみが必要

1)プロセステーブルの拡張

2)既存のスワップIOプリミティブを使用して、現在のプロセスをディスクスワップ領域にコピーし、プロセステーブルにいくつかの調整を行うフォークコールの追加。

実際、PDP-7のフォークコールには、正確に27行のアセンブリコードが必要でした。もちろん、オペレーティングシステムとユーザープログラムには他の変更が必要であり、それらの一部はかなり興味深く、予期しないものでした。ただし、fork-execの組み合わせは、exec自体が存在しないという理由だけで、かなり複雑になります。その機能は、シェルによって明示的なIOを使用して既に実行されています。

その論文以来、Unixは進化してきました。fork続いてexecは、プログラムを実行する唯一の方法ではありません。

  • vforkは、新しいプロセスがforkの直後にexecを実行する場合に、より効率的なforkとして作成されました。vforkを実行すると、親プロセスと子プロセスは同じデータスペースを共有し、子プロセスがプログラムを実行するか終了するまで、親プロセスは中断されます。

  • posix_spawnは、新しいプロセスを作成し、単一のシステムコールでファイルを実行します。それは、呼び出し元の開いているファイルを選択的に共有し、そのシグナルの性質と他の属性を新しいプロセスにコピーできるようにするパラメーターの束を取ります。


5
いい答えですが、vforkはもう使用しないでください。現在、パフォーマンスの違いはわずかであり、その使用は危険です。このSO質問stackoverflow.com/questions/4856255/...、このサイトを参照してくださいewontfix.com/7のvforkについて、および「高度なUnixのプログラミング」ページ299
ラファエルアーレンス

4
posix_spawn()簡単に使用できfork()、インラインコードと同じフォーク後の再配管ジョブを実行するために使用する必要があるマチネーション(データ構造セットアップ)は、使用fork()がはるかに簡単であるという説得力のある議論になります。
ジョナサンレフラー14年

34

[ ここから回答の一部を繰り返します。]

新しいプロセスをゼロから作成するコマンドを用意しないのはなぜですか? すぐに交換するだけのものをコピーするのは不合理で非効率ではありませんか?

実際、いくつかの理由でおそらくそれほど効率的ではありません。

  1. によって生成される「コピー」fork()は少し抽象化されています。これは、カーネルがコピーオンライトシステムを使用しているためです。実際に作成する必要があるのは、仮想メモリマップだけです。コピーがすぐにを呼び出すexec()場合、プロセスのアクティビティによって変更された場合にコピーされるはずだったデータのほとんどは、プロセスがその使用を必要としないため、実際にコピー/作成する必要はありません。

  2. 子プロセスのさまざまな重要な側面(たとえば、その環境)は、コンテキストなどの複雑な分析に基づいて個別に複製または設定する必要はありません。それらは、呼び出し元プロセスのそれと同じであると見なされます。これは私たちがよく知っているかなり直感的なシステムです。

#1をもう少し説明すると、「コピーされた」がその後アクセスされないメモリは、少なくともほとんどの場合、実際にはコピーされません。この文脈での例外がありますあなたはプロセスをフォークする場合は子供がで自身を置き換える前に、その後、親プロセスの終了を持っていましたexec()。私は言うかもしれないが(OSの実装に依存するであろうもの)、十分な空きメモリがある場合、親の多くがキャッシュされる可能性があるため、私はこれが悪用されるだろうどの程度までわかりません。

もちろん、それは表面上は空白のスレートを使用するよりもコピーを使用する方が効率的ではありません。ただし、「空白のスレート」は文字通り何もないので、割り当てが必要です。システムは、同じ方法でコピーする汎用のブランク/新しいプロセステンプレートを持つことができます1が、コピーオンライトフォークと比較すると、実際には何も保存されません。したがって、#1は、「新しい」空のプロセスを使用しても効率が良くないことを示しています。

ポイント2は、フォークを使用する方が効率的である理由を説明しています。子の環境は、完全に異なる実行可能ファイルであっても、親から継承されます。たとえば、親プロセスがシェルであり、子プロセスがWebブラウザーで$HOMEある場合、どちらも同じですが、どちらかが後でそれを変更できるため、これらは2つの別々のコピーでなければなりません。子供のものはオリジナルで製作されfork()ます。

1.文字通り意味をなさないかもしれない戦略ですが、私のポイントは、プロセスを作成することは、そのイメージをディスクからメモリにコピーするだけではありません。


3
両方の点が当てはまりますが、指定された実行可能ファイルから新しいプロセスをリレートする代わりにフォーク方法が選択された理由をサポートしていません。
SkyDan 14年

3
これで質問に答えられると思います。新しいプロセスを作成するのが最も効率的な方法である場合、代わりにforkを使用するコストは簡単です(プロセス作成コストの1%未満の可能性があるため)。一方、forkが劇的に効率的であるか、APIのはるかに単純な(ファイルハンドルの処理など)多くの場所があります。Unixが下した決定は、1つのAPIのみをサポートし、仕様を単純化することでした。
コートアンモン14年

1
@SkyDanあなたは正しい、それはなぜではなく、なぜなく答えです、マーク・プロトニックはより直接的に答えます-これは最も簡単な選択であるだけでなく、おそらく最も効率的であることを意味すると解釈します選択(デニスリッチーの引用によると:「PDP-7のfork呼び出しには正確に27行のアセンブリが必要でした... execが存在しなかった;その機能は既に実行されていました」)。だから、この「なぜ」で本当に1は、表面的特徴についての2つの戦略を物思いにふけって表示されます簡単かつおそらくそれは(の...怪しげな運命を目撃されていない場合に、より効率的な
ゴルディロックス

1
Goldilocksは正しいです。フォークと変更は、新しいものを最初から作成するよりも安価な場合があります。もちろん、最も極端な例は、フォーク動作自体が必要な場合です。 fork()(GLが述べたように、27行のアセンブリのオーダーで)非常に迅速にできます。別の方向を見ると、「ゼロからプロセスを作成」する場合fork()、作成された空白のプロセスから開始するよりもほんの少しだけ費用がかかります(27行のアセンブリ+ファイルハンドルを閉じるコスト)。したがってfork、forkとcreateの両方を適切にcreate処理しますが、createのみを処理できます。
コートアンモン14年

2
あなたの答えは、ハードウェアの改善に関するものでした:仮想メモリ、コピーオンライト。これらの前に、fork実際にすべてのプロセスメモリをコピーし、非常に高価でした。
バーマー14年

6

Unixにfork新しいプロセスを作成する機能しかなかった理由は、Unixの哲学の結果だと思います

彼らは一つのことをうまく行う一つの機能を構築します。子プロセスを作成します。

新しいプロセスで何をするかはプログラマー次第です。exec*関数の1つを使用して別のプログラムを開始するか、execを使用して同じプログラムの2つのインスタンスを使用することはできません。これは便利です。

使用できるので、より大きな自由度が得られます

  1. exec *なしのフォーク
  2. exec *でforkまたは
  3. フォークなしでただexec *

さらに、1970年代にはやらなければならなかっforkexec*関数呼び出しと関数呼び出しを覚えるだけで済みます。


3
フォークの仕組みと使用方法を理解しています。しかし、同じことをより少ない労力で行えるのに、なぜ新しいプロセスを作成したいのでしょうか?例えば、私の先生は私に課題を与えました。そこでは、argvに渡された各数値に対してプロセスを作成し、その数値が素数であるかどうかを確認する必要がありました。しかし、それは最終的に同じことをするだけの迂回ではありませんか?配列を使用し、各番号に関数を使用することもできました...では、なぜメインプロセスですべての処理を行うのではなく、子プロセスを作成するのでしょうか。
user1534664

2
私は、あなたがフォークの仕組みを理解し、どのようにそれらを使用すると言うことは思い切ってしまうので、一度(数と、実行時に指定されている)あなたがプロセスの束を作成する必要がありました割り当てを与えた教師を持っていました、それらを制御し、調整し、それらの間で通信します。もちろん、実際の生活でそのような些細なことをする人はいません。ただし、並列処理が可能な断片に簡単に分解される大きな問題(イメージ内のエッジ検出など)がある場合は、分岐により複数のCPUコアを同時に使用できます。
スコット

5

プロセス作成には2つの哲学があります。継承を伴うフォークと引数を伴う作成です。Unixは明らかにforkを使用します。(たとえば、OSE、およびVMSはcreateメソッドを使用します。)Unixには多くの継承可能な特性があり、定期的に追加されます。継承により、これらの新しい特性は既存のプログラムを変更することなく追加できます!引数付きの作成モデルを使用して、新しい特性を追加すると、作成引数に新しい引数が追加されます。Unixモデルはより単純です。

また、プロセスがそれ自体を複数の部分に分割できる、非常に便利なfork-exec-execモデルも提供します。これは非同期I / Oの形式が存在しない場合に不可欠であり、システム内の複数のCPUを利用する場合に役立ちます。(プリスレッド。)私はこれを何年も、最近でもずっとやってきました。本質的には、複数の「プログラム」を単一のプログラムにコンテナー化することができるため、破損やバージョンの不一致などの余地はまったくありません。

また、fork / execモデルは、特定の子がforkとexecの間に設定された根本的に奇妙な環境を継承する能力を提供します。特に、継承されたファイル記述子のようなもの。(stdio fdの拡張。)createモデルは、create呼び出しの作成者が想定していなかったものを継承する機能を提供しません。

一部のシステムは、ネイティブコードの動的コンパイルもサポートできます。この場合、プロセスは実際に独自のネイティブコードプログラムを作成します。言い換えれば、ソースコード/コンパイラ/リンカーのサイクルを経ることなく、ディスクスペースを占有することなく、自身をその場で書き込む新しいプログラムが必要です。(これを行うVerilog言語システムがあると思います。)フォークモデルはこれをサポートしますが、モデルの作成は通常サポートしません。


ファイル記述子は「stdioの拡張」ではありません。stdioファイルポインターは、ファイル記述子のラッパーです。ファイル記述子が最初に登場し、それらは基本的なUnix I / Oハンドルです。しかし、そうでなければ、これは良い点です。
スコット

2

fork()関数は、fatherプロセスをコピーするだけでなく、プロセスがfatherまたはsonプロセスであることを示す値を返します。以下の画像は、fork()を父親として使用し、息子:

ここに画像の説明を入力してください

プロセスが父親の場合に示されているようにfork()は息子のプロセスIDをPID 返します。0

たとえば、リクエストを受信するプロセス(Webサーバー)があり、各リクエストでson processこのリクエストを処理するために作成する場合、これを利用できます。ここでは、父親とその息子は異なるジョブを持っています。

そのため、プロセスのコピーを実行することは、fork()ほど正確なことではありません。


5
本当ですが、これは質問に答えません。別の実行可能ファイルを実行する場合、プロセスの作成にフォークが必要なのはなぜですか?
SkyDan 14年

1
私はSkyDanに同意します-これは質問に答えません。posix_spawnは、fork_execve関数として30年前(Posixが存在する前)に想像されていたかもしれないもののやや手の込んだバージョンです。新しいプロセスを作成し、実行可能ファイルからイメージを初期化し、親プロセスのイメージ(引数リスト、環境、プロセス属性(作業ディレクトリなど)を除く)をコピーすることさえ示唆せずに、呼び出し元(親プロセス)への新しいプロセスのPID
スコット14年

1
「親」情報を子に渡す方法は他にもあります。戻り値の技術は、単にからそれを行うための最も効率的な方法であることを起こるfork あなたが欲しいと仮定した場合fork、最初の場所で
Cortのアモン

0

I / Oリダイレクトは、forkの後、execの前に最も簡単に実装されます。子は、子であることを認識して、ファイル記述子を閉じたり、新しい記述子を開いたり、dup()またはdup2()して、親に影響を与えることなく正しいfd番号などを取得できます。それを実行し、おそらく必要な環境変数が変更された後(親にも影響しない)、調整された環境で新しいプログラムを実行できます。


ここで行っているのは、ジムキャシーの答えの 3番目の段落をもう少し詳しく説明することです。
スコット

-2

ここの誰もがフォークがどのように機能するかを知っていると思いますが、問題はフォークを使用して親の正確な複製を作成する必要があるのはなぜですか? 回答 ==>サーバーの例(フォークなし)を使用して、クライアント1がサーバーにアクセスしているときに、2番目のクライアント2が同時に到着し、サーバーにアクセスしたいが、サーバーが新しく到着したサーバーに許可を与えない場合サーバーがクライアント1を処理するためにビジーであるため、クライアント2は待機しなければなりません。クライアント1へのすべてのサービスが終了した後、クライアント2はサーバーにアクセスできるようになりました。 client-3が到着するため、client-3はclient-2へのすべてのサービスが終了するまで待機する必要があります。数千のクライアントが同時にサーバーにアクセスする必要があるシナリオを考えます。 wait(サーバーはビジーです!!)。

これは、(フォークを使用して)サーバーの正確な複製コピー(子)を作成することにより回避されます。各子(親、つまりサーバーの正確な複製コピー)は、新しく到着したクライアント専用であるため、同時にすべてのクライアントが同じサーバ。


これが、サーバープロセスをシングルスレッドにすべきではない理由です。クライアントリクエストを同時に処理できる場合(たとえば、別々のプロセスで)連続して処理する必要があります。ただし、マルチスレッドサーバーモデルは、クライアントからの要求を受け入れ、クライアントサービスプログラムを実行するための新しいプロセスを作成するリスナープロセスを使用して簡単に実装できます。親プロセスをコピーする呼び出しによって提供される唯一の利点は、2つの別個のプログラムを用意する必要がないことです。ただし、別個のプログラム(など)を使用すると、システムをよりモジュール化できます。forkinetd
スコット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.