Sehr Umfangreiche Webseite zum Programmieren in C Perl CGI, Skripting, Linux, SystemprogrammierungC C++ C/C++ ANSI C Linux Linuxsystemprogrammierung Unix Prozess signal Prozess Prozesse Signale IPC raise kill alarm pause Linuxsystemprogrammieren signal Prozess Prozesse Signale IPC raise kill alarm pause 4a. Signale

4.2. raise und kill           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Der Syntax von raise() lautet ...

#include <signal.h>

int raise(int sig);

Mit der Funktion raise, lassen sich Software-Signale vom Typ sig an ein ausführbares Programm senden. Wenn das Programm einen Signal-Handler für den durch sig angegebenen Signaltyp installiert hat, wird diese Routine ausgeführt. Ist kein Handler installiert, wird die Standardaktion (SIG_DFL) für den jeweiligen Signaltyp ausgeführt. Als einfaches Beispiel wollen wir ein Programm schreiben, dass uns nach 2 Zahlen zum dividieren abfragt. Wenn der Divisor 0 ist wollen wir an unser Programm das Signal SIGFPE (siehe die Kapitel zuvor) senden, um das Programm zu beenden ...

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

int main()
{
 int a,b;

 printf("Zahl1 : ");
 scanf("%d",&a);
 printf("geteilt durch : ");
 scanf("%d",&b);

 if(b==0)
   raise(SIGFPE);
 else
   printf("Ergebniss = %d\n",a/b);

 return 0;
}

Wenn Sie also als Divisor den Wert 0 eingeben haben, senden wir an das Programm das Signal SIGFPE. Das Signal SIGFPE führt als Default-Aktion das Beenden des Programms durch.

POSIX.1 schreibt die Funktion raise übrigens nicht vor. ANSI-C hingegen schon.

kill
Für Linux-Programmierer gibt es auch die Gleichnamige Shellfunktion kill, die folgenden Syntax besitzt ...

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

int kill(pid_t pid, int signalnummer);

Die Schreibweise kill(getpid(),signalnummer) ist übrigens gleichwertig zu raise(signalnummer). Mit kill können Sie anderen Prozessen ein Signal senden. Im Prozesstabelleneintrag des Zielprozesses wird dann dementsprechendes Bit gesetzt. Damit dies aber Möglich ist, müssen noch folgende Vorraussetzungen erfüllt sein ...

Im Fehlerfall gibt kill -1 zurück, ansonsten 0.

4.3. alarm und pause           zurück Ein Kapitel tiefer Ein Kapitel höher zum Inhaltsverzeichnis

Zur Ergänzung noch die Funktionen alarm und pause. Zuerst der Syntax zu alarm ...

#include <unistd.h>

long alarm(unsigned long sekunden);

Mit dieser Funktion kann ein Prozess für eine vorgegebene Zeit schlafen gelegt werden und vom Signal SIGALRM geweckt werden. Hier ein kurzes Beispiel ...

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

int main()
{
 while(1)
  alarm(0);

 return 0;
}

Dieser Prozess wird so lange Schlafen, bis sie Ihn mit dem Signal ...

kill -SIGALRM PID

... von der Konsole aus aufwecken. Die Funktion alarm hat übrigens eine Haltbarkeit von über 135 Jahre ;)

pause
Mit der Funktion pause ...

#include <unistd.h>

int pause(void);

... legen Sie einen Prozess so lange schlafen, bis ein Signal eintrifft. Logischerweise darf diesese Signal nicht als ein ignorierbares Signal eingetragen sein. Diese Funktion kann in einigen Fällen recht unzuverlässig sein. Daher werden sie im Kapitel Signale und IPC's eine gute Alternative dafür kennen lernen.

4.4. Ein besseres Signalkonzept           zurück Ein Kapitel tiefer Ein Kapitel höher zum Inhaltsverzeichnis

Auf Linuxsystemen gibt es außer dem von ANSI-C vorgeschriebenen Signalkonzept noch ein besseres Konzept. Dies wurde entwickelt, da beim alten Signalkonzept folgendes zu Bemängeln war ...

Dafür wurde das neue Signalkonzept geschrieben.

Zuerst benötigen Sie dazu eine Variable vom primitiven Datentypen sigset_t. Beispielsweise ...

sigset_t signal_menge;

Zuerst müssen Sie die Signalmenge initialisieren mit der Funktion sigemptyset ...

#include <signal.h>

int sigemptyset(sigset_t *sig_m);

Mit dieser Funktion werden alle Signalmengen aus sig_m entfernt und gleichzeitig wird die Variable hiermit initialisiert. Mit dem Beispiel unserer Variable sieht dies folgendermaßen aus ...

sigset_t signal_menge;
sigsetempty(&signal_menge);

Da Sie ja jetzt eine Art Maske für unsere Signalmenge haben, können Sie fröhlich neue Signale für diesen Prozess hinzufügen. Dies machen Sie einfach mit der Funktion ...

#include <signal.h>

int sigaddset(sigset_t *sig_m, int signr);

Die Signalemenge sig_m ist die selbe die Sie bereits mit sigemptyset initialiert haben. signr sind die Signale die sie der Signalmenge hinzufügen wollen. Auch hier wird geraten, den symbolischen Namen zu verwenden. Weiter zu unserem Beispiel ...

sigset_t signal_menge;

sigemptyset(&signal_menge);
sigaddset(&signal_menge, SIGINT);
sigaddset(&signal_menge, SIGCHLD);

Hier haben Sie SIGINT und SIGCHLD zu unserer Signalmenge hinzugefügt. Im Gegensatz dazu könnten Sie mit der Funktion ...

#include <signal.h>

int sigdelset(setsig_t *sig_m, int signr);

...das Signal signr aus der Signalmenge sig_m entfernen. Wollen Sie jetzt überprüfen ob ein Signal in der Menge vorhanden ist, um es anschließend zu setzen, falls es noch nicht vorhanden ist, können Sie folgende Funktion dazu benutzen ...

#include <signal.h>

int sigismember(sigset_t sig_m,int signr);

Wenn ein Signal in der Signalmenge vorhanden ist, gibt diese Funktion 1 zurück. Falls nicht dann 0. In unserem Beispiel sieht dies so aus ...

sigset_t signal_menge;

sigemptyset(&signal_menge);
sigaddset(&signal_menge, SIGINT);
if( (sigismember(&signal_menge, SIGCHLD)) == 0)
  sigaddset(&signal_menge, SIGCHLD);
else
  printf("SIGCHLD ist bereits in dieser Signalmenge vorhanden!!\n");

Irgendwann werden Sie mal vorhaben die Signalmaske zu Erfragen oder Sie zu verändern. Dafür steht Ihnen die Funktion ...

#include <signal.h>

int sigprocmask(int wie, const sigset_t *sig_m, sigset_t *alt_sig_m);

Dabei unterscheidet diese Fuktion sich in 3 Fällen ...

Folgende drei wie Konstanten stehen in dabei zur Verfügung ...

Wollen Sie die Signalmaske ändern, oder keine Signale während eines bestimmten Codeabschnittes erlauben, gibt es dafür die Funktion ...

#include <signal.h>

int sigsuspend(const sigset_t *sig_m);

Mit dieser Funktion können Sie einen Prozess solang blockieren, bis ein Signal eintrifft. Und wer in sigsuspend parallelen zu der Funktion pause() zieht hat recht. Nur ist sigsuspend die zuverlässiger Alternative zur Funktion pause, da dies mit der Funktion sigprocmask zusammenhängt und eine einzige atomare Operation ist.

Bei Fehler geben übrigens alle Funktionen in diesem Kapitel -1 zurück.

4.5. Signale als IPC verwenden           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Signale stellen die primitivste Art der Kommunikation zwischen zwei Prozessen da. Mit den Signalfunktionen alleine ist natürlich noch keine Interprozesskommunikation (IPC) möglich. Erst mit der Funktion kill() kann ein Prozess dem anderen ein Signal schicken. Der Prozess kann anschließend dementsprechend reagieren. Allerdings werden Signale äußerst selten als IPC's verwendet. Wollen wir uns doch mal bildlich ansehen wie das Funktionieren könnte. Wie wollen mit fork einen zweiten Prozess kreieren. Beide Prozesse (Eltern und Kind) sollen anschließend abwechseln eine Ausgabe auf dem Bildschirm (STDOUT_FILENO) machen (anstatt dem Bildschirm können sie natürlich auch eine Datei verwenden). Folgendermaßen gehen wir dabei vor ...

Signale als IPC's

Der Kindprozess wird in einem Wartezustand versetzt und der Elternprozess schreibt etwas auf dem Bildschirm.

Signale als IPC's

Der Elternprozess schickt dem Kindprozess ein Signal mittels kill() und der Elternprozess wird in einem Wartezustand versetzt.

Signale als IPC's

Der Kindprozess ist dran mit dem Schreiben auf dem Bildschirm.

Signale als IPC's

Nun sendet der Kindprozess dem Elternprozess ein Signal mittels kill() und der Kindprozess befindet sich wieder in einem Wartezustand.

Nun geht das ganze Spiel wieder von vorne los. Wollen wir uns nun das Programm dazu ansehen ...

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
enum { FALSE, TRUE };

sigset_t sig_m1, sig_m2, sig_null;
int signal_flag=FALSE;

void sig_func(int signr)
{
 start_signalmenge();
 signal_flag = TRUE;
}


void start_signalmenge()
{
 if(signal(SIGUSR1, sig_func) == SIG_ERR)
  exit(0);
 if(signal(SIGUSR2, sig_func) == SIG_ERR)
  exit(0);

 sigemptyset(&sig_m1);
 sigemptyset(&sig_null);
 sigaddset(&sig_m1,SIGUSR1);
 sigaddset(&sig_m1,SIGUSR2);

 if(sigprocmask(SIG_BLOCK, &sig_m1, &sig_m2) < 0)
   exit(0);
}

void message_for_parents(pid_t pid)
{
 kill(pid,SIGUSR2);
}

void wait_for_parents()
{
 while(signal_flag == FALSE)
  sigsuspend(&sig_null);
 signal_flag = FALSE;
 if(sigprocmask(SIG_SETMASK, &sig_m2, NULL) < 0)
  exit(0);
}

void message_for_child(pid_t pid)
{
 kill(pid, SIGUSR1);
}

void wait_for_child(void)
{
 while(signal_flag == FALSE)
  sigsuspend(&sig_null);
 signal_flag = FALSE;
 if(sigprocmask(SIG_SETMASK, &sig_m2, NULL) < 0)
   exit(0);
}


int main()
{
 pid_t pid;
 char x,y;
 start_signalmenge();

 switch( pid = fork())
  {
   case -1 : fprintf(stderr, "Fehler bei fork()\n");
             exit(0);
   case  0 : /*...im Kindprozess...*/
              for(x=2;x<=10;x+=2)
              {
               wait_for_parents();
               write(STDOUT_FILENO, "ping-",strlen("ping-"));
               message_for_parents(getppid());
              }

             exit(0);
   default : /*...im Elternprozess....*/
              for(y=1;y<=9;y+=2)
              {
               write(STDOUT_FILENO, "pong-", strlen("pong-"));
               message_for_child(pid);
               wait_for_child();
              }

   }
 printf("\n\n");
 return 0;
} char x,y;
 start_signalmenge();

 switch( pid = fork())
  {
   case -1 : fprintf(stderr, "Fehler bei fork()\n");
             exit(0);
   case  0 : /*...im Kindprozess...*/
              for(x=2;x<=10;x+=2)
              {
               wait_for_parents();
               write(STDOUT_FILENO, "ping-",strlen("ping-"));
               message_for_parents(getppid());
              }

             exit(0);
   default : /*...im Elternprozess....*/
              for(y=1;y<=9;y+=2)
              {
               write(STDOUT_FILENO, "pong-", strlen("pong-"));
               message_for_child(pid);
               wait_for_child();
              }

   }
 printf("\n\n");
 return 0;
}

Ich habe in diesem Programmbeispiel auf Fehlerausgaben verzichtet. Sollten Sie also wirklich vor haben Signale zur Kommunikation zwischen zwei Prozessen zu verwenden, so sollten Sie dies als letzte Alternative verwenden. Denn sollten zwei Signale in zu kurzen Zeitabständen eintreffen, während der Signalhandler aktiv ist, kann es passieren das einige Signale dabei verloren gehen.

Weiter mit 5.1. Grundlagen - Was sind IPC's und wofür werden sie benötigt