Domanda

In Questo domanda precedente ho postato la maggior parte del mio codice shell.Il mio prossimo passo è implementare l'esecuzione dei processi in primo piano e in background e attendere adeguatamente che terminino in modo che non rimangano come "zombi".

Prima di aggiungere la possibilità di eseguirli in background, tutti i processi venivano eseguiti in primo piano.E per questo ho semplicemente chiamato wait(NULL) dopo aver eseguito 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 tranquillamente in background se torno alla mia shell.

Tutto 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 "zombi".Questo è il mio problema, non so come farlo...

Credo di dover gestire SIGCHLD e fare qualcosa lì, ma devo ancora capire appieno quando viene inviato il segnale SIGCHLD perché ho provato ad aggiungere anche 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, wait(NULL), il che significa che non potevo fare nulla con la mia shell fino al termine del processo "in background".Che non funzionava più in background a causa dell'attesa nel gestore del segnale.

Cosa mi manca in tutto questo?

Un'ultima cosa, come parte di questo esercizio devo anche stampare le modifiche allo stato dei processi, come la terminazione del processo.Quindi, anche qualsiasi intuizione al riguardo è molto apprezzata.

Questo è il mio codice completo al 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;
}
È stato utile?

Soluzione

Ci sono varie opzioni per waitpid() per aiutarti (citazioni dallo standard POSIX):

WCONTINUA

La funzione waitpid() riporterà lo stato di qualsiasi processo figlio continuato specificato da pid il cui stato non è stato riportato poiché è continuato da un'interruzione del controllo del lavoro.

WNOHANG

La funzione waitpid() non sospenderà l'esecuzione del thread chiamante se lo stato non è immediatamente disponibile per uno dei processi figli specificati da pid.

In particolare, WNOHANG ti permetterà di vedere se ci sono cadaveri da raccogliere senza causare il blocco del tuo processo in attesa di un cadavere.

Se il processo chiamante ha SA_NOCLDWAIT impostato o SIGCHLD impostato su SIG_IGN, e il processo non ha figli non attesi che sono stati trasformati in processi zombie, il thread chiamante si bloccherà finché tutti i figli del processo contenente il thread chiamante non termineranno e wait() e waitpid() falliranno e imposteranno errno su [ECHILD].

Probabilmente non vorrai ignorare SIGCHLD, ecc., e il tuo gestore di segnale dovrebbe probabilmente impostare un flag per dire al tuo ciclo principale "Oops;c'è un bambino morto: vai a prendere quel cadavere!".

Anche i segnali SIGCONT e SIGSTOP saranno importanti per te: vengono utilizzati rispettivamente per riavviare e arrestare un processo figlio (almeno in questo contesto).

Consiglierei di guardare il libro di Rochkind o il libro di Stevens: trattano questi problemi in dettaglio.

Altri suggerimenti

Questo dovrebbe iniziare. La differenza principale è che mi sono liberato del gestore bambino e aggiunto waitpid nel circuito principale con un feedback. Testato e funzionante, ma ha bisogno ovviamente 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;
}

Modifica Aggiunta di nuovo nella gestione del segnale non è difficile con waitpid() utilizzando WNOHANG. E 'semplice come spostare la roba waitpid() dalla parte superiore del ciclo nel gestore di segnale. È necessario essere consapevoli di due cose, però:

In primo luogo, anche i processi di "conoscenze acquisite" invierà SIGCHLD. Dal momento che non ci può essere solo un processo in primo piano si può semplicemente memorizzare il pid primo piano (valore di ritorno del genitore da fork()) in modo visibile variabile al gestore di segnale, se si vuole fare la gestione speciale di primo piano contro sfondo.

In secondo luogo, si sta facendo attualmente bloccando di I / O standard input (la read() in alto ciclo principale). Siete estremamente suscettibili di essere bloccato read() quando si verifica SIGCHLD, con un conseguente chiamata di sistema interrotta. A seconda del sistema operativo, può riavviare la chiamata di sistema automaticamente, oppure può inviare un segnale che è necessario gestire.

È possibile utilizzare:

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 di attesa.

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 sono in esecuzione di un processo in primo piano "eliminare" il gestore per SIGCHLD in modo che non venga chiamato. Poi, dopo waitpid (), impostare di nuovo il gestore. In questo modo, saranno trattati i processi in background.

Pensi che ci sia qualcosa di sbagliato con questa soluzione?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top