簡體   English   中英

如何在fork()之后處理execvp(...)錯誤?

[英]How to handle execvp(…) errors after fork()?

我做常規的事情:

  • 叉子()
  • 小孩的execvp(cmd,)

如果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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM