Programmaufrufe innerhalb von C/C++-Programmen

weiter zu Aufruf von Programmen in Windows

1. Aufruf von Programmen in Linux

Die hier zugrunde liegende Problemstellung ist die einer Button-Leiste, mit der Programme gestartet werden. Die Code-Beispiele sind Ausschnitte aus dem Programm silidock.


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:
      • im Falle von fork() mit exit(),
      • im Falle von vfork() mit kill() oder _exit() (= exit() ohne cleanup).
    • Das Auftreten von Zombies verhindern (Teil 2)
    • Fehlerbehandlung (Teil 3)
    • Unerwünschte Dateideskriptoren aufräumen (Teil 4)


fork() und vfork():

Header: <unistd.h>
fork() erzeugt einen Kind-Prozess der mit dem Eltern-Prozess identisch ist bis auf die neue PID (process identifier).
vfork() arbeitet wie fork(), dupliziert aber nicht den Prozess im Arbeitsspeicher, ist also performanter. Allerdings ist der Performance-Unterschied in neueren Unix-Versionen gering. vfork() ist seit 2008 nicht mehr POSIX-kompatibel. Probleme beim gleichzeitigen Zugriff auf geöffneten Dateien, die im Arbeitsspeicher im Eltern- und Kind-Prozess doppelt vorliegen, spielen hier keine Rolle, da vfork() hier nur zur Erzeugung eines neuen Prozesses benutzt wird.
Der mit fork() erzeugte Prozess kann im Fehlerfall mit exit() beendet werden, im Falle von vfork() würde sich dann das ganze Programm beenden, da der Kind-Prozess kein Duplikat des Eltern-Prozesses ist. Der Prozess muss stattdessen mit _exit() (Unterstrich!) beendet werden.
Der Kind-Prozess erbt die Dateideskriptoren des Eltern-Prozesses (siehe Teil 3).
vfork() und fork() hat im Erfolgsfall zwei Rückgabewerte, mit denen Eltern- und Kind-Prozess unterschieden werden können: An den Eltern-Prozess wird die PID des neuen Kind-Prozesses zurückgegeben, an das Kind eine 0. Im Fehlerfall wird eine -1 zurückgegeben und der Wert für errno (number of last error) gesetzt.

Die exec*-Familie: execl, execlp, execle, execv, execvp, execvpe

Header: <unistd.h>:
Der laufende Prozess wird mit exec*() vollständig durch einen neuen ersetzt.
execv() übernimmt als erstes Argument den Dateinamen der auszuführenden Datei. Die Datei muss ausführbar bzw. eine Script-Datei sein, die eine ausführbare Datei aufruft. Weitere Argumente repräsentieren die optionale Liste von Argumenten für den Programmaufruf. Alle Argumente von execv() sind C-Strings, die mit '\0' enden müssen.
Bei execl, execlp, und execle werden die weiteren Argumente als Zeiger auf C-Strings angegeben.
Bei execve und execle können außerdem die Umgebungsvariablen gesetzt werden.
Die exec-Funktionen haben im Erfolgsfall keinen Rückgabewert, nur im Fehlerfall wird -1 zurückgegeben und der Wert für errno (number of last error) gesetzt.


Auruf von Programmen mit execv und fork().

Eltern- und Kind-Prozess können über den Rückgabewert von fork() unterschieden werden:
Kind-Prozess: 0, Eltern-Prozess: PID (process identifier), Fehlerfall: -1.
Mit getpid() (aktueller Prozess) und getppid() (Eltern-Prozess) können die Prozesskennungen (PIDs) ermittelt werden.
Hier ein vorläufiger Beispiel-Code mit vfork(), ein verkürzter Ausschnitt aus dem Quellcode von silidock. silidock ist eine einfache Menüleiste mit Buttons zum Programmaufruf. Der Programmaufruf wird in der Funktion execute_program vorgenommen. Das Argument "program_call" enthält Name und Pfad der auszuführenden Datei, "params" oder weitere Argumente bleiben ungenutzt.



// C++ Header
#include <iostreamh>
#include <cerrnoh>
// C-Header
extern "C" {
   #include <unistd.h> // fork, execv, exit
}

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();
        cout<< program_call << ": child PID: " << child_pid << ", parent PID: " << getppid() << endl;
			
        // 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:
    {
        cout<< "pid of parent: "<< getpid() << endl;
    }
}
    				

Diese etwas gewöhnungsbedürftige if-else-Konstruktion, die gewöhnlich im Sinne von "entweder-oder" funktioniert, scheint hier auf den erste Blick im Sinne von "sowohl-als-auch" abzulaufen, allerdings liege zwei Prozesse vor. Deutlich wird das, wenn jeweils die PID ermittelt wird.
Der Code nach dem execv-Aufruf wird nur dann abgearbeitet, wenn execv() fehlschlägt und das Programm nicht aufgerufen werden kann. In diesem Fall ist es notwendig, den Kind-Prozess mit _exit() zu beenden.
Im Falle von fork() sähe das Beispiel ganz ähnlich aus. Nur die Fehlerbehandlung würde über exit() statt über _exit() erfolgen, was ein cleanup (u.a. Standard-Input/Output schließen) zur Folge hätte. Im Falle von vfork() würde dieser cleanup das Hauptprogramm beenden, denn hier wurde der Eltern-Prozess nicht dupliziert.
Eine Alternative zu _exit() wäre auch
kill(child_pid, SIGKILL);
da die PID des Kind-Prozesses über getpid() ermittelt werden kann.

Das obige Code-Beispiel würde gut funktionieren und im normalen Gebrauch auf PCs auch keine nennenswerten Probleme aufwerfen.
Sollte es aber auf einem Rechner laufen, der nicht nach mehr oder weniger kurzer Zeit herunter fährt und damit alle Programme beendet und wieder neu startet, würden dabei zwei Probleme auftauchen:
Das Code-Beispiel produziert sogenannte Zombies am laufenden Band und die aufgerufenen Programme besitzen Dateideskriptoren (file descriptors), die sie jeweils vom Elternprozess übernommen haben, aber im hiesigen Anwendungsfall nicht benötigen.


weiter zu Teil 2: Erste Aufräumarbeiten