[英]How to handle execvp(…) errors after fork()?
我做常規的事情:
如果execvp因為沒有找到cmd而失敗,我怎么能在父進程中注意到這個錯誤?
#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 child's execvp: No such file or directory $ (sleep 1 && killall -QUIT sleep &); ./a.out sleep 60 waiting for child... child killed by 3 $ ./a.out true waiting for child... child exited with 0
這是如何工作的:
創建管道,並使寫入端點CLOEXEC
:成功執行exec
時自動關閉。
在孩子,嘗試exec
。 如果成功,我們將無法控制,但管道已關閉。 如果失敗,請將失敗代碼寫入管道並退出。
在父級中,嘗試從其他管道端點讀取。 如果read
返回零,則管道關閉,子exec
必須成功exec
。 如果read
返回數據,那就是我們孩子寫的失敗代碼。
你終止了孩子(通過調用_exit() )然后父母可以注意到這一點(通過例如waitpid() )。 例如,您的孩子可以退出,退出狀態為-1,表示執行失敗。 有一點需要注意的是,無法告訴你的父母處於原始狀態(即exec之前)的孩子是否返回-1或者是否是新執行的過程。
正如下面的評論中所建議的,使用“不尋常”的返回代碼是合適的,以便更容易區分您的特定錯誤和exec()'ed程序中的錯誤。 常見的是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()
而不是exit()
- 參見http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - 注意:適用於fork()
以及vfork()
。
2)執行比退出狀態更復雜的IPC的問題是你有一個共享的內存映射,如果你做了太復雜的事情,就可能會出現一些討厭的狀態 - 例如在多線程代碼中,一個被殺死的線程(在孩子本來可以拿着鎖。
任何時候exec在子進程中失敗,你應該使用kill(getpid(),SIGKILL)並且父應該總是有一個SIGCLD的信號處理程序,並以適當的方式告訴用戶程序沒有成功啟動進程。
好吧,您可以在父進程中使用wait
/ waitpid
函數。 您可以指定一個status
變量,其中包含有關已終止進程狀態的信息。 缺點是父進程被阻塞,直到子進程完成執行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.