Sehr Umfangreiche Webseite zum Programmieren in C Perl CGI, Skripting, Linux, SystemprogrammierungC C++ C/C++ ANSI C Linux Linuxsystemprogrammierung Unix fork system exec Prozess Prozesse getpid getppid getlogin Linuxsystemprogrammieren fork system exec Prozess Prozesse getpid getppid getlogin 2c. Kreiern und Starten von Prozessen

2.6. getpid,getppid - Hallo Prozeß, wer bist du?           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Bis jetzt sind Sie in der Lage die ID unseres Ausführenden Programms zu ermitteln. Was passiert aber, wenn Sie die ID von dem Kindprozess oder der Kindprozess die ID seiner Eltern wissen will. Dafür gibt es auch zwei einfache Funktionen ...

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);    //Ermitteln des aktuellen Prozesses
pid_t getppid(void);   //Ermitteln des Elternprozesses

Hierzu nun ein Beispiel ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
 pid_t kind1;

 while(1)
  {
   switch(kind1=fork())
    {
      case -1 : printf("Fehler bei fork()..........\n");  exit(0);
      case  0 : printf("Kindprozess: Mein PID ist %d\n",getpid());
                printf("Kindprozess: Meine Eltern haben die PID %d\n",getppid());
                exit(1);
      default : printf("Elternprozess: Ich habe folgendes Kind PID : %d\n",kind1);
                printf("Elternprozess: Meine PID lautet %d\n",getpid());
                printf("Elternprozess: Die PID meiner Eltern lautet %d\n",getppid());
                exit(2);
    }
  }
 return 0;
}

Die Funktionen getpid und getppid dürften nicht allzu schwer verständlich sein. An diesem kleinen Programmbeispiel lässt sich schön das Multitasking demonstrieren. Bei diesem Programm wird nicht zuerst der Kindprozess und dann der Elternprozess abgearbeitet sondern abwechselnd da Sie ja zwei Prozesse ausführen. Ich denke mal wer bisher die Funktion fork() oder das Thema Prozesse und Multitasking noch nicht verstanden hatte, dürfte jetzt wohl ein bisschen klarer sehen. Aber das war noch lange nicht alles dazu. Uns fehlt noch die KONTROLLE über den Prozessen. Denn manches mal ist es nötig, dass ein Prozess abhängig von einem anderen ist.

2.7. getlogin - Wer benutzt den Prozeß gerade?           zurück Ein Kapitel tiefer Ein Kapitel höher zum Inhaltsverzeichnis

Wollen Sie zum Beispiel ein Programm schreiben und in einer Log-Datei schreiben welcher User gerade auf den Prozess zugreift, könnten Sie das in der Konsole mit ...

echo $LOGNAME

...abfragen. Geben Sie aber jetzt zum Beispiel folgendes ein ...

export LOGNAME=guru

Jetzt fragen Sie den Namen nochmals mit echo $LOGNAME ab und nun steht hier guru.

Also ist hiermit nicht Garantiert, dass Sie so den echten Loginnamen erhalten. Für diesen Fall können Sie folgende Funktion verwenden ...

#include <unistd.h>

char *getlogin(void)

Im Fehlerfall liefert diese Funktion NULL zurück. Folgendermaßen können Sie diese Funktion anwenden ...

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
 char *login = malloc(strlen(getlogin()));
 login[0]=0;
 strcpy(login, getlogin());
 printf("Momentan benutzt %s den Prozeß %d\n",login,getpid());
 return 0;
}

Ein häufiger Fehler warum NULL zurückgegeben wird, ist das dem Prozess oftmals kein Terminal zugeordnet werden kann. Beispielsweise bei einem Dämonprozess. Verwenden Sie für dieses Programm ein echtes Terminal.

2.8. system - Prozeß startet Porzeß ohne fork?           zurück Ein Kapitel tiefer Ein Kapitel höher zum Inhaltsverzeichnis

Um aus einem Lauffähigen Programm ein anderes Programm zu starten, steht Ihnen die Funktion system() zur Verfügung ...

#include <stdlib.h>

int system(const char *kommandozeile);

Die Rückgabe bei Erfolg ist ungleich 0 und bei Fehler -1. Als kommandozeile können Sie alles eingeben was auch in der Kommandozeile erlaubt ist z.b. ls-s , dir usw.

Ich möchte Ihnen den Befehl system() anhand eines Beispiels demonstrieren, dass den Inhalt aller Verzeichnisse auf dem Bildschirm ausgibt, die Sie in der Kommandozeile eingeben. Das Programm benutzt den Dos-Befehl DIR /W bzw. unter Unix/Linux ls -C. Dies Programm ist für Linux/Unix geschrieben. Wenn Sie es unter Dos laufen lassen wollen, brauchen Sie nur den Befehl ls -C durch dir /w auszutauschen in sämtlichen Zeilen wo dies eben vorkommt ...

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
 int x;
 char kommando[100];

 if(argc == 1)
    system("ls -C");
 else
  {
   for(x=1; x<argc; x++)
    {
     sprintf(kommando,"ls -C %s",argv[x]);
     printf("Kommando : %s\n",kommando);
     system(kommando);
     printf("");getchar();
    }
  }
 return 0;
}

Sie können jetzt in der Kommandozeile eingeben ...

system1 home floppy cdrom

Somit gibt das Programm nacheinander den Inhalt von /home dann /floppy und anschließend von /cdrom aus. Als weiteres Beispiel möchte ich Ihnen ein weiteres Programm zeigen. Wir wollen ein Programm schreiben das ein Menü wie folgt haben soll ...

-1- Programm Editieren
-2- Programm compilieren
-3- Programm ausführen
-4- Weiter Editieren von programmname.c
-5- Ende

Dies Programm ist für Linux/Unix für Leute die Ihre Programme mit dem Editor Emacs schreiben. Ich benutze als Compiler den GNU-gcc und eben den entsprechenden Befehl gcc zum compilieren eines Programms. Wenn Sie einen anderen Compiler verwenden oder entsprechendes unter Linux/Unix machen wollen, müssen Sie das Programm einfach Ihren Bedürfnisse anpassen. Das allgemeine Programm bietet nur die Grundlage und kann noch an vielen Stellen verbessert werden ...

#include <stdio.h>
#include <stdlib.h>

/*Funktion zum Anhängen eines Strings an den anderen*/
char *ownstrcat(char *dest, char *src)
{
 char *sav;

 sav = dest;
 while (*dest)
    ++dest;
 while (*dest++ = *src++);

 return sav;
}

/*Funktion um aus dem String ein Lauffähigen Kommandonamen zu
erzeugen damit das Programm getestet werden kann
z.B. aus Hello.c wird Hello */
char *kommandoname(char *dest, char *src)
{
 while ((*dest++ = *src++) && (*src != '.'));

 *dest++='\0';

 return dest;
}

/*Funktion zum löschen des Inhaltes eines Arrays*/
char *empty(char *dest)
{
 *dest = '\0';
 return dest;
}


int main()
{
 int wahl;
 char temp[100],temp2[100],temp3[100];
 char editor [] = "emacs "; /*Bitte anpassen*/
 char compiler[] = "gcc";   /*Ebenfalls anpassen*/
 char kommandozeile[100];
 temp[0] = '\0';

 do{
      printf("-1- Programm Editieren\n");
      printf("-2- Programm compilieren\n");
      printf("-3- Programm ausführen\n");
      if(temp[0]!='\0')
        printf("-4- Weiter Editieren von %s\n",temp);
      else
        printf("-4- Noch keine Datei zum Editieren erstellt\n");
      printf("-5- Ende\n");
      printf("Ihre Auswahl bitte : ");
      scanf("%d",&wahl);
      switch(wahl)
       {
        case 1 : empty(kommandozeile);
                 printf("Welches Programm wollen sie editieren : ");
                 scanf("%s",temp);
                 ownstrcat(kommandozeile,editor);
                 ownstrcat(kommandozeile,temp);
                 system(kommandozeile);
                 break;
        case 2 : empty(kommandozeile);
                 sprintf(kommandozeile,"%s -o %s %s",
                         compiler,kommandoname(temp2,temp),temp);
                 system(kommandozeile);
                 break;
        case 3 : empty(kommandozeile);
                 sprintf(temp3,"./%s",kommandoname(temp2,temp));
                 system(temp3);
                 empty(temp2);
                 empty(temp3);
                 break;
        case 4 : empty(kommandozeile);
                 ownstrcat(kommandozeile,editor);
                 ownstrcat(kommandozeile,temp);
                 system(kommandozeile);
                 break;
        default: break;
       }
   }while(wahl != 5);
 return 0;
}

Sie sehen wie es möglich ist, mit ein paar Zeilen Code ein Schnittstelle zu programmieren, womit Sie auf mehrere Programme zugreifen können. Anstatt immer erst von Hand den Editor zu öffnen und anschließend das Programm von Hand in der Kommandozeile compilieren und anschließend wieder bearbeiten, haben wir das alles in einem Menü gepackt. Somit ist die Arbeitsweise auch schneller ein Programm zu schreiben.

Um zu testen ob ein Kommandoprozessor überhaupt verfügbar ist, brauchen sie dies nur mit ...

if(system(NULL) == 0)
   {........FEHLER...........}

... zu testen.

Sicherlich kommt Ihnen das Verhalten der Funktion system() irgendwie bekannt vor. Sie starten ein Programm und rufen während dessen Ausführung einen neuen Prozess auf und das Aufrufende Programm wartet solange. Manch einer wird es erraten haben, intern sieht unser Funktionsaufruf system folgendermaßen aus : fork(), exec(), waitpid() Einfacher noch, system() übernimmt uns die ganze Arbeit der Fehlerbehandlung der Signale ab.

system und Sicherheit
Leider gibt es noch ein paar kleine Schönheitsfehler der Funktion system(), die sie aber nicht stören sollten, wenn sie folgendes Beachten.

Vermeiden sie solche Schreibweisen dieser Funktion ...

sprintf(kommando,"fgrep -i %s adressen.txt", name);
system(kommando);

Auf den ersten Blick mag dies ganz praktisch erscheinen. Sie suchen mittels fgrep in der Datei adressen.txt nach einem bestimmten Namen. Vom Buffer Overflow mal abgesehen haben Sie hier eine noch viel größere Sicherheitslücke. Nehmen Sie mal an, der User hat für Namen folgendes eingegeben ...

"root" /etc/passwd

Sie können dies gerne in der Kommandozeile testen ...

find -i "root" /etc/passwd adressen.txt

Damit haben sie dem Angreifer ein kleine Chance gegeben, sich bei Ihnen Zugang zu schaffen. In solch einem Beispiel sind Sie mit den exec() Funktionen sicherer beraten ...

execle ("/bin/fgrep", "-i", name, "adressen.txt" ,
        "PATH=/usr/bin:/bin:/usr/sbin:/sbin\0USER=nobody");

Dies ist ein Beispiel, wie man es oft in einem CGI-Programm findet.

Die zweite Gefahr von system() kommt weit aus seltener vor. Starten sie in einem Prozess, bei dem die setuser/setgroup-ID gesetzt ist, bleibt dieses bei einem Aufruf von system() auch bei dem neuen Prozess bestehen. Auf älteren Systemen kann man in diesem Fall den Input Field Seperator manipulieren. Bei den neueren Systemen funktioniert dies aber nicht mehr.

Wenn sie diese beiden Sicherheitsmaßnamen beachten, ist die Funktion system() eine gute Wahl.

2.9. exec-Funktionen - Prozesse komplett überlagern           zurück Ein Kapitel höher zum Inhaltsverzeichnis

Die exec-Funktionen (execute) laden und starten ein anderes Programm. Somit ersetzt das neue Programm den laufenden Prozess vollkommend. Das neue Programm beginnt seine Ausführung in der main-Funktion. Sämtliche, durch das aufrufende Programm geöffnete Dateien, bleiben geöffnet. Sie stehen damit auch dem neuen Programm zur Verfügung. Folgende sechs verschiedene exec-Funktionen stehen Ihnen dabei zur Verfügung ...

#include <unistd.h>

int execl(char *name, char *arg0, ... /*NULL*/);
int execv(char *name, char *argv[]);
int execle(char *name, char *arg0, ... /*,NULL, char *envp[]*/);
int execve(char *name, char *arv[], char *envp[]);
int execlp(char *name, char *arg0, ... /*NULL*/);
int execvp(char *name, char *argv[]);

Ein exec-Aufruf bewirkt somit, dass über das als Argument übergebene Programm, in den Speicher geladen wird, und zwar über das alte Programm, das die exec - Funktion aufruft. Das alte Programm ist somit nicht mehr verfügbar da die Segmente durch das neue Programm überschrieben wurden.

Die Buchstaben l,v,p und e am Ende der Funktionsnamen bestimmen das Format und den Umfang der Argumente, sowie die Verzeichnisse, in denen das zu ladende Programm zu suchen ist.

Buchstabe Bedeutung
l (list) Die Argumente aus der Kommandozeile werden in der Form einer Liste arg0,arg1,....argn,NULL übergeben. Diese Form verwendet man wenn die Anzahl der Argumente bekannt ist.
v (vector) Die Argumente aus der Kommandozeile werden in Form eines Vektors argv[] übergeben. Die einzelnen Argumente sind also argv[0],argv[1],...argv[n]. Das letzte Argument (argv[n]) muss der NULL - Zeiger sein.
p (path) Die mit Namen angegebene Datei wird nicht nur im aktuellen Directory gesucht, sondern auch in den durch die Umgebungsvariable PATH bestimmten Directorys.
e (environment) Die Funktion erwartet die Environment - Liste als Vektor (envp[]) und benutzt nicht das aktuelle Environment.


Zuerst ein Beispiel zu execl . Schreiben Sie dazu zuerst folgendes Programm ...

#include <stdio.h>

int main(int argc, char *argv[])
{
 int i=0;
 printf("%s\n",argv[0]);
 printf("Das neue Programm läuft und gibt aus : ");
 while(argv[++i] != NULL)
 printf("%s ",argv[i]);
 return 0;
}

Speichern und compilieren Sie es. Nennen wir es hier mal hellow. Dieses Programm wollen wir jetzt mit Hilfe der execl()-Funktion aufrufen, mit weiteren Strings als Argumente. Hier der Programmaufruf ...

execl("hellow"," ","Hallo", "Welt", "wie", "geht's",NULL);

Damit rufen Sie das neue Programm hellow auf und übergeben dem Programm noch weitere Argumente. Dies entspricht dem selben, als wenn Sie das Programm hellow in der Kommandozeile folgendermaßen aufrufen würden ...

hellow Hallo Welt wie geht's

In dem zweiten String von execl() kann ich mir einen Eintrag ersparen, da dieser bei dem neu Aufgerufenen Programm der Adresse von argv[0] entspricht und steht somit für den Programmnamen. Mit dem folgenden Programm (execl) wird also das Programm hellow aufgerufen (vorausgesetzt es existiert) und hellow gibt anschließend aus ...

Hallo Welt wie geht's
Hier der Code zu execl.....
#include <stdio.h>
#include <unistd.h>

int main(int argc, int *argv[])
{
 printf("%s wird ausgefuehrt...\n\n", argv[0]);
 printf("%s ruft auf ", argv[0]);
 execl("hellow","","Hallo", "Welt", "wie", "gehts",NULL);

 return 0;
}

Das selbe wollen wir jetzt auch mit einem Vektor machen. Das folgende Programm demonstriert die Funktion execv() ...

#include <stdio.h>
#include <unistd.h>

int main(int argc, int *argv[])
{
 printf("%s wird ausgefuehrt...\n\n", argv[0]);
 printf("%s ruft auf ", argv[0]);
 execl("hellow","","Hallo", "Welt", "wie", "gehts",NULL);

 return 0;
}

Diese Programm macht wieder das selbe wie unser Programm mit execl() schon zuvor. Nur das Sie die Kommandozeilenargumente als Vektor übergeben. Sie können aber auch die Argumente die Sie beim Programmstart von execv eingeben, an unser neues Programm übergeben ...

#include <stdio.h>
#include <unistd.h>

int main(int argc, int *argv[])
{
 printf("%s wird ausgefuehrt...\n\n", argv[0]);
 printf("%s ruft auf ", argv[0]);
 execv("hellow",argv);

 return 0;
}

Bei diesen Beispielen dürfte Ihnen jetzt klar geworden sein, wo die Unterschiede der Form der Argumentübergabe zwischen execl, execlp, execle UND execv, execvp, execve liegt ...

execl,execlp,execle

Hier müssen die Kommandozeilenargumente des neu zu startenden Programms in Form einer Liste übergeben werden. Das Ende der Argumentliste wird mit einem NULL-Zeiger markiert ...

execlp(programmname,"bla","blabla","blubb",NULL);

execv,execvp,execve
Hier müssen Sie die Kommandozeilenargumente des neu zu startenden Programms in Form eines Stringvektors (char *argv[]) übergeben ...

execv(programmname,argv);

Die Benutzung der Funktionen execle und execve, also zusätzlich mit dem Buchstaben e, wird hauptsächlich dazu verwendet, um dem neu zu startende Programm das aktuelle Environment zu vererben. z.B. mit execle ...

int main(int argc, char *argv[], char *envp[])
{
.......................
   execle(programmname,"Hallo","Welt",NULL,envp);
........................
}

...oder mit execve...

execve(programmname,argv,envp);

Bleibt uns nur noch eine kleine Anmerkung zu machen zu dem Buchstaben p, also execlp und execvp. Enthält der beim Aufruf angegebene Dateinamen einen Slash ('/' = Linux/Unix) oder einen Backslash ('\' = MS-Dos), so wird er als Pfadname interpretiert. Ansonsten wird in den PATH-Directorys nach einer Ausführbaren Datei mit diesem Namen gesucht und gestartet.

close-on-exec-Flag
Jetzt kommen wir zur Erklärung was das close-on-exec-Flag bewirkt wenn es gesetzt oder nicht gesetzt ist. Im Kapitel fcntl haben sie bereits gelernt wie man das close-on-exec-Flag verändern kann. Wenn das Flag nicht gesetzt ist, (Default-Einstellung) bleiben alle bereits geöffneten Dateien, beim neu durch einen exec-Aufruf gestarteten Programm, offen. Ist das Flag aber gesetzt, was Sie mit der Funktion fcntl einstellen können, werden die entsprechenden Filedeskriptoren beim exec-Aufruf geschlossen.

Die einzelnen exec - Funktionen haben wir ja bereits durchgenommen. Was noch wichtig ist, es ändert sich bei einem Programm das Sie mit exec Aufrufen, nicht die PID, da im Gegensatz zu fork() nicht ein neuer Prozess gekreiert wird. Es werden nur die Segmente des aktuellen Prozesses durch den neuen überschrieben.

Die execl()-Funktion führt vor allem bei Programmierern, die diese Funktion das erste Mal nutzen, zur Verwirrung. Dies liegt daran, dass das zweite an execl() übergebene Argument nicht das erste Kommandozeilenargument ist, das an dem aufzurufende Programm (spezifiziert in path) übergeben wird. Vielmehr ist das zweite Argument der Name, unter dem der neue Prozess in der vom ps-Befehl erzeugten Prozessliste aufgeführt wird. Das erste Argument, das an das (in path spezifizierte) Programm übergeben wird, ist also tatsächlich das dritte Argument, das an execl() übergeben wird.

#include <signal.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  pid_t kind;

  printf("Mein Programmname lautet : %s\n",*argv);
  printf("Mein PID lautet : %d\n",getpid());

  printf("Ich werde jetzt durch 'ps -u' ersetzt!!!\n");

  execl ("/bin/ps", "Prozesse", "-u", NULL);

  printf("Ich werde nie ausgeführt :(\n");

  return 0;
}

An diesem simplen Beispiel können Sie sehen, dass der Prozess mit dem Namen *argv und der der Nummer PID durch den Prozess ...

execl ("/bin/ps", "Prozesse", "-u", NULL);

...also den Aufruf ps -u ersetzt wird. 'Prozesse' ist der Eintrag des Namens der in der Prozessliste geführt wird. Und als Beweis, dass der Prozess komplett durch den neuen Ersetzt wird, wird der Text 'Ich werde nie ausgeführt' auch wirklich nie ausgeführt. Sie können ja mal execl durch system("ps -u"); ersetzen. Damit wird nämlich die nächste Zeile wieder ausgeführt.

Weiter mit 3.1. Dämonprozesse - Prozesse aus dem Untergrund