exec()関数とそのファミリについて説明してください


98

何であるexec()関数とその家族は?なぜこの関数が使用され、どのように機能するのですか?

誰でもこれらの機能を説明してください。


4
スティーブンスをもう一度読んでみて、理解していないことを明確にしてください。
vlabrecque 2010年

回答:


244

簡単に言えば、UNIXにはプロセスとプログラムの概念があります。プロセスは、プログラムが実行される環境です。

UNIXの「実行モデル」の背後にある単純な考え方は、実行できる操作が2つあるということです。

1つ目はtoですfork()。これは、現在のプログラムの複製(ほとんどが)を含み、その状態を含むまったく新しいプロセスを作成します。2つのプロセスにはいくつかの違いがあり、どちらが親でどちらが子であるかを判別できます。

2つ目はto exec()で、現在のプロセスのプログラムをまったく新しいプログラムに置き換えます。

これらの2つの単純な操作から、UNIX実行モデル全体を構築できます。


上記に詳細を追加するには:

使用fork()exec()新しいプロセスを開始する非常に簡単な方法を提供するという点で、UNIXの精神を例示しています。

fork()コールは、ほぼすべての方法(ではないが同じで、現在のプロセスの近くの重複を作るすべてが、たとえば、リソースのいくつかの実装では限界が、アイデアは近づけできるだけコピーを作成することで、コピーされます)。1つのプロセスのみが呼び出します fork()が、その呼び出しから2つのプロセスが戻ります-奇妙に聞こえますが、それは本当に非常にエレガントです

新しいプロセス(子と呼ばれる)は別のプロセスID(PID)を取得し、古いプロセス(親)のPIDをその親PID(PPID)として持ちます。

2つのプロセスはまったく同じコードを実行しているので、どちらが-の戻りコードがfork()この情報を提供するか-子は0を取得し、親は子のPIDを取得します(fork()失敗した場合、いいえ子が作成され、親はエラーコードを受け取ります)。

このようにして、親は子のPIDを認識し、それと通信したり、強制終了したり、待機したりできます(子は常にへの呼び出しで親プロセスを見つけることができますgetppid())。

exec()呼び出しは、新しいプログラムとプロセスの全体の現在の内容を置き換えます。プログラムを現在のプロセス空間にロードし、エントリポイントから実行します。

だから、fork()そしてexec()多くの場合、現在のプロセスの子として実行する新しいプログラムを取得するために順番に使用されています。シェルは通常find、次のようなプログラムを実行しようとするときにこれを行います-シェルがフォークし、子がfindプログラムをメモリにロードして、すべてのコマンドライン引数、標準I / Oなどを設定します。

ただし、一緒に使用する必要はありません。たとえば、プログラムに親コードと子コードの両方が含まれている場合、プログラムがfork()フォローなしで呼び出すことは完全に許容されexec()ます(実装には注意が必要です。実装ごとに制限がある場合があります)。

これは、TCPポートでリッスンし、親のリッスンに戻っている間に特定の要求を処理するために自分のコピーをフォークするデーモンにかなり多く(そして今でも使用されています)されています。この状況では、プログラムには親コード子コードの両方が含まれています。

同様に、彼らは完成している知っているとプログラムがちょうどする必要はありません別のプログラムを実行したいfork()exec()その後、wait()/waitpid()子供のために。を使用して、現在のプロセス空間に直接子をロードできますexec()

一部のUNIX実装には、fork()コピーオンライトと呼ばれるものを使用する最適化があります。これはfork()、プログラムがその空間の何かを変更しようとするまで、プロセス空間のコピーを遅らせるトリックです。これは、使用してそれらのプログラムのために有用でfork()はないexec()、彼らは全体のプロセス空間をコピーする必要がないという点で。Linuxでは、fork()ページテーブルと新しいタスク構造のコピーを作成するだけexec()で、2つのプロセスのメモリを「分離」する面倒な作業を行います。

exec 次のように呼び出された場合fork(これが主に発生します)、プロセススペースへの書き込みが発生し、変更が許可される前に子プロセスにコピーされます。

Linuxには、vfork()さらに最適化されたもあり、2つのプロセス間でほぼすべてを共有します。そのため、子供ができることには一定の制限があり、子供がexec()またはを呼び出すまで、親は停止します_exit()

2つのプロセスが同じスタックを共有しているため、親を停止する必要があります(子は現在の関数から戻ることができません)。これは、fork()直後にが続くという従来の使用例では、少し効率的ですexec()

全体の家族があることに注意してくださいexec呼び出しは(execlexecleexecveなど)が、exec文脈でここではそれらのいずれかを意味します。

次の図fork/execは、bashシェルを使用してlsコマンドでディレクトリを一覧表示する一般的な操作を示しています。

+--------+
| 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

12
このような詳細な説明をありがとうございました:)
Faizan 2013年

2
findプログラムのシェルリファレンスをありがとう。正確に私が理解する必要があったもの。
ユーザー

exec現在のプロセスのIOをリダイレクトするためにユーティリティが使用されるのはなぜですか?引数なしでexecを実行する「null」の場合、どのようにしてこの規則に慣れましたか?
レイ

@Ray、私はいつもそれを自然な拡張と考えてきました。あなたが考える場合はexec別で、このプロセスでは、現在のプログラム(シェル)を交換するための手段として、そしてない他のプログラムが缶と交換することを指定すると、単にあなたが意味していない、それを交換したいです。
paxdiablo 2015

「自然な拡張」とは、「有機的成長」の線に沿って何かを意味する場合、私はあなたが何を意味するのかを理解しています。プログラム置換機能をサポートするためにリダイレクトが追加されたようですexecが、プログラムなしで呼び出された場合の退化したケースにこの動作が残っていることがわかります。しかし、このシナリオでは少し奇妙です。なぜなら、新しいプログラム(実際にはexecutされるプログラム)へのリダイレクトの本来の有用性が消え、有用なアーティファクトがあり、現在のプログラム(リダイレクトexecまたは開始されていない)をリダイレクトするからです。いずれにせよ-代わりに。
レイ

36

exec()ファミリーの関数には、さまざまな動作があります。

  • l:引数は文字列のリストとしてmain()に渡されます
  • v:引数は文字列の配列としてmain()に渡されます
  • p:新しい実行中のプログラムを検索するためのパス/秒
  • e:呼び出し元が環境を指定できます

あなたはそれらを混合することができます、したがってあなたは持っています:

  • int execl(const char * path、const char * arg、...);
  • int execlp(const char * file、const char * arg、...);
  • int execle(const char * path、const char * arg、...、char * const envp []);
  • int execv(const char * path、char * const argv []);
  • int execvp(const char * file、char * const argv []);
  • int execvpe(const char * file、char * const argv []、char * const envp []);

それらすべての最初の引数は、実行されるファイルの名前です。

詳細については、exec(3)のマニュアルページを参照してください

man 3 exec  # if you are running a UNIX system

1
興味深いことに、execve()POSIXで定義されているリストから外し、POSIXでexecvpe()定義されていないを追加しました(主にこれまでの先例の理由により、一連の関数が完了しています)。それ以外の場合は、ファミリの命名規則についての役立つ説明— paxdiabloの便利な補助' 関数の働きについて詳しく説明する回答
Jonathan Leffler、2018年

そして、あなたの弁護のために、execvpe()(et al)のLinuxマニュアルページにはリストされていませんexecve()。独自の個別のマニュアルページがあります(少なくともUbuntu 16.04 LTSの場合)。他のexec()ファミリ関数はセクション3(関数)にexecve()記載されているのに対し、セクション2(システムコール)に記載されている点が異なります。基本的に、ファミリーの他のすべての関数は、への呼び出しに関して実装されexecve()ます。
Jonathan Leffler、2018年

18

exec機能の家族はあなたのプロセスが実行されていた古いプログラムを置き換え、別のプログラムを実行させます。つまり、あなたが呼び出す場合

execl("/bin/ls", "ls", NULL);

次に、lsプログラムは、プロセスID、現在の作業ディレクトリ、およびを呼び出したプロセスのユーザー/グループ(アクセス権)を使用して実行されexeclます。その後、元のプログラムは実行されなくなります。

新しいプロセスを開始するには、forkシステムコールが使用されます。オリジナルを交換せずにプログラムを実行するには、次のようにする必要がありfork、その後、exec


本当に助かりましたありがとうございます。私は現在exec()を使用する必要があるプロジェクトをやっており、あなたの説明は私の理解を固めました。
TwilightSparkleTheGeek

7

exec関数とそのファミリーは何ですか。

exec関数ファミリは、以下のようなファイルを実行するために使用されるすべての機能、されたexeclexeclpexecleexecv、とexecvp。彼らはすべてのためのフロントエンドですexecveし、それを呼び出すの異なる方法を提供します。

この関数が使用される理由

exec関数は、ファイル(プログラム)を実行(起動)するときに使用されます。

そしてそれはどのように機能しますか。

これらは、現在のプロセスイメージを起動したイメージで上書きすることで機能します。現在実行中のプロセス(execコマンドを呼び出したプロセス)を、起動した新しいプロセスで(終了することにより)置き換えます。

詳細については、このリンクを参照してください


7

execはと組み合わせて使用​​されることがよくありforkますが、これについても同様に質問されたので、これを念頭に置いて説明します。

exec現在のプロセスを別のプログラムに変えます。ドクター・フーを見たことがありますか?これは彼が再生するときのようです-彼の古い体は新しい体に置き換えられます。

これがプログラムで発生する方法であり、プログラムの引数(最初の引数)としてexec渡したファイルがexec現在のユーザー(プロセスのユーザーID)によって実行可能かどうかをOSカーネルが確認する多くのリソース作りexecコール)とあれば、それは仮想メモリを現在のプロセスの仮想メモリマッピングを置き換えるように、新しいプロセスとコピーargvenvpに渡されたデータexecこの新しい仮想メモリマップのエリアに呼び出し。他にもいくつかのことがここで発生する可能性がありますが、呼び出されたプログラムに対して開かれていたファイルexecは新しいプログラムに対しても開かれ、同じプロセスIDを共有しますが、呼び出されたプログラムexecは停止します(execが失敗しない限り)。

これがこの方法で行われる理由は新しいプログラムの実行をこのように2つのステップに分離する ことにより、2つのステップの間にいくつかのことを実行できるためです。最も一般的なことは、新しいプログラムが特定のファイルを特定のファイル記述子として開いていることを確認することです。(ここで、ファイル記述子はとは異なりますが、カーネルが認識している値です)。これを行うと、次のことができます。 FILE *int

int X = open("./output_file.txt", O_WRONLY);

pid_t fk = fork();
if (!fk) { /* in child */
    dup2(X, 1); /* fd 1 is standard output,
                   so this makes standard out refer to the same file as X  */
    close(X);

    /* I'm using execl here rather than exec because
       it's easier to type the arguments. */
    execl("/bin/echo", "/bin/echo", "hello world");
    _exit(127); /* should not get here */
} else if (fk == -1) {
    /* An error happened and you should do something about it. */
    perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */

これは実行を達成します:

/bin/echo "hello world" > ./output_file.txt

コマンドシェルから。


5

プロセスがfork()を使用すると、プロセスはそれ自体の複製コピーを作成し、この複製がプロセスの子になります。fork()は、カーネルから2回戻るLinuxのclone()システムコールを使用して実装されています。

  • ゼロ以外の値(子のプロセスID)が親に返されます。
  • ゼロの値が子に返されます。
  • メモリ不足などの問題が原因で子が正常に作成されなかった場合、-1がfork()に返されます。

例でこれを理解しましょう:

pid = fork(); 
// Both child and parent will now start execution from here.
if(pid < 0) {
    //child was not created successfully
    return 1;
}
else if(pid == 0) {
    // This is the child process
    // Child process code goes here
}
else {
    // Parent process code goes here
}
printf("This is code common to parent and child");

この例では、子プロセス内でexec()が使用されていないと想定しています。

しかし、親と子はPCB(プロセス制御ブロック)属性の一部が異なります。これらは:

  1. PID-子と親の両方が異なるプロセスIDを持っています。
  2. 保留中のシグナル-子は親の保留中のシグナルを継承しません。作成されると、子プロセスでは空になります。
  3. メモリロック-子は親のメモリロックを継承しません。メモリロックは、メモリ領域をロックするために使用できるロックであり、このメモリ領域をディスクにスワップすることはできません。
  4. レコードロック-子は親のレコードロックを継承しません。レコードロックは、ファイルブロックまたはファイル全体に関連付けられています。
  5. 子のプロセスリソース使用率と消費されたCPU時間はゼロに設定されます。
  6. また、子は親からタイマーを継承しません。

しかし、子供の記憶はどうですか?子供用に新しいアドレススペースが作成されますか?

いいえの答え。fork()の後、親と子の両方が親のメモリアドレス空間を共有します。Linuxでは、これらのアドレス空間は複数のページに分割されています。子が親メモリページの1つに書き込むときのみ、そのページの複製が子に対して作成されます。これは、書き込み時のコピーとも呼ばれます(子が書き込むときにのみ親ページをコピーします)。

例を使ってコピーオンライトを理解しましょう。

int x = 2;
pid = fork();
if(pid == 0) {
    x = 10;
    // child is changing the value of x or writing to a page
    // One of the parent stack page will contain this local               variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.  
}
else {
    x = 4;
}

しかし、なぜコピーオンライトが必要なのでしょうか?

典型的なプロセスの作成は、fork()-exec()の組み合わせによって行われます。まず、exec()が何をするかを理解しましょう。

Exec()グループの関数は、子のアドレス空間を新しいプログラムに置き換えます。子の中でexec()が呼び出されると、親のアドレススペースとは完全に異なる、別のアドレススペースが子用に作成されます。

fork()に関連付けられたコピーオンライトメカニズムがなかった場合、重複したページが子に作成され、すべてのデータが子のページにコピーされます。新しいメモリの割り当てとデータのコピーは非常にコストのかかるプロセスです(プロセッサの時間とその他のシステムリソースが必要)。また、ほとんどの場合、子がexec()を呼び出すため、子のメモリが新しいプログラムに置き換えられることもわかっています。したがって、コピーオンライトがなければ、最初のコピーは無駄になります。

pid = fork();
if(pid == 0) {
    execlp("/bin/ls","ls",NULL);
    printf("will this line be printed"); // Think about it
    // A new memory space will be created for the child and that   memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
    wait(NULL);
    // parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.

なぜ親は子プロセスを待つのですか?

  1. 親は子にタスクを割り当て、タスクが完了するまで待つことができます。その後、他の作業を行うことができます。
  2. 子プロセスが終了すると、プロセス制御ブロックを除いて、子プロセスに関連するすべてのリソースが解放されます。現在、子供はゾンビ状態です。wait()を使用すると、親は子のステータスを照会し、PCBを解放するようカーネルに要求できます。親が待機を使用しない場合、子はゾンビ状態のままになります。

exec()システムコールが必要なのはなぜですか?

exec()をfork()と一緒に使用する必要はありません。子が実行するコードが親に関連付けられたプログラム内にある場合、exec()は不要です。

しかし、子供が複数のプログラムを実行しなければならない場合を考えてください。シェルプログラムの例を見てみましょう。find、mv、cp、dateなどの複数のコマンドをサポートします。これらのコマンドに関連付けられたプログラムコードを1つのプログラムに含めたり、必要に応じて子がこれらのプログラムをメモリにロードしたりするのは正しいでしょうか?

それはすべてあなたのユースケースに依存します。2 ^ xをクライアントに返す入力xを指定したWebサーバーがあります。リクエストごとに、Webサーバーは新しい子を作成し、計算するように要求します。これを計算してexec()を使用する別のプログラムを作成しますか?または、親プログラム内に計算コードを書き込むだけですか?

通常、プロセスの作成には、fork()、exec()、wait()、exit()呼び出しの組み合わせが含まれます。


4

exec(3,3p)機能が代わる別に現在のプロセスを。つまり、現在のプロセスが停止し、代わりに別のプロセス実行され、元のプログラムが持っていたリソースの一部が引き継がれます。


6
結構です。現在のプロセスイメージを新しいプロセスイメージに置き換えます。プロセスは、同じpid、同じ環境、同じファイル記述子テーブルを持つ同じプロセスです。変更されたのは、仮想メモリ全体とCPUの状態です。
JeremyP 2010年

@JeremyP「同じファイル記述子」はここで重要でした、それはシェルでリダイレクトがどのように機能するかを説明します!execがすべてを上書きした場合にリダイレクトがどのように機能するかについて、私は困惑しました!ありがとう
FUD 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.