2009-05-22 19 views
8

En this pregunta anterior publiqué la mayor parte de mi propio código de shell. Mi próximo paso es implementar la ejecución del proceso en primer plano y en segundo plano y esperar a que finalicen para que no queden como "zombies".¿Cómo esperar adecuadamente los procesos de primer plano/fondo en mi propio shell en C?

Antes de agregar la posibilidad de ejecutarlos en segundo plano, todos los procesos se ejecutaban en primer plano. Y para eso, simplemente llamé a wait (NULL) después de ejecutar cualquier proceso con execvp(). Ahora, compruebo el carácter '&' como último argumento y si está allí, ejecuto el proceso en segundo plano al no llamar a wait (NULL) y el proceso puede ejecutarse felizmente en segundo plano. Volveré a mi shell.

Todo esto funciona correctamente (creo), el problema ahora es que también necesito llamar a wait() (o waitpid()?) De alguna manera para que el proceso de fondo no permanezca "zombie". Ese es mi problema, no estoy seguro de cómo hacer eso ...

Creo que tengo que manejar SIGCHLD y hacer algo allí, pero todavía tengo que entender completamente cuándo se envía la señal SIGCHLD porque también intenté add wait (NULL) a childSignalHandler() pero no funcionó porque tan pronto como ejecuté un proceso en segundo plano, se llamó a la función childSignalHandler() y, en consecuencia, a la espera (NULL), lo que significa que no pude hacer nada con mi caparazón hasta que finalice el proceso de "fondo". Que ya no se ejecutaba en segundo plano debido a la espera en el manejador de señal.

¿Qué me estoy perdiendo en todo esto?

Una última cosa, parte de este ejercicio, también necesito imprimir los cambios del estado de los procesos, como la terminación del proceso. Por lo tanto, cualquier idea sobre eso también es muy apreciada.

Este es mi código completo en la actualidad

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <wait.h> 
#include <signal.h> 
#include <sys/types.h> 

#include "data.h" // Boolean typedef and true/false macros 


void childSignalHandler(int signum) { 
    // 
} 

int main(int argc, char **argv) { 
    char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr; 
    bool background; 
    ssize_t rBytes; 
    int aCount; 
    pid_t pid; 

    //signal(SIGINT, SIG_IGN); 

    signal(SIGCHLD, childSignalHandler); 

    while(1) { 
     write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27); 
     rBytes = read(0, bBuffer, BUFSIZ-1); 

     if(rBytes == -1) { 
      perror("read"); 
      exit(1); 
     } 

     bBuffer[rBytes-1] = '\0'; 

     if(!strcasecmp(bBuffer, "exit")) { 
      exit(0); 
     } 

     sPtr = bBuffer; 
     aCount = 0; 

     do { 
      aPtr = strsep(&sPtr, " "); 
      pArgs[aCount++] = aPtr; 
     } while(aPtr); 

     background = FALSE; 

     if(!strcmp(pArgs[aCount-2], "&")) { 
      pArgs[aCount-2] = NULL; 
      background = TRUE; 
     } 

     if(strlen(pArgs[0]) > 1) { 
      pid = fork(); 

      if(pid == -1) { 
       perror("fork"); 
       exit(1); 
      } 

      if(pid == 0) { 
       execvp(pArgs[0], pArgs); 
       exit(0); 
      } 

      if(!background) { 
       wait(NULL); 
      } 
     } 
    } 

    return 0; 
} 

Respuesta

4

Hay varias opciones a waitpid() para ayudarle (citas del estándar POSIX):

WCONTINUED

El waitpid() la función informará el estado de cualquier proceso hijo continuado especificado por pid cuyo estado no se haya informado ya que continuó desde una detención de control de trabajo. función

WNOHANG

El waitpid() no suspenderá la ejecución del subproceso de llamada si el estado no es inmediatamente disponible para uno de los procesos secundarios especificados por pid.

En particular, WNOHANG le permitirá ver si hay cadáveres que recoger sin que el proceso se bloquee mientras espera un cadáver.

Si el proceso de llamada tiene SA_NOCLDWAIT establecer o tiene SIGCHLD ajustado a SIG_IGN, y el proceso no tiene unwaited-para los niños que se transformaron en los procesos zombi, el subproceso de llamada se bloqueará hasta que todos los niños del proceso que contiene el hilo de llamada termina, y wait() y waitpid() fallarán y establecerán errno en [ECHILD].

Es probable que no quiere estar ignorando SIGCHLD, etc, y su manejador de señales probablemente debería estar fijándose una bandera de decirle a su bucle principal "Vaya,! No hay niño muerto - ir recoger el cadáver".

Las señales SIGCONT y SIGSTOP también serán relevantes para usted: se utilizan para reiniciar y detener un proceso hijo, respectivamente (en este contexto, en cualquier caso).

Recomiendo mirar el libro de Rochkind o el libro de Stevens: cubren estos problemas en detalle.

2

Esto debería comenzar. La principal diferencia es que me deshice del controlador de niños y agregué waitpid en el ciclo principal con algunos comentarios. Probado y funcionando, pero obviamente necesita más TLC.

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <strings.h> 
#include <unistd.h> 
#include <wait.h> 
#include <signal.h> 
#include <sys/types.h> 

int main(int argc, char **argv) { 
     char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr; 
     int background; 
     ssize_t rBytes; 
     int aCount; 
     pid_t pid; 
     int status; 
     while(1) { 
       pid = waitpid(-1, &status, WNOHANG); 
       if (pid > 0) 
         printf("waitpid reaped child pid %d\n", pid); 
       write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27); 
       rBytes = read(0, bBuffer, BUFSIZ-1); 
       if(rBytes == -1) { 
         perror("read"); 
         exit(1); 
       } 
       bBuffer[rBytes-1] = '\0'; 
       if(!strcasecmp(bBuffer, "exit")) 
         exit(0); 
       sPtr = bBuffer; 
       aCount = 0; 
       do { 
         aPtr = strsep(&sPtr, " "); 
         pArgs[aCount++] = aPtr; 
       } while(aPtr); 
       background = (strcmp(pArgs[aCount-2], "&") == 0); 
       if (background) 
         pArgs[aCount-2] = NULL; 
       if (strlen(pArgs[0]) > 1) { 
         pid = fork(); 
         if (pid == -1) { 
           perror("fork"); 
           exit(1); 
         } else if (pid == 0) { 
           execvp(pArgs[0], pArgs); 
           exit(1); 
         } else if (!background) { 
           pid = waitpid(pid, &status, 0); 
           if (pid > 0) 
             printf("waitpid reaped child pid %d\n", pid); 
         } 
       } 
     } 
     return 0; 
} 

EDIT: Adición de vuelta en el manejo de señales no es difícil con waitpid() usando WNOHANG. Es tan simple como mover el material waitpid() desde la parte superior del bucle al manejador de señal. Sin embargo, debe tener en cuenta dos cosas:

Primero, incluso los procesos de "primer plano" enviarán SIGCHLD. Dado que solo puede haber un proceso en primer plano, puede almacenar el pid de primer plano (valor de retorno del padre desde fork()) en una variable visible para el manejador de señal si desea hacer un manejo especial de primer plano frente a fondo.

En segundo lugar, actualmente está realizando bloqueos de E/S en la entrada estándar (read() en la parte superior del bucle principal). Es muy probable que se bloquee en read() cuando se produce SIGCHLD, lo que da como resultado una llamada al sistema interrumpida. Según el sistema operativo, puede reiniciar automáticamente la llamada al sistema o enviar una señal que debe manejar.

+0

Gracias, pero realmente necesito usar el manejador de señal, es parte del ejercicio. –

+0

Acabo de ver su edición ... Coloqué el waitpid() en el manejador de señal pero tengo el problema que describe en su segundo párrafo. Simplemente no quería usar variables globales para solucionar el problema, pero no estoy seguro de cómo solucionarlo sin ellas ... –

+0

Evitar las variables globales es bueno, pero la comunicación con un manejador de señal es uno de los casos en los que re necesario. – dwc

0

En lugar de utilizar una variable global, pensé en una solución diferente:

if(!background) { 
    signal(SIGCHLD, NULL); 

    waitpid(pid, NULL, 0); 

    signal(SIGCHLD, childSignalHandler); 
} 

Si estoy ejecutando un proceso en primer plano "suprimir" el controlador para SIGCHLD por lo que no se consiga llamar. Luego, después de waitpid(), configure el controlador nuevamente. De esta forma, solo se manejarán los procesos en segundo plano.

¿Cree que hay algún problema con esta solución?

+1

Ha introducido una condición de carrera en la que se cierra un proceso en segundo plano, pero no se instala ningún manejador de señal, lo que da como resultado un niño no transformado y finalmente un zombi. No agregue tales problemas a su programa solo para evitar una variable global, solo porque escuchó que eran malos. – dwc

+0

Pero todavía no veo cómo implementar una variable global para esto y también evitar una condición de carrera. La forma en que lo veo, agregar una variable global para manejar esta situación aún creará una condición de carrera. ¿Cuidar para dar un ejemplo donde no introduce una condición de carrera? –

+0

Tenga en cuenta que el segundo argumento para 'signal()' debe ser un puntero a una función de manejador de señal, o 'SIG_IGN' o' SIG_DFL'; 'NULL' no es una opción válida, aunque' SIG_IGN' es a menudo un puntero de función nulo. Probablemente deberías capturar el valor de retorno de la primera 'señal()' y restablecerlo en la segunda llamada. Si otras señales llegan a SIGCHLD, 'waitpid()' podría regresar temprano con EINTR. Usar 'sigaction()' en vez de 'signal()' le permitirá controlar qué señales están permitidas. –

2

Usted puede utilizar:

if(!background) 
    pause(); 

De esta manera, los bloques de proceso hasta que se recibe la señal SIGCHLD y el manejador de la señal va a hacer las cosas de espera.

+1

¿Qué sucede si la señal secundaria viene entre la comprobación de '' background' y la invocación '' pause() ''? Hay una condición de carrera aquí. – jcoffland

Cuestiones relacionadas