2009-10-18 18 views
10

zrobić regularne rzeczy:Jak obsługiwać błędy execvp (...) po fork()?

  • fork()
  • execvp (cmd) u dziecka

Jeśli execvp powiodło się, ponieważ nie cmd zostanie znaleziony, w jaki sposób można zauważyć ten błąd w jednostce proces?

+0

Nie, ponieważ rozwidlone dziecko jest oddzielnym procesem z własną przestrzenią adresową i własną niezależną kopią errno. Rodzic nie widzi errno dziecka. –

+0

Usunięto ten głupi komentarz. Dzięki Andrew Medico i ikrze. – dirkgently

+0

Czy to nie jest praca dla niektórych prostych IPC? W ten sposób otrzymasz więcej niż 8 bitów statusu wyjścia. Otrzymasz również pewność, że twój execv nie działa, w przeciwieństwie do zerwanego procesu. Myślę, że to zależy od tego, jak bardzo zależy ci na tym, żeby twoje dziecko nie strzelało. – mrduclaw

Odpowiedz

15

Dobrze znany self-pipe trick może być w tym celu adapted.

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

Oto pełny program.

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

Jak to działa:

Tworzenie rury i dokonać zapisu końcowego CLOEXEC: to automatycznie zamyka się, gdy exec pomyślnie wykonana.

U dziecka, spróbuj exec. Jeśli się powiedzie, nie mamy już kontroli, ale rura jest zamknięta. Jeśli się nie powiedzie, zapisz kod błędu na rurze i zakończ.

W obiekcie nadrzędnym spróbuj odczytać z innego punktu końcowego rury. Jeśli read zwróci wartość zero, oznacza to, że rura została zamknięta, a dziecko musi mieć pomyślnie exec. Jeśli read zwraca dane, to kod błędu, który napisał nasze dziecko.

+2

To rozwiązanie, po prostu, skały. – caf

+0

W instrukcji switch powinien znajdować się dodatkowy zestaw nawiasów, aby zapewnić kompilatorowi, że ma to służyć jako instrukcja przypisania – tay10r

+0

@TaylorFlores: ?? Kompilator doskonale rozumie składnię. Wiele kompilatorów * ostrzega * na nagim zleceniu wewnątrz 'if', ponieważ kontrole równości są tam bardziej powszechne, ale nie ma powodu, aby to robić w' przełączniku'. – ephemient

6

Przerywasz dziecko (dzwoniąc pod numer _exit()), a rodzic może to zauważyć (np. Przez waitpid()). Na przykład Twoje dziecko może wyjść ze statusem wyjścia -1, aby wskazać niepowodzenie wykonania. Jedynym zastrzeżeniem jest to, że nie można powiedzieć od rodzica, czy dziecko w swoim pierwotnym stanie (tj. Przed exec) zwróciło -1, czy też było to nowo wykonane działanie.

Jak zasugerowano w komentarzach poniżej, użycie "nietypowego" kodu powrotu byłoby odpowiednie, aby ułatwić rozróżnienie między konkretnym błędem a błędem z programu wykonawczego exec(). Typowe to 1, 2, 3 itd., Natomiast wyższe liczby 99, 100 itd. Są bardziej niezwykłe. Powinieneś trzymać numery poniżej 255 (bez podpisu) lub 127 (podpisane), aby zwiększyć przenośność.

Ponieważ waitpid blokuje twoją aplikację (lub raczej wątek wywołujący ją), musisz albo umieścić ją w wątku tła, albo użyć mechanizmu sygnalizacji w POSIX, aby uzyskać informacje o kończeniu procesu potomnego. Zobacz sygnał SIGCHLD i funkcję sigaction, aby podłączyć słuchacza.

Można również wykonać sprawdzanie błędów przed rozwidleniem, np. Upewniając się, że plik wykonywalny istnieje.

Jeśli używasz czegoś takiego jak Glib, są w tym celu funkcje narzędziowe, które zawierają całkiem niezłe raporty o błędach. Zajrzyj do sekcji "spawning processes" w podręczniku.

+0

nie jest stanem wyjścia niepodpisanym na 8 bitów? (-1 -> 255) – falstro

+1

To tylko 8 bitów danych. Nie ma znaczenia, czy interpretujesz je jako podpisane bez znaku. (po prostu spójnie) Nie jestem pewien, co mówi standard POSIX, ale dość często zwracają wartości ujemne z main(), aby wskazać błąd –

+0

Czy metoda waitpid() nie zablokuje procesu nadrzędnego? –

0

Cóż, można użyć funkcji wait/waitpid w procesie nadrzędnym. Można określić zmienną status, która zawiera informacje o statusie zakończonego procesu. Minusem jest to, że proces nadrzędny jest blokowany do momentu zakończenia procesu potomnego.

+3

spójrz na sigchld, jeśli nie chcesz blokować wykonania. – falstro

1

nie powinien się zastanawiać, jak może zauważyć w procesie macierzystym, ale też należy pamiętać, że koniecznością Zawiadomienie błąd w procesie macierzystym. Jest to szczególnie ważne w przypadku aplikacji wielowątkowych.

Po execvp musisz wywołać funkcję kończącą proces. Nie powinieneś wywoływać żadnych złożonych funkcji, które współdziałają z biblioteką C (np. Stdio), ponieważ ich efekty mogą łączyć się z pthreads funkcji libc procesu nadrzędnego. Dlatego nie można wydrukować wiadomości z printf() w procesie potomnym i zamiast tego trzeba poinformować rodzica o błędzie.

Najprostszym sposobem jest między innymi przekazywanie kodu powrotu. Podaj niezerowy argument do funkcji _exit() (patrz uwaga poniżej) użytej do zakończenia elementu potomnego, a następnie sprawdź kod powrotu w obiekcie nadrzędnym.Oto przykład:

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

Zamiast _exit(), można by pomyśleć o exit() funkcji, ale jest to błędne, ponieważ funkcja ta zrobi część czyszczenia C-biblioteki, które powinny być wykonywane tylko wtedy, gdy rodzica Proces kończy się. Zamiast tego użyj funkcji _exit(), która nie wykonuje takiego czyszczenia.

+1

Nie należy używać exit() po fork, należy użyć _exit(). –

+0

@Douglas Leeder, dziękuję za wskazanie tej ogromnej wady. Naprawiłem post. –

1

1) Użyj _exit() nie exit() - patrz http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - NB: dotyczy fork() jak również vfork().

2) Problem z wykonywaniem bardziej skomplikowanego IPC niż stan wyjścia polega na tym, że masz wspólną mapę pamięci i możliwe jest uzyskanie nieprzyjemnego stanu, jeśli zrobisz coś zbyt skomplikowanego - np. w wielowątkowym kodzie, jeden z zabitych wątków (u dziecka) mógł trzymać zamek.

0

Anytime exec kończy się niepowodzeniem w podprocesie, powinieneś użyć kill (getpid(), SIGKILL), a rodzic powinien zawsze mieć procedurę obsługi sygnału dla SIGCLD i poinformować użytkownika programu, we właściwy sposób, że proces był nie powiodło się.

Powiązane problemy