2009-10-18 9 views
10

faccio la cosa normale:Come gestire gli errori execvp (...) dopo fork()?

  • fork()
  • execvp (cmd) nel bambino

Se execvp fallisce perché non cmd viene trovato, come posso notare questo errore nel genitore processi?

+0

No, perché il figlio biforcuto è un processo separato con il proprio spazio indirizzo e una propria copia indipendente di errno. Il genitore non può vedere l'errore del bambino. –

+0

Rimosso quel commento stupido. Grazie Andrew Medico e uova. – dirkgently

+0

Non è questo un lavoro per qualche semplice IPC? Questo ti offre più di 8 bit di uno stato di uscita. E puoi anche essere sicuro che il tuo execv è fallito, in contrasto con il fallimento del processo generato. Immagino dipenda da quanto tieni a cuore che tuo figlio non stia davvero sparando. – mrduclaw

risposta

15

Il noto self-pipe trick può essere adapted per questo scopo.

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

Ecco un programma completo.

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

Come funziona:

creare una pipe, e fare il punto finale di scrittura CLOEXEC: si auto-chiude quando un exec viene eseguita con successo.

Nel bambino, provare a exec. Se riesce, non abbiamo più controllo, ma il tubo è chiuso. Se fallisce, scrivi il codice di errore nella pipe ed esci.

Nel genitore, provare a leggere dall'altro endpoint del tubo. Se read restituisce zero, la pipe è stata chiusa e il figlio deve avere exec correttamente. Se read restituisce i dati, è il codice di errore scritto da nostro figlio.

+2

Questa soluzione, molto semplicemente, oscilla. – caf

+0

Ci dovrebbe essere un ulteriore set di parentesi all'interno dell'istruzione switch, per assicurare al compilatore che è inteso come una dichiarazione di assegnazione – tay10r

+0

@TaylorFlores: ?? Il compilatore capisce bene la sintassi. Molti compilatori * avvertono * su un compito nullo all'interno di un 'if', poiché i controlli di uguaglianza sono più comuni lì, ma non c'è motivo di farlo in un' switch'. – ephemient

6

Si termina il bambino (chiamando _exit()) e quindi il genitore può notare questo (tramite ad esempio waitpid()). Ad esempio, tuo figlio potrebbe uscire con uno stato di uscita di -1 per indicare il fallimento dell'esecuzione. Un avvertimento con questo è che è impossibile dire da tuo genitore se il bambino nel suo stato originale (cioè prima di exec) restituito -1 o se fosse il processo appena eseguito.

Come suggerito nei commenti seguenti, l'utilizzo di un codice di ritorno "insolito" sarebbe appropriato per semplificare la distinzione tra il tuo errore specifico e uno dal programma exec()'ed. Quelli comuni sono 1, 2, 3 ecc. Mentre i numeri più alti 99, 100, ecc. Sono più insoliti. Dovresti mantenere i tuoi numeri inferiori a 255 (senza segno) o 127 (firmati) per aumentare la portabilità.

Poiché waitpid blocca la tua applicazione (o piuttosto il thread che la chiama) dovrai metterla su un thread in background o usare il meccanismo di segnalazione in POSIX per ottenere informazioni sulla terminazione del processo figlio. Vedi il segnale SIGCHLD e la funzione sigaction per collegare un listener.

È anche possibile eseguire alcuni controlli degli errori prima di eseguire la foratura, ad esempio assicurandosi che l'eseguibile esista.

Se si utilizza qualcosa come Glib, ci sono funzioni di utilità per fare questo, e sono dotati di segnalazione degli errori piuttosto buona. Dai un'occhiata alla sezione "spawning processes" del manuale.

+0

non è lo stato di uscita a 8 bit senza segno? (-1 -> 255) – falstro

+1

Sono solo 8 bit di dati. Non importa se li interpreti come firmati di non firmati. (essere solo coerenti) Non sono sicuro di ciò che dice lo standard POSIX, ma è abbastanza comune restituire valori negativi da main() per indicare l'errore –

+0

Non waitpid() blocca il mio processo genitore? –

0

Bene, è possibile utilizzare le funzioni wait/waitpid nel processo principale. È possibile specificare una variabile status che contiene informazioni sullo stato del processo che è terminato. Lo svantaggio è che il processo genitore è bloccato fino a quando il processo figlio non termina l'esecuzione.

+3

dai un'occhiata a sigchld se non vuoi bloccare l'esecuzione. – falstro

1

Non dovresti chiedere come si può preavviso nel processo genitore, ma anche si dovrebbe tenere a mente che si deve avviso l'errore nel processo padre. Questo è particolarmente vero per le applicazioni multithread.

Dopo aver eseguito l'operazione deve effettuare una chiamata alla funzione che termina il processo in ogni caso. Non dovresti chiamare funzioni complesse che interagiscono con la libreria C (come stdio), poiché gli effetti di questi potrebbero mescolarsi con i pthread della funzionalità libc del processo genitore. Pertanto, non è possibile stampare un messaggio con printf() nel processo figlio e informare invece il genitore dell'errore.

Il modo più semplice, tra l'altro, è il codice di ritorno. Fornisci argomenti diversi da zero alla funzione _exit() (vedi nota sotto) che hai usato per terminare il bambino e quindi esaminare il codice di ritorno nel genitore.Ecco l'esempio:

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

Invece di _exit(), si potrebbe pensare di exit() funzione, ma non è corretto, dal momento che questa funzione farà parte della pulizia C-libreria che dovrebbe essere fatto solo quando genitore Il processo termina. Utilizzare invece la funzione _exit(), che non esegue tale pulizia.

+1

Non utilizzare exit() dopo il fork, usare _exit(). –

+0

@Douglas Leeder, grazie per aver sottolineato questo enorme difetto. Ho risolto il post. –

1

1) Utilizzare _exit() non exit() - vedi http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - NB: vale per fork() così come vfork().

2) Il problema con l'IPC più complicato rispetto allo stato di uscita è che si dispone di una mappa di memoria condivisa ed è possibile ottenere uno stato sgradevole se si fa qualcosa di troppo complicato, ad es. nel codice multithreaded, uno dei thread uccisi (nel child) avrebbe potuto contenere un lock.

0

Ogni volta che l'esecuzione non riesce in un sottoprocesso, è necessario utilizzare kill (getpid(), SIGKILL) e il genitore dovrebbe sempre disporre di un gestore di segnale per SIGCLD e comunicare all'utente del programma, nel modo appropriato, che il processo era non avviato correttamente.

Problemi correlati