fork
とexec
の違いは何ですか?
fork
とexec
の使用は、新しいプロセスを開始する非常に簡単な方法を提供するという点でUNIXの精神を例示しています。
fork
呼び出しは、基本的に現在のプロセスの複製を作成し、すべての方法でalmostで同一です。すべてがコピーされるわけではありません(たとえば、一部の実装でのリソース制限)が、アイデアはできる限り近いコピーを作成することです。
新しいプロセス(子)は異なるプロセスID(PID)を取得し、古いプロセス(親)のPIDをその親PID(PPID)として持っています。 2つのプロセスはまったく同じコードを実行しているため、fork
の戻りコードによってどちらがどちらであるかを判断できます。子は0を取得し、親は子のPIDを取得します。もちろん、これはすべてfork
呼び出しが機能することを前提としています。動作しない場合、子は作成されず、親はエラーコードを受け取ります。
exec
呼び出しは、基本的に現在のプロセス全体を新しいプログラムに置き換える方法です。プログラムを現在のプロセススペースにロードし、エントリポイントから実行します。
したがって、fork
とexec
は、現在のプロセスの子として実行される新しいプログラムを取得するために、順番に使用されることがよくあります。通常、シェルはfind
などのプログラムを実行しようとするたびにこれを行います。シェルはフォークし、その後、子はfind
プログラムをメモリにロードし、すべてのコマンドライン引数、標準I/Oなどを設定します。
ただし、一緒に使用する必要はありません。たとえば、プログラムに親コードと子コードの両方が含まれる場合、プログラムがfork
ingなしでexec
自体になることは完全に許容できます(実行に注意する必要があります。各実装には制限があります)。これは、TCPポートとfork
のコピーを単にリッスンするデーモンで、親がリッスンに戻っている間に特定のリクエストを処理するために、非常に多く使用されています(今でも使用されています)。
同様に、終了したことを知っており、別のプログラムを実行したいだけのプログラムは、子供に対してfork
、exec
、そしてwait
をする必要はありません。子を直接プロセス空間にロードできます。
一部のUNIX実装には、コピーオンライトと呼ばれるものを使用する最適化されたfork
があります。これは、プログラムがそのスペース内の何かを変更しようとするまで、fork
内のプロセススペースのコピーを遅延させるトリックです。これは、プロセス空間全体をコピーする必要がないという点で、fork
ではなくexec
のみを使用するプログラムに役立ちます。
exec
isがfork
に続いて呼び出された場合(これがほとんどの場合です)、プロセス空間への書き込みが発生し、それが子プロセスにコピーされます。
exec
呼び出しのファミリー全体(execl
、execle
、execve
など)がありますが、ここでのexec
はそれらのいずれかを意味します。
次の図は、bash
コマンドを使用してディレクトリをリストするためにls
シェルが使用される典型的なfork/exec
操作を示しています。
+--------+
| pid=7 |
| ppid=4 |
| bash |
+--------+
|
| calls fork
V
+--------+ +--------+
| pid=7 | forks | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash | | bash |
+--------+ +--------+
| |
| waits for pid 22 | calls exec to run ls
| V
| +--------+
| | pid=22 |
| | ppid=7 |
| | ls |
V +--------+
+--------+ |
| pid=7 | | exits
| ppid=4 | <---------------+
| bash |
+--------+
|
| continues
V
fork()
は、現在のプロセスを2つのプロセスに分割します。または、言い換えると、プログラムについて考えやすいニースリニアは、突然、1つのコードを実行する2つの別個のプログラムになります。
int pid = fork();
if (pid == 0)
{
printf("I'm the child");
}
else
{
printf("I'm the parent, my child is %i", pid);
// here we can kill the child, but that's not very parently of us
}
これはあなたの心を吹き飛ばすことができます。これで、2つのプロセスによって実行されるほぼ同一の状態の1つのコードができました。子プロセスは、fork()
呼び出しが中断したところから開始することを含め、作成したばかりのプロセスのすべてのコードとメモリを継承します。唯一の違いは、あなたが親か子かを知らせるfork()
戻りコードです。あなたが親の場合、戻り値は子のIDです。
exec
は少しわかりやすく、ターゲットの実行可能ファイルを使用してプロセスを実行するようにexec
に指示するだけで、同じコードを実行したり、同じ状態を継承したりする2つのプロセスはありません。 @Steve Hawkinsが言うように、exec
は、fork
の後に使用して、現在のプロセスでターゲット実行可能ファイルを実行できます。
"Advanced Unix Programming" by Marc Rochkind のいくつかの概念は、fork()
/exec()
のさまざまな役割を理解するのに、特にWindows CreateProcess()
型:
A program is a collection of instructions and data that is kept in a regular file on disk. (from 1.1.2 Programs, Processes, and Threads)
。
プログラムを実行するために、カーネルは最初に、プログラムが実行される環境である新しいprocessの作成を要求されます。 (1.1.2プログラム、プロセス、およびスレッドからも)
。
プロセスとプログラムの違いを完全に理解しないと、execまたはforkシステムコールを理解することはできません。これらの条件が初めての場合は、戻ってセクション1.1.2を確認してください。すぐに続行する準備ができている場合、区別を1つの文に要約します。プロセスは、命令、ユーザーデータ、システムデータセグメント、および実行時に取得される他の多くのリソースで構成される実行環境です。 、プログラムは、プロセスの命令とユーザーデータセグメントを初期化するために使用される命令とデータを含むファイルです。 (5.3
exec
システムコールから)
プログラムとプロセスの違いを理解すると、fork()
およびexec()
関数の動作は次のように要約できます。
fork()
は、現在のプロセスの複製を作成しますexec()
は、現在のプロセスのプログラムを別のプログラムに置き換えます(これは基本的に paxdiabloのより詳細な回答 )の簡易版の「ダミー」バージョンです。
Forkは、呼び出しプロセスのコピーを作成します。一般的に構造に従います
int cpid = fork( );
if (cpid = = 0)
{
//child code
exit(0);
}
//parent code
wait(cpid);
// end
(子プロセスtext(code)、data、stackは呼び出しプロセスと同じです)子プロセスはifブロックでコードを実行します。
EXECは、現在のプロセスを新しいプロセスのコード、データ、スタックに置き換えます。一般的に構造に従います
int cpid = fork( );
if (cpid = = 0)
{
//child code
exec(foo);
exit(0);
}
//parent code
wait(cpid);
// end
(exec呼び出し後、unixカーネルは子プロセスのテキスト、データ、スタックをクリアし、fooプロセス関連のテキスト/データで埋めます)したがって、子プロセスは異なるコード(fooのコード{親とは異なります})
これらは一緒に使用して、新しい子プロセスを作成します。まず、fork
を呼び出すと、現在のプロセス(子プロセス)のコピーが作成されます。次に、exec
が子プロセス内から呼び出され、親プロセスのコピーを新しいプロセスに「置き換え」ます。
プロセスは次のようになります。
child = fork(); //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail
if (child < 0) {
std::cout << "Failed to fork GUI process...Exiting" << std::endl;
exit (-1);
} else if (child == 0) { // This is the Child Process
// Call one of the "exec" functions to create the child process
execvp (argv[0], const_cast<char**>(argv));
} else { // This is the Parent Process
//Continue executing parent process
}
fork()は現在のプロセスのコピーを作成し、fork()呼び出しの直後から新しい子で実行されます。 fork()の後は、fork()関数の戻り値を除いて同じです。 (詳細については、RTFM。)2つのプロセスはさらに分岐する可能性があり、共有ファイルハンドルを介した場合を除き、一方が他方に干渉することはありません。
exec()は、現在のプロセスを新しいプロセスに置き換えます。 fork()とは何の関係もありません。ただし、現在のプロセスを置き換えるのではなく、別の子プロセスを起動する場合、exec()がfork()の後に続くことがよくあります。
fork()
とexec()
の主な違いは、
fork()
システムコールは、現在実行中のプログラムのクローンを作成します。元のプログラムは、fork()関数呼び出しの次のコード行で実行を継続します。クローンは、コードの次の行からも実行を開始します。 http://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/ から取得した次のコードを見てください
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
printf("--beginning of program\n");
int counter = 0;
pid_t pid = fork();
if (pid == 0)
{
// child process
int i = 0;
for (; i < 5; ++i)
{
printf("child process: counter=%d\n", ++counter);
}
}
else if (pid > 0)
{
// parent process
int j = 0;
for (; j < 5; ++j)
{
printf("parent process: counter=%d\n", ++counter);
}
}
else
{
// fork failed
printf("fork() failed!\n");
return 1;
}
printf("--end of program--\n");
return 0;
}
このプログラムは、fork()
ingの前に、ゼロに設定されたカウンター変数を宣言します。 fork呼び出しの後、2つのプロセスが並行して実行され、どちらもカウンターの独自のバージョンをインクリメントします。各プロセスは完了するまで実行され、終了します。プロセスは並行して実行されるため、どちらが先に終了するかを知る方法はありません。このプログラムを実行すると、以下に示すような結果が出力されますが、実行ごとに結果が異なる場合があります。
--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--
exec()
ファミリーのシステムコールは、プロセスの現在実行中のコードを別のコードに置き換えます。プロセスはPIDを保持しますが、新しいプログラムになります。たとえば、次のコードを検討してください。
#include <stdio.h>
#include <unistd.h>
main() {
char program[80],*args[3];
int i;
printf("Ready to exec()...\n");
strcpy(program,"date");
args[0]="date";
args[1]="-u";
args[2]=NULL;
i=execvp(program,args);
printf("i=%d ... did it work?\n",i);
}
このプログラムは、execvp()
関数を呼び出して、コードを日付プログラムに置き換えます。コードがexec1.cという名前のファイルに保存されている場合、実行すると次の出力が生成されます。
Ready to exec()...
Tue Jul 15 20:17:53 UTC 2008
プログラムは、「Ready to exec()」という行を出力します。 。 。そして、execvp()関数を呼び出した後、コードを日付プログラムに置き換えます。行に注意してください―。 。 。その時点でコードが置き換えられたため、機能しましたか?は表示されません。代わりに、「date -u」を実行した結果が表示されます。
実行中のプロセスのコピーを作成します。実行中のプロセスは親プロセスと呼ばれ、新しく作成されたプロセスは子プロセスと呼ばれます。 2つを区別する方法は、戻り値を調べることです。
fork()
は、親の子プロセスのプロセス識別子(pid)を返します
fork()
は、子で0を返します。
exec()
:
プロセス内で新しいプロセスを開始します。新しいプログラムを現在のプロセスにロードし、既存のプロセスを置き換えます。
fork()
+ exec()
:
新しいプログラムを起動するときは、最初にfork()
を実行し、新しいプロセスを作成してから、実行するプログラムバイナリをexec()
(つまり、メモリにロードして実行)します。
int main( void )
{
int pid = fork();
if ( pid == 0 )
{
execvp( "find", argv );
}
//Put the parent to sleep for 2 sec,let the child finished executing
wait( 2 );
return 0;
}
fork()
およびexec()
の概念を理解するための主要な例は、Shell、ユーザーがシステムにログインした後に通常実行するプログラム。シェルはcommand lineの最初のWordをcommand名として解釈します
多くのコマンドでは、Shell-forksおよび子プロセスexecsコマンドラインの残りの単語をコマンドのパラメーターとして扱う名前に関連付けられたコマンド.
Shellでは、3種類のコマンドを使用できます。まず、コマンドは実行可能ファイルにすることができます。これには、ソースコードのコンパイルによって生成されたオブジェクトコードが含まれます(たとえばCプログラム)。次に、コマンドは、一連のシェルコマンドラインを含む実行可能ファイルにすることができます。最後に、コマンドは内部シェルコマンドにすることができます(実行可能ファイルの代わりにex-> cd、lsなど)