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 ...
- die effektive Benutzererkennung ist 0 (Beispiel root)
- effektive oder reale Benutzererkennung ist gleich wie die des Zielprozesses
Im Fehlerfall gibt kill -1 zurück, ansonsten 0.
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.
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 ...
- Erfragen des aktuellen Signalstatutes war nicht so ohne weiteres möglich
- Konflikte zwischen 2 gleichen Signalen im selben Programm. Beispielsweise SIGINT. Ein Signalhandler soll diese Funktion ignorieren und ein anderer eine bestimmte Funktion ausführen. Dies können durcheinandergeraten.
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 ...
- sigprocmask(wie, NULL, alt_sig_m);
Hiermit wird die, im gerade laufenden Prozeß, Signalmenge in die Adresse alt_sig_m geschrieben. 'wie' hat in diesem Fall keinen Effekt.
- sigprocmask(wie, sig_m, NULL);
Signalmaske wird geändert. Zu 'wie' kommen wir gleich.
- sigprocmask(wie, sig_m, alt_sig_m);
Zuerst wird die aktuelle im laufenden Prozeß verwendete Signalmaske in alt_sig_m geschrieben oder man könnte auch sagen gesichert. Anschließend wird die Signalmenge sig_m gesetzt. Beispielsweise ...
sigprocmask(SIG_BLOCK, &neue_signal_maske, &backup_signal_maske);
/*...viele Zeilen Code später wollen
wir die alte Signalmaske wiederherstellen....*/
sigprocmask(SIG_SETMASK, &backup_signal_maske, NULL);
Folgende drei wie Konstanten stehen in dabei zur Verfügung ...
- SIG_BLOCK
Signalmenge bekommt alle in sig_m stehenden Signale hinzugefügt.
- SIG_UNBLOCK
Die in sig_m stehenden Signale werden alle entfernt.
- SIG_SETMASK
Eine neue Signalmaske wird komplett neu erstellt mit den Signale die sich in sig_m befinden.
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.
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 ...
Der Kindprozess wird in einem Wartezustand versetzt und der Elternprozess schreibt etwas auf dem Bildschirm.
Der Elternprozess schickt dem Kindprozess ein Signal mittels kill() und der Elternprozess wird in einem Wartezustand versetzt.
Der Kindprozess ist dran mit dem Schreiben auf dem Bildschirm.
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.
|