Programmaufrufe innerhalb von C/C++-Programmen

weiter zu Aufruf von Programmen in Windows

Aufruf von Programmen in Linux, Teil 2

Erste Aufräumarbeiten: Zombies

zurück zu Aufruf von Programmen in Linux, Teil 1

Zur Erinnerung das Vorgehen in Kurzform:

  1. Einen Kind-Prozess erzeugen mit vfork() oder fork()
  2. Mit exec() den Kind-Prozess durch das neue Programm ersetzen
  3. Aufräumarbeiten:
    • Im Fehlerfall von exec() den Kind-Prozess beenden (Teil 1)
    • Das Auftreten von Zombies verhindern:
      • entweder über signal(SIGCHLD, SIG_IGN)
      • oder über waitpid().
    • Fehlerbehandlung
    • Unerwünschte Dateideskriptoren (file descriptors) aufräumen (Teil 3)


Zombies oder defunct processes

Zombies sind Prozesse, die sich permanent im Status "terminated" befinden. Der Prozess ist zwar beendet, aber der Eintrag in der Prozesstabelle besteht weiter. Dem Prozess ist noch eine PID zugewiesen, obwohl er nichts mehr macht. Die Speicheranforderung, die ein Zombie verursacht, ist minimal und am PC dürfte deren Existenz kaum auffallen, aber beispielsweise bei Servern, die ständig laufen, sammeln sich Zombies an und werden zum Problem. Das kill -Kommando hilft hier nicht weiter, denn der Prozess ist bereits tot.

Das Problem

Die Kombination fork() und execv() produziert solche Zombies, denn wenn der Kind-Prozess beendet ist, soll der Eltern-Prozess den Exit-Status lesen können. So lange das nicht geschehen ist, verbleibt der Kind-Prozess im halb-toten Zustand.

Lösungsmöglichketen

Es gibt mehrere Möglichkeiten, Zombies zu verhindern. Zwei übliche werden hier vorgestellt:
1. Den Eltern-Prozess auffordern, auf das Lesen des Exit-Status zu verzichten:
Die Anweisung
signal(SIGCHLD, SIG_IGN);
bewirkt, dass der Eltern-Prozess nicht auf eine Antwort beim Beenden der Programms wartet. Die Anweisung muss nur einmal ausgeführt werden, denn sie bezieht sich auf den Eltern-Prozess, der gleich bleibt.
2. Den Exit-Status lesen mit waitpid:
Dafür gibt es eine praktische "Aufräum-Schleife" (aus: stackoverflow ):

pid_t p;
/* Reap all pending child processes */
do {
   p = waitpid(-1, NULL, WNOHANG);
} while (p != (pid_t)0 && p != (pid_t)-1);
waitpid liest hier den Status aller Kind-Prozesse aus (Parameter -1),
ohne den Status zu speichern(Parameter NULL) und
ohne den Eltern-Prozess anzuhalten (Parameter WNOHANG).
Der Rückgabewert von waitpid ist die PID eines Kind-Prozesses oder im Fehlerfall -1 oder 0 solange noch mehrere nicht behandelte Kind-Prozesse vorliegen.
Diese Schleife wird entweder in gewissen Zeitabständen aufgerufen oder wie hier bei jeder Erzeugung eines Kind-Prozesses. Wenn der Eltern-Prozess keinen Zugriff auf den Kind-Prozess haben muss, genügt die signal-Anweisung.

Kontrolle

Um herauszufinden, ob sich Zombies in den Arbeitsspeicher eingeschlichen haben, wird
ps -x
in die Konsole eingegeben und nach Einträgen gesucht, die beispielsweise so aussehen:
6186 ? Z 0:00 [Your_program]
Das "Z" markiert den Prozess als Zombie.


Der Code-Schnipsel zum Aufruf sieht dann folgendermaßen aus:
(dies ist ein verkürzter Ausschnitt aus dem Quellcode von silidock)

// C++ Header
#include <iostream.h>
#include <cerrno.h>
// C-Header
extern "C" {
   #include <unistd.h> // fork, execv, getpid  
   #include <sys/wait.h> // for signal, waitpid
}

void execute_program(const char* program_call, const char* param )
{
    pid_t child = vfork();

    if(child == 0) // executed by child process: 
    {
        int child_pid = getpid();
			
        // replace the child process with exec*-function
        char *args[2]; // arguments for exec
        args[0] = (char*)program_call; // first argument is program_call
        args[1] = (char*)param;
        args[2] = NULL; // no arguments...

        execv(program_call,args);

        // executes if execv failed
        _exit(2);
    }
    else if (child == -1) // error
    {
        cerr<< "fork to call program failed: " << program_call << endl;
    }
    else // executed by parent process:
    {
        // prevent zombies: 
        pid_t p;
        // Reap all pending child processes
        do {
            p = waitpid(-1, NULL, WNOHANG);
        } while (p != (pid_t)0 && p != (pid_t)-1);
    }
}    			

weiter zu Teil 3: Fehlerbehandlung