2009-05-22 10 views
8

In this domanda precedente Ho postato la maggior parte del mio codice shell. Il mio prossimo passo è quello di implementare l'esecuzione del processo in primo piano e in background e attendere che si concludano in modo che non rimangano come "zombi".Come attendere correttamente i processi in primo piano/in background nella mia shell in C?

Prima di aggiungere la possibilità di eseguirli in background, tutti i processi erano in esecuzione in primo piano. E per questo, ho semplicemente chiamato wait (NULL) dopo l'esecuzione di qualsiasi processo con execvp(). Ora, controllo il carattere '&' come ultimo argomento e se è presente, eseguo il processo in background non chiamando wait (NULL) e il processo può essere eseguito felicemente in background, tornerò nella mia shell.

Questo funziona correttamente (credo), il problema ora è che devo anche chiamare wait() (o waitpid()?) In qualche modo in modo che il processo in background non rimanga "zombie". Questo è il mio problema, non sono sicuro di come farlo ...

Credo di dover gestire SIGCHLD e fare qualcosa lì, ma devo ancora capire quando viene inviato il segnale SIGCHLD perché ho provato anche a aggiungi wait (NULL) a childSignalHandler() ma non ha funzionato perché non appena ho eseguito un processo in background, è stata chiamata la funzione childSignalHandler() e, di conseguenza, l'attesa (NULL), il che significa che non ho potuto fare nulla con la mia shell fino al termine del processo "background". Che non funzionava più sullo sfondo a causa dell'attesa nel gestore del segnale.

Cosa mi manca in tutto questo?

Un'ultima cosa, parte di questo esercizio, ho anche bisogno di stampare le modifiche dello stato dei processi, come la terminazione del processo. Quindi, qualsiasi intuizione su questo è anche molto apprezzata.

Questo è il mio codice completo in questo momento:

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

risposta

4

Ci sono varie opzioni per waitpid() per aiutarvi (citazioni da standard POSIX):

WCONTINUED

Il waitpid() la funzione deve riportare lo stato di qualsiasi processo infantile continuato specificato da pid il cui stato non è stato segnalato dal momento in cui è continuato da una fermata di controllo del lavoro. Funzione

WNOHANG

Il waitpid() non sospende l'esecuzione del thread chiamante se lo stato non è immediatamente disponibile per uno dei processi secondari specificati dal pid.

In particolare, WNOHANG consente di vedere se ci sono dei cadaveri da raccogliere senza che il processo blocchi l'attesa di un cadavere.

Se il processo chiamante ha SA_NOCLDWAIT impostare o ha SIGCHLD impostata su SIG_IGN, e il processo non ha unwaited-per i bambini che sono stati trasformati in processi di zombie, il thread chiamante deve bloccare fino a quando tutti i bambini del processo contenente il thread chiamante termina e wait() e waitpid() falliscono e impostano errno su [ECHILD].

Probabilmente non si vuole ignorare SIGCHLD, ecc, e il vostro gestore di segnale dovrebbe probabilmente essere imposta una bandiera per dire al vostro ciclo principale "Oops;! Non c'è bambino morto - andare raccogliere quel cadavere".

Anche i segnali SIGCONT e SIGSTOP sono importanti per l'utente: vengono utilizzati per riavviare e arrestare un processo figlio, rispettivamente (in questo contesto, in ogni caso).

Suggerirei di guardare il libro di Rochkind o il libro di Stevens - trattano questi argomenti in dettaglio.

2

Questo dovrebbe iniziare. La principale differenza è che mi sono sbarazzato del gestore del bambino e ho aggiunto waitpid nel ciclo principale con un feedback. Testato e funzionante, ma ovviamente ha bisogno di più 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: calcolata nel trattamento del segnale non è difficile con waitpid() usando WNOHANG. È semplice come spostare la roba waitpid() dalla cima del loop al gestore del segnale. Dovresti essere a conoscenza di due cose:

In primo luogo, anche i processi "in primo piano" invieranno SIGCHLD. Dal momento che può esserci solo un processo in primo piano, è sufficiente memorizzare il primo piano (valore di ritorno del genitore da fork()) in una variabile visibile al gestore del segnale se si desidera eseguire una gestione speciale del primo piano rispetto allo sfondo.

In secondo luogo, si sta attualmente bloccando l'I/O sull'ingresso standard (read() nella parte superiore del loop principale). È estremamente probabile che venga bloccato su read() quando si verifica SIGCHLD, provocando una chiamata di sistema interrotta. A seconda del sistema operativo, la chiamata di sistema può essere riavviata automaticamente oppure è possibile che venga inviato un segnale che è necessario gestire.

+0

Grazie, ma ho davvero bisogno di usare il gestore del segnale, fa parte dell'esercizio. –

+0

Ho appena visto la tua modifica ... Ho inserito l'waitpid() nel gestore del segnale ma ho il problema che descrivi nel tuo secondo paragrafo. Non volevo usare le variabili globali per risolvere il problema, ma non sono sicuro di come risolverlo senza di loro ... –

+0

Evitare le variabili globali è buono, ma comunicare con un gestore di segnale è uno dei casi in cui sono " necessario. – dwc

0

Invece di usare una variabile globale, ho pensato a una soluzione diversa:

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

    waitpid(pid, NULL, 0); 

    signal(SIGCHLD, childSignalHandler); 
} 

Se sto correndo un processo in primo piano "eliminare" il gestore per SIGCHLD in modo che non venga chiamato. Quindi, dopo waitpid(), imposta di nuovo il gestore. In questo modo, verranno gestiti solo i processi in background.

Pensi che ci sia qualcosa di sbagliato in questa soluzione?

+1

Hai introdotto una condizione di competizione in cui un processo in background termina ma nessun gestore di segnale è installato, risultando in un bambino non sagomato e infine uno zombi. Non aggiungere tali problemi al tuo programma solo per evitare una variabile globale, solo perché hai sentito che erano cattivi. – dwc

+0

Ma non vedo ancora come implementare una variabile globale per questo e anche evitare una condizione di competizione. Il modo in cui lo vedo, aggiungendo una variabile globale per gestire questa situazione, creerà comunque una condizione di competizione. Ti preghiamo di fornire un esempio in cui non introduce una condizione di competizione? –

+0

Si noti che il secondo argomento di 'signal()' dovrebbe essere un puntatore a una funzione di gestione del segnale, o 'SIG_IGN' o' SIG_DFL'; 'NULL' non è un'opzione valida, anche se' SIG_IGN' è spesso un puntatore a funzione nulla. Probabilmente dovresti catturare il valore di ritorno dal primo 'segnale()' e reintegrarlo nella seconda chiamata. Se arrivano altri segnali del SIGCHLD, 'waitpid()' potrebbe tornare presto con EINTR. L'uso di 'sigaction()' invece di 'signal()' consente di controllare quali segnali sono consentiti. –

2

si può usare:

if(!background) 
    pause(); 

In questo modo, i blocchi di processo fino a quando non riceve il segnale SIGCHLD, e il gestore di segnale farà la roba attesa.

+1

Cosa succede se il segnale figlio passa tra il controllo di '' background' e la chiamata di '' pause() ''? C'è una condizione di gara qui. – jcoffland