私は通常のことをします:
- フォーク()
- 子のexecvp(cmd、)
cmdが見つからないためにexecvpが失敗した場合、親プロセスでこのエラーにどのように気付くことができますか?
よく知られているセルフパイプトリックは、この目的に適合させることができます。
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <unistd.h>
int main(int argc, char **argv) {
int pipefds[2];
int count, err;
pid_t child;
if (pipe(pipefds)) {
perror("pipe");
return EX_OSERR;
}
if (fcntl(pipefds[1], F_SETFD, fcntl(pipefds[1], F_GETFD) | FD_CLOEXEC)) {
perror("fcntl");
return EX_OSERR;
}
switch (child = fork()) {
case -1:
perror("fork");
return EX_OSERR;
case 0:
close(pipefds[0]);
execvp(argv[1], argv + 1);
write(pipefds[1], &errno, sizeof(int));
_exit(0);
default:
close(pipefds[1]);
while ((count = read(pipefds[0], &err, sizeof(errno))) == -1)
if (errno != EAGAIN && errno != EINTR) break;
if (count) {
fprintf(stderr, "child's execvp: %s\n", strerror(err));
return EX_UNAVAILABLE;
}
close(pipefds[0]);
puts("waiting for child...");
while (waitpid(child, &err, 0) == -1)
if (errno != EINTR) {
perror("waitpid");
return EX_SOFTWARE;
}
if (WIFEXITED(err))
printf("child exited with %d\n", WEXITSTATUS(err));
else if (WIFSIGNALED(err))
printf("child killed by %d\n", WTERMSIG(err));
}
return err;
}
これが完全なプログラムです。
$ ./a.out foo 子のexecvp:そのようなファイルまたはディレクトリはありません $(sleep 1 && killall -QUIT sleep&); ./a.out sleep 60 子供を待っています... 3人で殺された子供 $ ./a.out true 子供を待っています... 子は0で終了しました
これがどのように機能するか:
パイプを作成し、書き込みエンドポイントを作成します。が正常に実行されると、パイプはCLOEXEC
自動的に閉じます。exec
子供では、してみてくださいexec
。成功すると、制御できなくなりますが、パイプは閉じられます。失敗した場合は、失敗コードをパイプに書き込んで終了します。
親で、他のパイプエンドポイントから読み取ってみてください。ゼロを返す場合read
、パイプは閉じられており、子はexec
正常に動作している必要があります。データを返す場合read
、それは私たちの子供が書いた失敗コードです。
子を終了すると( _exit()を呼び出すことにより)、親はこれに気付くことができます(たとえば、waitpid()を介して)。たとえば、子が終了ステータス-1で終了して、実行に失敗したことを示すことができます。これに関する1つの注意点は、元の状態(つまり、execの前)の子が-1を返したかどうか、またはそれが新しく実行されたプロセスであったかどうかを親から判断できないことです。
以下のコメントで示唆されているように、「異常な」リターンコードを使用すると、特定のエラーとexec()されたプログラムのエラーを簡単に区別できるようになります。一般的なものは1、2、3などですが、99、100などの数字が大きいほど珍しいものです。移植性を高めるために、番号を255(符号なし)または127(符号付き)未満に保つ必要があります。
waitpidはアプリケーション(またはそれを呼び出すスレッド)をブロックするため、アプリケーションをバックグラウンドスレッドに配置するか、POSIXのシグナリングメカニズムを使用して子プロセスの終了に関する情報を取得する必要があります。リスナーを接続するには、SIGCHLDシグナルとsigaction関数を参照してください。
実行可能ファイルが存在することを確認するなど、フォークする前にエラーチェックを行うこともできます。
Glibのようなものを使用する場合、これを行うためのユーティリティ関数があり、それらにはかなり良いエラー報告が付属しています。マニュアルの「スポーンプロセス」セクションをご覧ください。
親プロセスでどのように気付くことができるのか不思議に思う必要はありませんが、親プロセスでエラーに気付く必要があることにも注意する必要があります。これは、マルチスレッドアプリケーションに特に当てはまります。
execvpの後で、どのような場合でもプロセスを終了する関数を呼び出す必要があります。Cライブラリと相互作用する複雑な関数(stdioなど)を呼び出さないでください。それらの効果は、親プロセスのlibc機能のpthreadと混ざり合う可能性があります。そのため、子プロセスでメッセージを印刷することはできず、printf()
代わりに親にエラーについて通知する必要があります。
特に最も簡単な方法は、リターンコードを渡すことです。_exit()
子を終了するために使用した関数(下記の注を参照)にゼロ以外の引数を指定してから、親の戻りコードを調べます。次に例を示します。
int pid, stat;
pid = fork();
if (pid == 0){
// Child process
execvp(cmd);
if (errno == ENOENT)
_exit(-1);
_exit(-2);
}
wait(&stat);
if (!WIFEXITED(stat)) { // Error happened
...
}
の代わりに_exit()
、関数について考えるかもしれませんがexit()
、この関数は、親プロセスが終了したときにのみ実行する必要があるCライブラリのクリーンアップの一部を実行するため、正しくありません。代わりに、_exit()
そのようなクリーンアップを行わない関数を使用してください。
1)使用し_exit()
ない-http: //opengroup.org/onlinepubs/007908775/xsh/vfork.htmlexit()
を参照-注:に適用されます。fork()
vfork()
2)終了ステータスよりも複雑なIPCを実行する場合の問題は、共有メモリマップがあることです。たとえば、マルチスレッドコード、強制終了されたスレッドの1つなど、複雑すぎる処理を実行すると、厄介な状態になる可能性があります。子供)はロックを保持している可能性があります。
Anytime exec fails in a subprocess, you should use kill(getpid(),SIGKILL) and the parent should always have a signal handler for SIGCLD and tell the user of the program, in the appropriate way, that the process was not successfully started.
さて、あなたは親プロセスでwait
/関数を使うことができます。終了したプロセスのステータスに関する情報を保持waitpid
する変数を指定できます。status
欠点は、子プロセスが実行を終了するまで親プロセスがブロックされることです。