私はあまり経験がなく、ユーザーレベルからハードウェアにどのように解釈するかについてのプロセスに参加しようとしています。
したがって、コマンドがシェルから呼び出されると、fork()
はその子プロセスを継承し、exec()
は子プロセスをメモリにロードして実行します。
fork
およびexec
の概念は、UNIXのすべての実行可能プログラムに適用されますか?シェルスクリプトの場合も、コマンドの場合のみですか?シェルの組み込みコマンドにも適用されますか?一度に多くの質問をするのは申し訳ありませんが、コマンドの実行について考えると、これらすべての質問がすぐに思い浮かびます。
したがって、シェルからコマンドが起動されると、fork()はその子プロセスを継承し、exec()は子プロセスをメモリにロードして実行します。
結構です。 fork()
は現在のプロセスを複製し、同一の子を作成します。 exec()
は、現在のプロセスに新しいprogramをロードし、既存のプロセスを置き換えます。
私のqsは:
子プロセスに親プロセス(元のプロセス)のすべての属性が含まれている場合、この子プロセスの必要性は何ですか?元のプロセスもメモリに読み込まれている可能性があります。
必要なのは、親プロセスがまだ終了したくないからです。新しいプロセスが実行され、同時に実行を継続することを望んでいます。
このforkとexecの概念は、UNIXのすべての実行可能プログラムに適用されますか?シェルスクリプトと同様に、またはコマンドのみと同様ですか?シェルの組み込みコマンドにも適用されますか?
外部コマンドの場合、シェルはfork()
を実行して、コマンドが新しいプロセスで実行されるようにします。ビルトインはシェルによって直接実行されます。別の注目すべきコマンドはexec
で、最初にexec()
ingせずに外部プログラムをfork()
するようにシェルに指示します。これは、シェル自体が新しいプログラムに置き換えられているため、そのプログラムが終了したときに、そのプログラムが戻ることはできないことを意味します。 _exec true
_と言うと、_/bin/true
_はシェルを置き換えてすぐに終了し、ターミナルで何も実行されていない状態で終了します。
コマンド/スクリプトを実行する場合、コピーオンライトコンセプトを使用する場合
石器時代に戻って、fork()
は実際には、呼び出しプロセスのすべてのメモリを新しいプロセスにコピーする必要がありました。コピーオンライトは、2つのプロセスがすべて同じメモリの共有を開始するようにページテーブルがセットアップされ、どちらかのプロセスによって書き込まれたページのみが必要なときにコピーされるように最適化されています。
execve()
を呼び出して別のコードでオーバーレイします。fork()
とexec()
はすべての実行可能ファイルに適用されます。実際、argcとargv、およびパイプ、fork、execとともに、Unixを他のオペレーティングシステムと区別しています。 BSDのfork()
、プラン9のvfork()
、Linuxのrfork()
など、clone()
のいくつかの特殊化または一般化が存在しますが、プリンシパルはそのままです同じ。malloc()
で割り当てられたメモリ、または静的またはグローバルスコープ変数でさえも)は、「書き込み時にコピー」される可能性があります。子プロセスがfork()
呼び出しで作成されると、カーネルは子プロセスを設定して、ヒープとスタックとまったく同じメモリページを親プロセスとスタックします。ハードウェア(メモリ管理ユニット)がヒープまたはスタックの書き込みを検出した場合、カーネルはメモリの新しい物理ページを取得し、親のページを新しいページにコピーし、その新しいページを子プロセスのスタックまたはヒープにマップします。カーネルがページマッピングのセットアップに費やす時間は、子プロセスのスタックとヒープを完全にコピーするよりも短いので、これは最適化を構成します。子プロセスに親プロセス(元のプロセス)のすべての属性が含まれている場合、この子プロセスの必要性は何ですか?元のプロセスもメモリに読み込まれている可能性があります。
この質問は、厳しいメモリ制約の下で動作する必要があり、一度にメモリ/アドレス空間で実行中のプロセスが1つしかなかった最も初期のUnix実装を調べることで、非常に例証的に回答されています。
マルチタスクは、プロセスをディスクにスワップアウトし、別のプロセスをスワップインすることによって実現されました。
これでfork
システムコールはほとんど同じになりました。プロセスをディスクにスワップアウトしましたが、別のプロセスをスワップインする代わりに、メモリ内コピーに別のプロセスIDを与えて、それに戻りました。そして、それは、このプロセスが結局別の実行可能ファイルにexec
を入れることを決定する好機でした。
したがって、fork
+ exec
を使用しても、実際にスポーン時に顕著なオーバーヘッドが発生することはありません。プロセスをディスクにスワップアウトする必要があり、古いプロセスイメージを実行可能なメモリロケーションに保存していました。
利用可能なメモリとメモリ管理ユニット、および複数のメモリ内プロセスの量が増加するにつれて、フォークの最初は無視できるほどのコストが一部のアーキテクチャにとってやや厄介なものになりました。したがって、vfork
が生まれました。
これをできるだけ理解しやすくするために、類推を使用します。パイを焼きましょう!
私たちはレシピ本を手に取り、手作りの皮でイチゴのルバーブパイ(私のお気に入り)を読み始めます。卵と果物を除いて、必要なものはほとんどすべて台所にありますが、私たちは農場に住んでいて、果物が旬であるため、これは問題ではありません。問題は、オーブンが壊れており、すべてのことを行うのに十分な時間がないことです。私が複数いるのはいいことではないでしょうか。
fork()が助けになりました。今私は二人います。そして、私たちは二人ともキッチンに向かい、パイ生地を作り始めます。おっとっと。それでは、フォークからの戻りを見てみましょう。彼がゼロになった数が多かったので、彼が鶏小屋と庭に向かう間、私は台所に向かいます。オーブンを通り過ぎて再びfork()し、戻り値を確認します。私は壊れたオーブンを見つめながら、彼は小麦粉を続けます。私はドアを開けます、光がありません、私はドアを閉めます。オーブンを修理する方法を知っている人はいますか?
exec()を使用してください。ツールベルトの電圧計に手を伸ばしました。電球は診断の可能性があるので、電源を確認しました。実際にブレーカーが落ちて、簡単に修正できました。ブレーカーパネルに向かって歩いていると、仲間がルバーブを拾っているのが見えます。おい!私はチョコレートシルクパイが好きです。