25 Stimmen

Verfolgen des Todes eines Kindprozesses

Wie könnte ich den Tod eines Kindprozesses verfolgen, ohne den Elternprozess warten zu lassen, bis der Kindprozess getötet wurde?

Ich versuche ein Client-Server-Szenario, bei dem der Server die Verbindung von einem Client akzeptiert und für jede akzeptierte Verbindung einen neuen Prozess erstellt.

Ich ignoriere SIGCHLD-Signale, um die Erstellung von Zombies zu verhindern.

signal(SIGCHLD, SIG_IGN);
while(1)
{
  accept();
  clients++;
  if(fork() ==0)
  {
     childfunction();
     clients--;
  }
  else
  {
  }
}

Das Problem in dem obigen Szenario ist, dass, wenn der Kindprozess in der childfunction()-Funktion getötet wird, die globale Variable clients nicht dekrementiert wird.

HINWEIS: Ich suche nach einer Lösung, die kein SIGCHLD-Signal verwendet ... Wenn möglich

0 Stimmen

Du kannst etwas im Signalhandler von SIGCHLD tun.

0 Stimmen

Ich habe bereits erwähnt... Wird das SIGCHLD-Signal ignoriert...?

14 Stimmen

+1 für einen wahnsinnig dramatischen Titel und den ersten Satz. D:

28voto

asveikau Punkte 37340

Typischerweise schreiben Sie einen Handler für SIGCHLD, der waitpid() für die PID -1 aufruft. Sie können den Rückgabewert davon verwenden, um festzustellen, welche PID gestorben ist. Zum Beispiel:

void my_sigchld_handler(int sig)
{
    pid_t p;
    int status;

    while ((p=waitpid(-1, &status, WNOHANG)) != -1)
    {
       /* Den Tod von PID p behandeln */
    }
}

/* Es ist besser, sigaction() anstelle von signal() zu verwenden. Sie werden nicht in das Problem laufen, dass BSD signal() auf eine bestimmte Weise reagiert und Linux oder SysV anders. */

struct sigaction sa;

memset(&sa, 0, sizeof(sa));
sa.sa_handler = my_sigchld_handler;

sigaction(SIGCHLD, &sa, NULL);

Alternativ können Sie waitpid(pid, &status, 0) mit der angegebenen Prozess-ID des Kindes aufrufen und synchron auf dessen Tod warten. Oder verwenden Sie WNOHANG, um seinen Status ohne Blockierung zu überprüfen.

0 Stimmen

Aber in meinem Beispiel wird SIGCHLD IGNORIERT ??

2 Stimmen

@codingfreak Und ich schlage vor, dass Sie das vielleicht überdenken möchten. Sie müssen es nicht ignorieren, um Zombies zu vermeiden. Wenn Sie waitpid() verwenden, verschwindet der "Zombie".

0 Stimmen

@asveikau - Ich versuche, einen Dämonenprozess zu erstellen, und indem ich den SIGCHLD-Handler verwende, wird die Stabilität der Anwendung beeinträchtigt. Die Elternprozess sollte die ganze Zeit im SIGCHLD-Handler auf das Herunterfahren warten.

9voto

Michael Harvey Punkte 218

Keine der bisherigen Lösungen bietet einen Ansatz ohne die Verwendung von SIGCHLD, wie es die Frage verlangt. Hier ist eine Implementierung eines alternativen Ansatzes unter Verwendung von poll, wie in dieser Antwort dargelegt (die auch erklärt, warum Sie in Situationen wie dieser die Verwendung von SIGCHLD vermeiden sollten):

Stellen Sie sicher, dass Sie für jeden von Ihnen erstellten Kindprozess eine Pipe haben. Es kann entweder deren stdin/stdout/stderr oder einfach eine zusätzliche Dummy-Dateideskriptor sein. Wenn der Kindprozess terminiert, wird sein Ende der Pipe geschlossen, und Ihre Hauptereignisschleife erkennt die Aktivität auf diesem Dateideskriptor. Aufgrund der Tatsache, dass es geschlossen wurde, erkennen Sie, dass der Kindprozess gestorben ist, und rufen waitpid auf, um den Zombie zu bereinigen.

(Hinweis: Ich habe einige bewährte Verfahren wie Fehlerüberprüfung und Bereinigung von Dateideskriptoren der Kürze halber weggelassen)

/**
 * Legt die maximale Anzahl von Clients fest, die in der Tabelle verfolgt werden sollen.
 */
#define MAX_CLIENT_COUNT 1000

/**
 * Verfolgt Clients, indem ihre Prozess-IDs und Pipe-Dateideskriptoren gespeichert werden.
 */
struct process_table {
    pid_t clientpids[MAX_CLIENT_COUNT];
    struct pollfd clientfds[MAX_CLIENT_COUNT];
} PT;

/**
 * Initialisiert die Prozesstabelle. -1 bedeutet, dass der Eintrag in der Tabelle verfügbar ist.
 */
void initialize_table() {
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
        PT.clientfds[i].fd = -1;
    }
}

/**
 * Gibt den Index des nächsten verfügbaren Eintrags in der Prozesstabelle zurück.
 */
int get_next_available_entry() {
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
        if (PT.clientfds[i].fd == -1) {
            return i;
        }
    }
    return -1;
}

/**
 * Fügt Informationen über einen neuen Client zur Prozesstabelle hinzu.
 */
void add_process_to_table(int i, pid_t pid, int fd) {
    PT.clientpids[i] = pid;
    PT.clientfds[i].fd = fd;
}

/**
 * Entfernt Informationen über einen Client aus der Prozesstabelle.
 */
void remove_process_from_table(int i) {
    PT.clientfds[i].fd = -1;
}

/**
 * Bereinigt alle toten Kindprozesse aus der Prozesstabelle.
 */
void reap_zombie_processes() {
    int p = poll(PT.clientfds, MAX_CLIENT_COUNT, 0);

    if (p > 0) {
        for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
            /* Hat sich die Pipe geschlossen? */
            if ((PT.clientfds[i].revents & POLLHUP) != 0) {
                // printf("[%d] erledigt\n", PT.clientpids[i]);
                waitpid(PT.clientpids[i], NULL, 0);
                remove_process_from_table(i);
            }
        }
    }
}

/**
 * Simuliert das Warten auf eine Verbindung eines neuen Clients.
 */
void accept() {
    sleep((rand() % 4) + 1);
}

/**
 * Simuliert die Ausführung nützlicher Arbeit durch den Kindprozess und das Beenden.
 */
void childfunction() {
    sleep((rand() % 10) + 1);
    exit(0);
}

/**
 * Hauptprogramm
 */
int main() {
    /* Initialisiere die Prozesstabelle */
    initialize_table();

    while (1) {
        accept();

        /* Erstelle die Pipe */
        int p[2];
        pipe(p);

        /* Forken eines Kindprozesses. */
        pid_t cpid = fork();

        if (cpid == 0) {
            /* Kindprozess */
            close(p[0]);
            childfunction();
        }
        else {
            /* Elternprozess */
            close(p[1]);
            int i = get_next_available_entry();
            add_process_to_table(i, cpid, p[0]);
            // printf("[%d] gestartet\n", cpid);
            reap_zombie_processes();
        }
    }

    return 0;
}

Und hier ist eine Beispielausgabe beim Ausführen des Programms mit den printf-Anweisungen auskommentiert:

[31066] gestartet
[31067] gestartet
[31068] gestartet
[31069] gestartet
[31066] erledigt
[31070] gestartet
[31067] erledigt
[31068] erledigt
[31071] gestartet
[31069] erledigt
[31072] gestartet
[31070] erledigt
[31073] gestartet
[31074] gestartet
[31072] erledigt
[31075] gestartet
[31071] erledigt
[31074] erledigt
[31081] gestartet
[31075] erledigt

3voto

jschmier Punkte 14832

Sie möchten keinen Zombie. Wenn ein Kindprozess stirbt und der Elternprozess noch läuft, aber niemals einen wait()/waitpid() Aufruf zum Abfragen des Status ausgibt, gibt das System die Ressourcen, die mit dem Kindprozess verbunden sind, nicht frei, und es bleibt ein Zombie/defekter Prozess in der Prozess-Tabelle zurück.

Versuchen Sie, Ihren SIGCHLD Handler näher an folgendes anzupassen:

void chld_handler(int sig) {
    pid_t p;
    int status;

    /* solange es Kinder zum Verarbeiten gibt, weiterlaufen */
    while (1) {

       /* Abrufen der Prozess ID des Kindprozesses (falls vorhanden) */
       p = waitpid(-1, &status, WNOHANG);

       /* Überprüfen, ob die Bedingungen erfüllt sind, die den Schleifendurchlauf beenden */
       if (p == -1) {
           /* bei Unterbrechung (EINTR) fortfahren */
           if (errno == EINTR) {
               continue;
           }
           /* bei allem anderen abbrechen (EINVAL oder ECHILD gemäß Manpage) */
           break;
       }
       else if (p == 0) {
           /* keine weiteren Kinder zu verarbeiten, daher abbrechen */
           break;
       }

       /* gültige Prozess ID des Kindprozesses abgerufen, entsprechend verarbeiten */
       ...
    }   
}

Sie können optional während der Ausführung des Signalhandlers zusätzliche SIGCHLD Signale mit sigprocmask() maskieren/blockieren. Die blockierte Maske muss ihren ursprünglichen Wert wieder annehmen, wenn die Signalbehandlungsroutine beendet ist.

Wenn Sie wirklich keinen SIGCHLD Handler verwenden möchten, könnten Sie versuchen, die Kinderverarbeitungsschleife an einer Stelle hinzuzufügen, an der sie regelmäßig aufgerufen wird und nach beendeten Kindern sucht.

0 Stimmen

Nach den von dir vorgenommenen Änderungen sehe ich keine Zombies erstellt .. Lass mich sehen, wie es sich im HTTP-Server-Daemon verhält ...

0 Stimmen

Ich rechne nicht mit größeren Problemen bei der Verwendung dieses Codes in Ihrem Daemon.

1voto

trojanfoe Punkte 117964

Die Variable 'clients' befinden sich in verschiedenen Prozessadressräumen nach fork() und wenn Sie die Variable im Kindprozess dekrementieren, wird dies den Wert im Elternprozess nicht beeinflussen. Ich glaube, Sie müssen SIGCHLD behandeln, um die Anzahl richtig zu handhaben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X