In dieser und einer Reihe weiterer Folgen werden wir uns dem zentralen Problem der Kommunikation zwischen Prozessen zuwenden, die für eine Vielzahl von Problemen der Programmierung von großer Bedeutung ist und ja gerade in der heutigen Szenerie, die Client-Server Verarbeitung propagiert, von besonderem Interesse ist. Da die Kommunikation zwischen Prozessen unter Linux und anderen Unix-Systemen sehr vielfältig ist, impliziert dies notwendigerweise eine umfangreiche Menge von Systemaufrufen, die in diesen Bereichen aktiv sind. Diese reichen von klassischer Kommunikation über Files und Pipes bis hin zu fortschrittlicheren Aspekten des sogenannten IPC-Moduls und rechnerübergreifender Verständigung. Daher ist es erforderlich, diese Systemcalls in einer Vielzahl von Folgen vorzustellen; wir beschäftigen uns hier zunächst mit den Signalen.
Signale sind asynchrone Ereignisse, die zu jedem beliebigen Zeitpunkt an einen Prozeß gesandt werden können, wobei der Sender ein anderer Prozeß ist als auch hardwareseitig über den Kernel ein Signal produziert werden kann, so zum Beispiel bei fehlerhaften Gleitkommaoperationen. Der Empfänger eines Signals hat mehrere Möglichkeiten
Der Informationswert eines Signals ist relativ gering, da mit ihm lediglich ein numerischer Wert verknüpft ist, der in einem gewissen Umfang interpretiert werden kann, jedoch nicht für den Austausch einer größeren Menge von Informationen nutzbar ist. Warum man dann überhaupt Signale benutzt, ist eine andere Frage, ich selber schätze ihre Relevanz eher gering ein - es sei denn, man betrachtet eine Reihe von Situationen, die jedoch relativ selten sind.
Wir werden uns bei der Betrachtung der Systemcalls rund um die Signale den gängigen zuwenden, da die neueren meiner Auffassung nach nichts bringen (ich lasse mich hier gerne belehren). Zunächst sei der Systemcall signal vorgestellt.
| Protyp signal |
#include <sys/types.h> #include <signal.h> void (*signal(int sig,void (*fp)(int)))(int) |
Der erste Paramter sid ist der numerische Wert des Signals, die möglichen Signale können der Datei <asm/signal.h> entnommen werden. Der zweite Paramter ist ein Zeiger auf eine Funktion, die aufgerufen wird, wenn der Empfänger des Signals bewußt reagieren will, ansonsten können SIG_DFL für Defaultaktionnen und SIG_IGN für das Ignorieren des Signals (ist bei SIGKILL nicht möglich) übergeben werden. Die aufgerufende Funktion erhält als Parameter den numerischen Wert des Signals.
Was macht man nun mit solchen Dingen? Ich wähle als erstes ein Beispiel, in dem wir die Bereitschaft setzen, auf ein Signal mit einer Funktion zu reagieren. Das Beispiel skizziert die Situation eines Servers, der ja aufgrund seiner Tätigkeit üblichweise in einer Endlosschleife läuft. Sollte er terminieren, wie auch immer, kann es jedoch erforderlich sein, eine Reihe von Aufräumtätigkeiten vorzunehmen, weil ansonsten eine Situation hinterlassen wird, die für den weiteren Ablauf unerträglich ist.
| Listing 1: clean_up.cpp |
#include <sys/types.h>
#include <signal.h>
void catcher(int);
int main(int argc,char **argv) {
signal(SIGINT, catcher);
while(1) {
//Verarbeitung
}
//never reached
return 1;
}
void catcher(int) {
//clean up
exit (1);
}
|
Clean_up setzt die Bereitschaft, mit der Funktion catcher auf das Signal SIGINT (Control-C von Tastatur) zu reagieren und geht anschließend in die Endlosschleife der Verarbeitung. Trifft das Signal SIGINT ein, wird die Funktion catcher aufgerufen, die die Aufräumarbeiten ausführt und anschließend terminiert der Prozeß. Eine mögliche Reaktion wäre auch über das Ignorieren möglich, was folgende Optik hat:
signal(SIGINT,SIG_IGN);
wobei wie bereits erwähnt natürlich nicht alle Signale ignoriert werden können, ein SIGKILL von root führt definitv zum Ende eines Prozesses.
Signale werden in der Struktur task_struct in der Komponente signal gespeichert, pro Signal ein Bit, damit sind 32 Signale möglich, die Manipulation der Rücksprungadresse erfolgt vom Kernel , der Instruction-Pointer wird auf die Adresse der ersten ausführbaren Instruktion der aufzurufenden Routine gesetzt, die Manipulationen am Stack erfolgen nach der üblichen Behandlung eines Funktionsaufrufes; dies ergibt die eines eigenen Aufrufs.
Bislang haben wir die Passivseite der Signalbehandlung betrachtet, wir wenden uns nun der Aktivseite zu, dem Versenden von Signalen via kill.
| Prototyp kill |
#incluse <sys/types.h> #include <signal.h> int kill(int pid, int sig); |
Mit dem Systemcall kill (der Name hat nur wenig mit der Aufgabe gemein) kann prinzipiell jeder Prozeß ein Signal an einen anderen versenden, jedoch können nicht alle Signale von allen Prozessen an alle anderen gesandt werden, so kann ein normaler User via Prozeß natürlich das Signal SIGKILL nur an einen Prozeß versenden, der auch ihm gehört, alles andere wäre töricht.
Der Systemcall kill benötigt lediglich die PID des Empfängers und die Nummer des Signals, das geschickt werden soll. Wir betrachten uns ein kleines Beispiel, in der die Signalbehandlung zur minimalen Prozeßkommunikation herangezogen wird.
init ist der Prozeß, der für die Gestaltung des Ablaufs nach dem Bootvorgang von zentraler Bedeutung ist, da er ja das System in ein bestimmtes Run-Level bringt und dies auch zur Laufzeit ändern kann, dies tat und tut man mit telinit.
Da nun also init und telinit miteinander kommunizieren müssen, benutzt telinit ein Signal, um init zu verständigen, wobei das Signal gleichzeitig das neue Run-Level implizieren kann. Wir betrachten uns als nächtes eine einfache Variante von telinit.
| Listing 2: telinit.cpp |
#include <sys/types.h>
#include <signal.h>
extern "C" {
int atoi(const char *);
}
int main(int argc, char **argv) {
kill(1, atoi(*++argv);
}
|
Die Prozeßnummer von init ist bekannt, es ist die Nummer eins, also versenden wir das Signal, das das neue Run-Level impliziert. So kann man sich auch verständigen! Interessant sind im Zusammenhang mit der Signalbehandlung auch die beiden Systemcalls alarm und pause, deren Prototypen ich zunächst vorstelle.
| Prototypen alarm und pause |
#include <sys/types.h> #include <unistd.h> int alarm(int sec); int pause(); |
Mit alarm können wir einen Wecker stellen, der nach der übergebenen Zeitspanne an den Prozeß das Signal SIGALRM sendet, pause versetzt einen Prozeß in den Schlummerzustand. Ich konstruiere aus diesen beiden Systemaufrufen im Zusammenhang mit signal das Kommando sleep, das im Rahmen der Shell-Programmierung ja hinlänglich bekannt sein sollte.
| Listing 3: sleep.cpp |
#include <sys/types.h>
#include <signal.h>
void foo(int);
extern "C" {
int atoi(const char *);
};
int main(int argc,char **argv) {
signal(SIGALRM, foo);
alarm(atoi(*++argv));
pause();
}
void foo() {}
|
Mit signal setze ich die Bereitschaft, auf das Alarm-Signal zu ragieren, mit alarm stelle ich den Wecker auf die übergebene Anzahl der zu schlafenden Sekunden und mit pause versetze ich mich in den Schlummerzustand. Kommt das Signal, wird die Funktion foo aufgerufen. foo wird lediglich benutzt, um eine eigene Reaktion zu erzwingen und keine Default-Aktionen zuzulassen und um pause definitiv zu unterbrechen.
Damit sei der Behandlung dieser Schmalspurversion der Kommuniktion genüge getan. Die elaborierteren Varianten der Signalbehandlung seien noch kurz vorgestellt. Zunächst die Prototypen.
| Weitere Prototypen |
#include <sys/types.h> #include <signal.h> int sigaction(int signum, const struct sigaction *new, struct sigaction *old); int sgetmask(); int ssetmask(int newmask); int sigpending(sigset_t *buf); int sigsuspended(const sigset_t *mask); int sigprocmask(int how, const sigset_t *set, sigset_t *old_set); |
Die Anwendung dieser letzlich posixkonformen Funktionen seien der neueren Literatur entnommen. Soviel für diesmal! In der nächsten Folge widmen wir uns dem Versenden von Nachrichten, den Messages.
| Infos |
|
[1] Linux-Kernel-Programmierung, Addison-Wesley, 1997 [2] Rochkind, Unix-Programmierung für Fortgeschrittene, Hanser, 1985 [3] Bentson,R.,Inside Linux,Seattle:SSC 1996 [4] Brown,Chris,UNIX distributed programming,Prentice Hall [5] Robbins, K.A:,Robbins,Steven, Practical Unix Programming,Prentice Hall |
| Der Autor |
|
Wolfgang Hetzler arbeitet seit 1985 als Dozent an einem privaten Institut für Fort- und Ausbildung im EDV-Bereich und als Lehrbeauftragter an der FH-Frankfurt im Bereich Ingenieur-Informatik. Linux ist ihm seit der Version 0.9xx bekannt und eine Quelle ständiger Auseinandersetzung. Zu erreichen ist er unter het@het.gg.eunet.de. |
Copyright © 1998 Linux-Magazin Verlag