2009-10-18 18 views
10

ich tun, um die regelmäßige Sache:Wie mit EXECVP (...) Fehler nach fork() umgehen?

  • fork()
  • execvp (cmd,) in der Kinder

Wenn execvp schlägt fehl, da kein cmd gefunden wird, wie kann ich diesen Fehler feststellen, in Mutter verarbeiten?

+0

Nein, weil das gegabelte Kind ein separater Prozess mit einem eigenen Adressraum und einer eigenen unabhängigen Kopie von errno ist. Der Elternteil kann das Errno des Kindes nicht sehen. –

+0

Dieser blöde Kommentar entfernt. Danke Andrew Medico und Rogen. – dirkgently

+0

Ist das nicht ein Job für einige einfache IPC? Auf diese Weise erhalten Sie mehr als 8 Bit eines Exit-Status. Und Sie können auch sicher sein, dass Ihre Execv im Gegensatz zum erzeugten Prozess fehlgeschlagen ist. Ich schätze, es hängt davon ab, wie sehr du dich um dein Kind sorgst, das nicht wirklich feuert. – mrduclaw

Antwort

15

Der bekannte self-pipe trick kann adapted für diesen Zweck sein.

#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; 
} 

Hier ist ein komplettes Programm.

 
$ ./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 

Wie das funktioniert:

erstellen Rohr, und stellen Sie den Schreib Endpunkt CLOEXEC: es automatisch schließt, wenn ein exec erfolgreich durchgeführt wird.

In dem Kind, versuchen Sie exec. Wenn es gelingt, haben wir keine Kontrolle mehr, aber das Rohr ist geschlossen. Wenn dies fehlschlägt, schreiben Sie den Fehlercode in die Pipe und beenden Sie.

Versuchen Sie im übergeordneten Objekt, vom anderen Pipe-Endpunkt zu lesen. Wenn read Null zurückgibt, wurde die Pipe geschlossen und das Kind muss exec erfolgreich haben. Wenn read Daten zurückgibt, ist dies der Fehlercode, den unser Kind geschrieben hat.

+2

Diese Lösung rockt ganz einfach. – caf

+0

Es sollte einen zusätzlichen Satz Klammern in der switch-Anweisung geben, um sicherzustellen, dass der Compiler als Zuweisungsanweisung dient. – tay10r

+0

@ TaylorFlores: ?? Der Compiler versteht die Syntax gut. Viele Compiler * warnen * bei einer nackten Zuweisung innerhalb eines 'if', da dort Gleichheitsprüfungen häufiger sind, aber es gibt keinen Grund, dies in einem' switch' zu tun. – ephemient

6

Sie beenden das Kind (durch den Aufruf _exit()) und dann kann das Elternteil dies (z. B. waitpid()) bemerken. Zum Beispiel könnte Ihr Kind mit einem Exit-Status von -1 beendet werden, um anzuzeigen, dass exec nicht ausgeführt wurde. Ein Nachteil dabei ist, dass es unmöglich ist, von Ihrem Elternteil zu unterscheiden, ob das Kind in seinem ursprünglichen Zustand (d. H. Vor exec) -1 zurückgegeben hat oder ob es der neu ausgeführte Prozess war.

Wie unten in den Kommentaren vorgeschlagen, wäre sachgemäßer einen „ungewöhnlich“ Return-Code verwendet es einfacher zu machen, zwischen Ihrem spezifischen Fehler und einem von der exec() "ed Programm zu unterscheiden. Übliche sind 1, 2, 3 usw., während höhere Zahlen 99, 100 usw. eher ungewöhnlich sind. Sie sollten Ihre Nummern unter 255 (nicht signiert) oder 127 (signiert) halten, um die Portabilität zu erhöhen.

Da waitpid Ihre Anwendung (oder vielmehr den Thread, der sie aufruft) blockiert, müssen Sie sie entweder in einen Hintergrundthread einfügen oder den Signalisierungsmechanismus in POSIX verwenden, um Informationen über die Beendigung des untergeordneten Prozesses zu erhalten. Sehen Sie sich das Signal SIGCHLD und die sigaction Funktion an, um einen Listener anzuschließen.

Sie könnten auch einige Fehlerprüfung, bevor gabeln, wie dafür, dass die ausführbare Datei vorhanden ist.

Wenn Sie etwas wie Glib verwenden, gibt es Dienstprogrammfunktionen, um dies zu tun, und sie kommen mit ziemlich guten Fehlerberichten. Werfen Sie einen Blick auf den Abschnitt "spawning processes" des Handbuchs.

+0

ist nicht der Ausgangsstatus 8 Bit unsigned? (-1 -> 255) – falstro

+1

Es sind nur 8 Datenbits. Es spielt keine Rolle, ob Sie sie als nicht signiert interpretieren. (nur konsistent sein) Ich bin mir nicht sicher, was der POSIX-Standard sagt, aber es ist durchaus üblich, negative Werte von main() zurückzugeben, um den Fehler anzuzeigen –

+0

Wird nicht waitpid() meinen Elternprozess blockieren? –

0

Nun, Sie könnten die wait/waitpid Funktionen im übergeordneten Prozess verwenden. Sie können eine status Variable angeben, die Informationen über den Status des beendeten Prozesses enthält. Der Nachteil besteht darin, dass der übergeordnete Prozess blockiert wird, bis der untergeordnete Prozess die Ausführung beendet hat.

+3

gucken Sie sigchld, wenn Sie die Ausführung nicht blockieren möchten. – falstro

1

nicht, sollten Sie sich fragen, wie Sie können Bekanntmachung es in übergeordneten Prozess, sondern auch sollten Sie bedenken, dass Sie muss Mitteilung der Fehler im übergeordneten Prozess. Dies gilt insbesondere für Multithread-Anwendungen.

Nach execvp müssen Sie einen Aufruf an die Funktion stellen, die den Prozess in jedem Fall beendet. Sie sollten keine komplexen Funktionen aufrufen, die mit der C-Bibliothek interagieren (z. B. stdio), da sich deren Effekte mit pthreads der libc-Funktionalität des übergeordneten Prozesses mischen können. Sie können also keine Nachricht mit printf() im untergeordneten Prozess drucken und stattdessen Eltern über den Fehler informieren.

Der einfachste Weg, unter den anderen, Return-Code übergeben. Geben Sie ein Argument ungleich Null an die _exit()-Funktion (siehe Hinweis unten), die Sie zum Beenden des untergeordneten Elements verwendet haben, und untersuchen Sie dann den Rückgabecode im übergeordneten Element.Hier ist das Beispiel:

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 
... 
} 

Statt _exit(), Sie exit() Funktion vielleicht denken, aber es ist falsch, da diese Funktion einen Teil der C-Bibliothek Bereinigung tun, die nur getan werden sollte, wenn Eltern Prozess wird beendet. Verwenden Sie stattdessen _exit() Funktion, die eine solche Bereinigung nicht durchführt.

+1

Verwenden Sie exit() nicht nach fork, verwenden Sie _exit(). –

+0

@ Douglas Leeder, danke, dass du auf diesen großen Fehler aufmerksam gemacht hast. Ich habe die Post repariert. –

1

1) Verwenden Sie _exit() nicht exit() - siehe http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - NB: gilt für fork() sowie vfork().

2) Das Problem mit der komplizierteren IPC als der Exit-Status ist, dass Sie eine gemeinsame Speicherabbild haben, und es ist möglich, einige fiese Zustand, wenn Sie etwas zu kompliziert machen - z. In Multithread-Code könnte einer der getöteten Threads (im Child) eine Sperre enthalten haben.

0

Immer wenn exec in einem Subprozess fehlschlägt, sollten Sie kill (getpid(), SIGKILL) verwenden und der Eltern sollte immer einen Signalhandler für SIGCLD haben und dem Benutzer des Programms in der entsprechenden Weise mitteilen, dass der Prozess war nicht erfolgreich gestartet

Verwandte Themen