何であるexec()
関数とその家族は?なぜこの関数が使用され、どのように機能するのですか?
誰でもこれらの機能を説明してください。
何であるexec()
関数とその家族は?なぜこの関数が使用され、どのように機能するのですか?
誰でもこれらの機能を説明してください。
回答:
簡単に言えば、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
呼び出しは(execl
、execle
、execve
など)が、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
exec
現在のプロセスのIOをリダイレクトするためにユーティリティが使用されるのはなぜですか?引数なしでexecを実行する「null」の場合、どのようにしてこの規則に慣れましたか?
exec
別で、このプロセスでは、現在のプログラム(シェル)を交換するための手段として、そしてない他のプログラムが缶と交換することを指定すると、単にあなたが意味していない、それを交換したいです。
exec
が、プログラムなしで呼び出された場合の退化したケースにこの動作が残っていることがわかります。しかし、このシナリオでは少し奇妙です。なぜなら、新しいプログラム(実際にはexec
utされるプログラム)へのリダイレクトの本来の有用性が消え、有用なアーティファクトがあり、現在のプログラム(リダイレクトexec
または開始されていない)をリダイレクトするからです。いずれにせよ-代わりに。
exec()ファミリーの関数には、さまざまな動作があります。
あなたはそれらを混合することができます、したがってあなたは持っています:
それらすべての最初の引数は、実行されるファイルの名前です。
詳細については、exec(3)のマニュアルページを参照してください。
man 3 exec # if you are running a UNIX system
execve()
POSIXで定義されているリストから外し、POSIXでexecvpe()
定義されていないを追加しました(主にこれまでの先例の理由により、一連の関数が完了しています)。それ以外の場合は、ファミリの命名規則についての役立つ説明— paxdiabloの便利な補助' 関数の働きについて詳しく説明する回答。
execvpe()
(et al)のLinuxマニュアルページにはリストされていませんexecve()
。独自の個別のマニュアルページがあります(少なくともUbuntu 16.04 LTSの場合)。他のexec()
ファミリ関数はセクション3(関数)にexecve()
記載されているのに対し、セクション2(システムコール)に記載されている点が異なります。基本的に、ファミリーの他のすべての関数は、への呼び出しに関して実装されexecve()
ます。
exec
機能の家族はあなたのプロセスが実行されていた古いプログラムを置き換え、別のプログラムを実行させます。つまり、あなたが呼び出す場合
execl("/bin/ls", "ls", NULL);
次に、ls
プログラムは、プロセスID、現在の作業ディレクトリ、およびを呼び出したプロセスのユーザー/グループ(アクセス権)を使用して実行されexecl
ます。その後、元のプログラムは実行されなくなります。
新しいプロセスを開始するには、fork
システムコールが使用されます。オリジナルを交換せずにプログラムを実行するには、次のようにする必要がありfork
、その後、exec
。
exec関数とそのファミリーは何ですか。
exec
関数ファミリは、以下のようなファイルを実行するために使用されるすべての機能、されたexecl
、execlp
、execle
、execv
、とexecvp
。彼らはすべてのためのフロントエンドですexecve
し、それを呼び出すの異なる方法を提供します。
この関数が使用される理由
exec関数は、ファイル(プログラム)を実行(起動)するときに使用されます。
そしてそれはどのように機能しますか。
これらは、現在のプロセスイメージを起動したイメージで上書きすることで機能します。現在実行中のプロセス(execコマンドを呼び出したプロセス)を、起動した新しいプロセスで(終了することにより)置き換えます。
詳細については、このリンクを参照してください。
exec
はと組み合わせて使用されることがよくありfork
ますが、これについても同様に質問されたので、これを念頭に置いて説明します。
exec
現在のプロセスを別のプログラムに変えます。ドクター・フーを見たことがありますか?これは彼が再生するときのようです-彼の古い体は新しい体に置き換えられます。
これがプログラムで発生する方法であり、プログラムの引数(最初の引数)としてexec
渡したファイルがexec
現在のユーザー(プロセスのユーザーID)によって実行可能かどうかをOSカーネルが確認する多くのリソース作りexec
コール)とあれば、それは仮想メモリを現在のプロセスの仮想メモリマッピングを置き換えるように、新しいプロセスとコピーargv
とenvp
に渡されたデータ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
コマンドシェルから。
プロセスがfork()を使用すると、プロセスはそれ自体の複製コピーを作成し、この複製がプロセスの子になります。fork()は、カーネルから2回戻るLinuxのclone()システムコールを使用して実装されています。
例でこれを理解しましょう:
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(プロセス制御ブロック)属性の一部が異なります。これらは:
しかし、子供の記憶はどうですか?子供用に新しいアドレススペースが作成されますか?
いいえの答え。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.
なぜ親は子プロセスを待つのですか?
exec()システムコールが必要なのはなぜですか?
exec()をfork()と一緒に使用する必要はありません。子が実行するコードが親に関連付けられたプログラム内にある場合、exec()は不要です。
しかし、子供が複数のプログラムを実行しなければならない場合を考えてください。シェルプログラムの例を見てみましょう。find、mv、cp、dateなどの複数のコマンドをサポートします。これらのコマンドに関連付けられたプログラムコードを1つのプログラムに含めたり、必要に応じて子がこれらのプログラムをメモリにロードしたりするのは正しいでしょうか?
それはすべてあなたのユースケースに依存します。2 ^ xをクライアントに返す入力xを指定したWebサーバーがあります。リクエストごとに、Webサーバーは新しい子を作成し、計算するように要求します。これを計算してexec()を使用する別のプログラムを作成しますか?または、親プログラム内に計算コードを書き込むだけですか?
通常、プロセスの作成には、fork()、exec()、wait()、exit()呼び出しの組み合わせが含まれます。
exec(3,3p)
機能が代わる別に現在のプロセスを。つまり、現在のプロセスが停止し、代わりに別のプロセスが実行され、元のプログラムが持っていたリソースの一部が引き継がれます。