2009-10-18 25 views
10

Hice lo normal:Cómo manejar errores execvp (...) después de fork()?

  • tenedor()
  • execvp (cmd) en niños

Si execvp falla porque no se encuentra ninguna cmd, ¿cómo puedo tener este error en los padres ¿proceso?

+0

No, porque el hijo bifurcado es un proceso separado con su propio espacio de direcciones y su propia copia independiente de errno. El padre no puede ver el error del niño. –

+0

Se eliminó ese comentario estúpido. Gracias Andrew Medico y huevas. – dirkgently

+0

¿No es este un trabajo para un simple IPC? Al hacerlo, obtendrá más de 8 bits de un estado de salida. Y también puede estar seguro de que su ejecutiva falló, a diferencia del proceso engendrado que falla. Supongo que depende de cuánto te importa que tu hijo no esté disparando. – mrduclaw

Respuesta

15

El bien conocido self-pipe trick puede ser adapted para este fin.

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

Aquí hay un programa 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 

¿Cómo funciona esto:

Crear una tubería, y crea el punto final de escritura CLOEXEC: se auto-cierra cuando un exec se haya efectuado correctamente.

En el niño, intente exec. Si tiene éxito, ya no tenemos control, pero la tubería está cerrada. Si falla, escriba el código de falla en la tubería y salga.

En el elemento primario, intente leer desde el otro punto final de la tubería. Si read devuelve cero, entonces la tubería se cerró y el niño debe tener exec con éxito. Si read devuelve datos, es el código de falla que escribió nuestro hijo.

+2

Esta solución, simplemente, rocas. – caf

+0

Debería haber un conjunto adicional de paréntesis dentro de la instrucción switch, para garantizar que el compilador sea una declaración de asignación – tay10r

+0

@TaylorFlores: ?? El compilador entiende la sintaxis muy bien. Muchos compiladores * advierten * sobre una asignación simple dentro de un 'si', ya que las verificaciones de igualdad son más comunes allí, pero no hay razón para hacer eso en un 'interruptor'. – ephemient

6

Usted termina al niño (llamando al _exit()) y luego el padre puede notar esto (a través, por ejemplo, waitpid()). Por ejemplo, su hijo podría salir con un estado de salida de -1 para indicar que no ejecutó. Una advertencia con esto es que es imposible saber a partir de sus padres si el niño en su estado original (es decir, antes ejecutivo) devuelve -1 o si fue el proceso recién ejecutado.

Como se sugiere en los comentarios a continuación, utilizar un código de retorno "inusual" sería apropiado para facilitar la distinción entre su error específico y uno del programa exec() 'ed. Los más comunes son 1, 2, 3 etc., mientras que los números más altos 99, 100, etc. son más inusuales. Debe mantener sus números por debajo de 255 (sin firmar) o 127 (con firma) para aumentar la portabilidad.

Como waitpid bloquea su aplicación (o más bien, el hilo que la llama), deberá colocarla en una cadena de fondo o usar el mecanismo de señalización en POSIX para obtener información sobre la terminación del proceso hijo. Vea la señal SIGCHLD y la función sigaction para conectar un oyente.

También podría hacer un poco de comprobación de errores antes de que se bifurcan, tales como asegurarse de que existe el ejecutable.

Si utiliza algo así como Glib, existen funciones de utilidad para hacer esto, y ellos vienen con bastante bien el informe de errores. Eche un vistazo a la sección "spawning processes" del manual.

+0

no es el estado de salida de 8 bits sin firmar? (-1 -> 255) – falstro

+1

Son solo 8 bits de datos. No importa si los interpreta como firmados sin firmar. (solo sea consistente) No estoy seguro de lo que dice el estándar POSIX, pero es bastante común devolver valores negativos desde main() para indicar el error –

+0

¿No bloqueará waitpid() el proceso principal? –

0

Bueno, podría usar las funciones wait/waitpid en el proceso principal. Puede especificar una variable status que contiene información sobre el estado del proceso que finalizó. La desventaja es que el proceso principal se bloquea hasta que el proceso secundario finaliza la ejecución.

+3

eche un vistazo a sigchld si no quiere bloquear la ejecución. – falstro

1

no debería uno se pregunta cómo se puede notificación en proceso padre, sino que también se debe tener en cuenta que es imprescindible aviso de error en el proceso padre. Eso es especialmente cierto para aplicaciones multiproceso.

Después de execvp usted debe llamar al para finalizar la función en cualquier caso. No debe llamar a ninguna función compleja que interactúe con la biblioteca de C (como stdio), ya que los efectos de ellas pueden mezclarse con las pthreads de la funcionalidad de libc del proceso principal. Así no se puede imprimir un mensaje con printf() en proceso hijo y tiene que informar a los padres sobre el error en su lugar.

La forma más fácil, entre los otros, es pasar el código de retorno. Suministrar argumento distinto de cero a _exit() de funciones (ver nota más abajo) que sirve para salir del niño y luego examinar el código de retorno del padre.Aquí está el ejemplo:

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

En lugar de _exit(), se podría pensar en exit() función, pero es incorrecto, ya que esta función hará una parte de la limpieza C-biblioteca que debe hacerse sólo cuando padres finaliza el proceso. En su lugar, use la función _exit(), que no hace tal limpieza.

+1

No use exit() después de la horquilla, use _exit(). –

+0

@Douglas Leeder, gracias por señalar este gran defecto. Yo arreglé la publicación. –

1

1) Use _exit() no exit() - ver http://opengroup.org/onlinepubs/007908775/xsh/vfork.html - NB: se aplica a fork(), así como vfork().

2) El problema con hacer un IPC más complicado que el estado de salida, es que tiene un mapa de memoria compartida, y es posible obtener un estado desagradable si hace algo demasiado complicado, por ejemplo. en el código multiproceso, uno de los hilos muertos (en el niño) podría haber estado manteniendo un bloqueo.

0

En cualquier momento en que el administrador falle en un subproceso, debe usar kill (getpid(), SIGKILL) y el padre siempre debe tener un manejador de señal para SIGCLD y decirle al usuario del programa, de la manera apropiada, que el proceso fue no iniciado con éxito

Cuestiones relacionadas