![]() |
Internet-TechnologieProf. Jürgen Plate |
UNIX und Linux stellen, wie viele andere Betriebssysteme auch, eine spezielle Funktion zur Verfügung, mit deren Hilfe man die PID eines Prozesses abfragen kann. Eine zweite Funktion erlaubt es einem Kindprozess, die PID seines Elternprozesses zu ermitteln. Beide Funktion sind in der Header-Datei unistd.h definiert:
pid_t getpid(void); pid_t getppid(void);
Die erste Funktion, getpid(), liefert die PID des Prozesses zurück, der getpid() aufgerufen hat. Die zweite Funktion, getppid(), liefert die Eltern-PID des Prozesses. Der Rückgabewert ist jeweils vom Typ pid_t, der in einer der in stdlib.h eingeschlossenen Header-Dateien als int definiert ist.
Beispiel: Die ID des aktuellen Prozesses und seines Elternprozesses ermitteln.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = getpid();
printf ("Meine PID = %d\n", pid) ;
pid = getppid();
printf ("Meine Eltern-PID = %d\n", pid) ;
return 0;
}
Das Programm aus diesem Listing definiert eine Variable vom Typ pid_t. Die Werte, die von den Funktionen getpid() und getppid() zurückgegeben werden, werden dann ausgegeben. Wenn Sie das Programm mehrmals im gleichen Konsolenfenster ausführen, erhalten Sie jedes Mal eine andere Prozess-ID, während die ID für den Elternprozess immer die gleiche bleibt.
pid_t fork(void);Tritt kein Fehler auf, erzeugt fork() einen neuen Prozess, der mit dem aufrufenden Prozess identisch ist. Sowohl der alte als auch der neue Prozess werden danach - ab der Anweisung hinter dem fork()-Aufruf - parallel ausgeführt. Obwohl beide Prozesse das gleiche Programm ausführen, verfügen sie über eigene Kopien aller Daten und Variablen. Eine dieser Variablen ist der Rückgabewert von fork().
switch (pid = fork())
{
case -1: printf("Schief gegangen!\n"); break;
case 0 : printf("Kindprozess!\n"); break;
default : printf("Prozeß %d wurde erzeugt!\n",i); break;
}
Da der Elternprozess eine vollständige Kopie seiner Daten für den Sohn
erzeugt, besteht im Anschluss keine Möglichkeit, daß Vater und Sohn
über gemeinsame Variablen kommunizieren. Jeder hat von jeder Variablen
ja sein eigenes Exemplar.
Beispiel: Mit Hilfe von fork() einen neuen Prozess erzeugen.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
int x = 22;
pid = fork();
if (pid < 0)
{
printf("Fehler: fork()-Rsultat %d.\n", pid);
exit(1);
}
if (pid == 0)
{
printf("Kind: PID = %u. Eltern-PID = %u\n",
getpid(), getppid());
printf("Kind: xalt = %d\n", x);
x = 11;
printf("Kind: xneu = %d\n", x);
sleep(2);
puts ("Kind: Beendet.");
exit(42);
}
else
{
printf("Eltern: PID = %u. Kind-PID = %u\n",
getpid(), pid);
puts("Eltern: 60 Sekunden Pause.");
sleep(60);
puts("Eltern: wieder wach.");
printf("Eltern: x = %d\n", x);
}
return 0;
}
Ausgabe:
Eltern: PID = 1535. Kind-PID = 1536 Eltern: 60 Sekunden Pause. Kind: PID = 1536. Eltern-PID = 1535 Kind: xalt = 22 Kind: xneu = 11 Kind: Beendet. Eltern: wieder wach. Eltern: x = 22
Anhand des Rückgabewertes von fork() wird festgestellt, ob ein Fehler aufgetreten ist. Sind keine Fehler aufgetreten, werden zwei Prozesse ausgeführt. Im Kindprozess ist der Wert von pid 0, im Elternprozess enthält die Variable eine Prozess-ID im Bereich zwischen 1 und 32767. Die if-Anweisung wird von beiden Prozessen ausgewertet. Der Kindprozess führt danach den Block nach dem if aus, der Elternprozess den Block nach dem else.
An der Programmausgabe können Sie erkennen, daß der Elternprozess nach dem fork()-Aufruf eine Meldung ausgibt und sich dann schlafen legt. Parallel wird der Kindprozess weiter ausgeführt. Als erstes gibt er seine eigene PID und die seines Elternprozesses aus. Als Nächstes gibt der Kindprozess den Wert der Variablen x aus, ändert den Wert und gibt ihn erneut aus. Schließlich geht auch er für 2 Sekunden Pause. Da der Elternprozess 60 Sekunden schläft, wacht der Kindprozess vor seinem Eltern auf und gibt eine Meldung aus. Dann beendet er sich und gibt den Wert 42 zurück. 60 Sekunden später erwacht der Elternprozess von seinem eigenen sleep()-Aufruf, gibt den Wert der Variablen x aus und beendet sich ebenfalls.
Der Parameter status dient dazu, dem Vaterprozess beispielsweise Informationen über die ordnungsgemäße Abwicklung des Sohnes zukommen zu lassen. Der Vater kann den Status mit der Systemfunktion wait() abfragen. Wenn ein Anwenderprogramm keine der Exit-Funktionen explizit aufruft, erfolgt dies implizit nach dem Verlassen der main()-Routine.
... jpl 1714 0.0 0.0 0 0 pts/5 Z Jan27 0:00 [kind <defunct>] ...Der Kind-Prozess wird als erloschen (defunct) gemeldet. In der STAT-Spalte dieses Prozesses steht ein Z, was bedeutet, daß es sich um einen so genannten "Zombie"-Prozess handelt.
Prozesse verwenden zum Beenden die return-Anweisung oder rufen die Funktion exit() mit einem Wert auf, der an das Betriebssystem zurückgeliefert wird. Das Betriebssystem lässt den Prozess so lange in seiner Prozesstabelle eingetragen, bis entweder der Elternprozess des Prozesses den zurückgelieferten Wert liest oder der Elternprozess selbst beendet wird. Ein Zombie-Prozess ist in diesem Sinne ein Prozess, der zwar beendet wurde, dessen Elternprozess den Exit-Wert des Kindes aber noch nicht gelesen hat. Erst wenn der Elternprozess beendet wird, wird auch der Zombie-Prozess aus der Prozesstabelle des Betriebssystems entfernt.
Es gibt mehrere Wege, die Entstehung von Zombie-Prozessen zu verhindern. Am häufigsten wird die Systemfunktion wait() verwendet (Header-Datei sys/wait.h):
pid_t wait(int *status);Wenn die Funktion wait() aufgerufen wird, hält sie die Ausführung des Elternprozesses so lange an, bis ein Kindprozess beendet wird. Beim Aufruf von "wait" gibt es drei mögliche Ergebnisse:
Wenn Sie an dem Rückgabewert des Kindprozesses nicht interessiert sind, übergeben Sie wait() den Wert NULL.
Beispiel: Mit wait() Zombie-Prozesse verhindern.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int status;
pid = fork();
if (pid < 0)
{
printf("Fehler: fork()-Rsultat %d.\n", pid);
exit(1);
}
if (pid == 0)
{
printf("Kind: PID = %u. Eltern-PID = %u\n",
getpid(), getppid());
sleep(1);
puts ("Kind: Beendet.");
exit(42);
}
else
{
printf("Eltern: PID = %u. Kind-PID = %u\n",
getpid(), pid);
puts("Eltern: 10 Sekunden Pause.");
sleep(10);
puts("Eltern: wieder wach.")
pid = wait(&status);
printf("Eltern: Kind mit PID %u ", pid);
if (WIFEXITED(status) != 0)
printf("wurde mit Status %d beendet\n",WEXITSTATUS(status));
else
printf("wurde mit Fehler beendet.\n");
}
return 0;
}
Dieses Listing entspricht weitgehend dem vorhergehenden Programm. Der Hauptunterschied liegt darin, daß der Elternprozess nach dem Erwachen die Funktion wait() aufruft. Da der Kindprozess schon vorher beendet wurde, kehrt wait() sofort nach dem Aufruf zurück und setzt die Variable pid auf die Prozess-ID des beendeten Kindprozesses. Des Weiteren kopiert die Funktion den Exit-Wert des Prozesses in die Variable status, deren Adresse der Funktion als Argument übergeben wurde. Der Elternprozess gibt die Prozess-ID des Kindes aus und verwendet die Makros, WIFEXITED() and WEXITSTATUS(), die in sys/wait.h definiert sind, um den Rückgabestatus des Kindprozesses abzufragen und ebenfalls auszugeben. Auf der Manpage zur wait()-Funktion können Sie nachlesen, daß diese Makros dafür sorgen, daß nur 8-Bit-Werte (1 bis 255) als Exit-Status zurückgeliefert werden.
Die wait()-Funktion ist offensichtlich recht nützlich, wenn man weiß, daß der Kindprozess bereits beendet wurde. Sollte dies nicht der Fall sein, hält die wait()-Funktion den Elternprozess so lange an, bis der Kindprozess beendet wird. Wenn dieses Verhalten nicht gewünscht, kann man die waitpid()-Funktion verwenden, die in der Header-Datei sys/wait.h definiert ist:
pid_t waitpid(pid_t pid, int *status, int options);Mit waitpid() können Sie auf einen bestimmten Prozess (spezifiziert durch seine Prozess-ID) oder einen beliebigen Kindprozess (falls für pid der Wert -1 übergeben wird) warten. Der Exit-Status des Kindprozesses wird im zweiten Argument zurückgeliefert. Dem letzten Parameter, options, kann man eine der Konstanten WNOHANG, WUNTRACED oder 0 (waitpid() verhält sich dann wie wait()) übergeben. Die erste dieser Konstanten ist die interessanteste, da sie dafür sorgt, daß waitpid() sofort mit einem Wert von 0 - einer ungültigen Prozess-ID - zurückkehrt, wenn kein Kindprozess beendet wurde. Der Elternprozess kann dann mit der Ausführung fortfahren und waitpid() zu einem späteren Zeitpunkt wieder aufrufen.
Beispiel: Mit waitpid() Zombie-Prozesse verhindern.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int status;
pid = fork();
if (pid < 0)
{
printf("Fehler: fork()-Rsultat %d.\n", pid);
exit(1);
}
if (pid == 0)
{
printf("Kind: PID = %u. Eltern-PID = %u\n",
getpid(), getppid());
sleep(10);
puts ("Kind: Beendet.");
exit(66);
}
else
{
printf("Eltern: PID = %u. Kind-PID = %u\n",
getpid(), pid);
while ((pid = waitpid (-1, &status, WNOHANG)) == 0)
{
printf("Eltern: Kein Kind beendet.");
puts(" 1 Sekunde Pause.");
sleep(1);
}
printf("Eltern: Kind mit PID %u ", pid);
if (WIFEXITED(status) != 0)
printf("wurde mit Status %d beendet\n", WEXITSTATUS(status));
else
printf("wurde mit Fehler beendet.\n");
}
return 0;
}
int execl( const char *path, const char *arg, ...);Diese Funktion kehrt nur dann zurück, wenn ein Fehler auftritt. Andernfalls wird der aufrufende Prozess vollständig durch den neuen Prozess ersetzt. Den Programmnamen des Prozesses, der den aufrufenden Prozess ersetzen soll, übergibt man im Argument zu path, etwaige Kommandozeile-Parameter werden danach übergeben. Im Unterschied zu Funktionen wie printf() ist execl() darauf angewiesen, daß man als letztes Argument einen NULL-Zeiger übergibt, der das Ende der Argumentenliste anzeigt.
Der zweite an execl() übergebene Parameter ist nicht der erste Kommandozeilen-Parameter, der an das aufzurufende Programm (spezifiziert in path) übergeben wird. Vielmehr ist er der Name, unter dem der neue Prozess in der vom ps-Befehl erzeugten Prozessliste aufgeführt wird. Der erste Parameter, der an das (in path spezifizierte) Programm übergeben wird, ist also tatsächlich der dritte Parameter von execl(). Wenn Sie beispielsweise das Programm /bin/ls mit dem Parameter -lisa aufrufen wollen und möchten, daß das Programm in der Prozessliste unter dem Namen "verz" aufgerufen wird, würden Sie execl() wie folgt aufrufen:
execl("/bin/ls", "verz", "-lisa", NULL);
Dieser Aufruf würde den aktuellen Prozess durch einen Prozess ersetzen, der dem
Aufruf von /bin/ls -lisa von der Befehlszeile entspricht.
Beispiel: Mit execl() einen Prozess durch einen anderen ersetzen.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main(void)
{
pid_t pid ;
pid = getpid();
printf ("Meine PID = %u\n", pid);
execl ("/bin/ps", "ps-proggie", "u", NULL);
puts("Ein Fehler ist aufgetreten.");
return 0;
}
Beachten Sie, daß der ursprüngliche Prozess die gleiche Prozess-ID trägt wie später der neue Prozess, der ihn ersetzte.
execl() ist nicht die einzige Funktion dieser Art, es gibt eine ganze Familie mit leicht unterschiedlicher Arbeitsweise.
int execve (char *filename,char *argv[], char *envp[]);. Der Parameter filename bezeichnet dann entweder ein ausführbares Programm oder ein Skript, das von einem Interpreter ausgeführt wird. argv ist ein Feld von Zeichenketten, das die Aufrufparameter enthält, mit denen das Programm versorgt werden soll. Dabei muß argv[0] der Name des Programmes selbst sein. envp ist ebenfalls ein Feld von Zeichenketten und enthält die Umgebungsvariablen (mit Inhalt) in der Form "NAME=inhalt", die dem Programm übergeben werden sollen. Beide Felder müssen mit einem NULL-Zeiger abgeschlossen sein.
Bei Erfolg kehrt die Funktion execve() wie execl() nicht zurück. Stattdessen wird das aufrufende Programm durch das aufgerufenen Programmes ersetzt (überschrieben) und dieses gestartet. Das gestartete Programm erhält die gleiche Prozeßnummer wie der aufrufende Prozeß und erbt in der Regel alle "offenen" Dateideskriptoren Im Fehlerfall liefert die Funktion den Wert -1 zurück. Beispiel:
char *parameter[] = { "ls", "lisa", NULL };
char *umgebung[] = { "PATH=/bin:/usr/bin", "HOME=/root", NULL };
execve("/bin/ls",parameter,umgebung);
printf("Ooops! ls konnte nicht gestartet werden\n");
| Name | Wert | Funktion |
|---|---|---|
| SIGHUP | 1 | Logoff |
| SIGINT | 2 | Benutzer-Interrupt (ausgelöst durch [Strg]+[C]) |
| SIGQUIT | 3 | Benutzeraufforderung zum Beenden (ausgelöst durch [Strg)+[\]) |
| SIGFPE | 8 | Fließkommafehler, beispielsweise Null-Division |
| SIGKILL | 9 | Prozess killen |
| SIGUSR1 | 10 | Benutzerdefiniertes Signal |
| SIGSEGV | 11 | Prozess hat versucht, auf Speicher zuzugreifen, der ihm nicht zugewiesen war |
| SIGUSR2 | 12 | Weiteres benutzerdefiniertes Signal |
| SIGALRM | 14 | Timer (Zeitgeber), der mit der Funktion alarm() gesetzt wurde, ist abgelaufen |
| SIGTERM | 15 | Aufforderung zum Beenden |
| SIGCHLD | 17 | Kindprozess wird aufgefordert, sich zu beenden |
| SIGCONT | 18 | Nach einem SIGSTOP- oder SIGTSTP-Signal fortfahren |
| SIGSTOP | 19 | Den Prozess anhalten |
| SIGTSTP | 20 | Prozess suspendiert, ausgelöst durch [Strg)+[Z]. |
Abgesehen von SIGSTOP und SIGKILL kann man das Standardverhalten jedes Signals durch Installation einer Signal-Bearbeitungsroutine anpassen. Eine Signal- Bearbeitungsroutine ist eine Funktion, die vom Programmierer implementiert wurde und die jedes Mal aufgerufen wird, wenn der Prozess ein entsprechendes Signal empfängt. Abgesehen von SIGSTOP und SIGKILL können Sie für jedes Signal aus eine eigene Signal-Bearbeitungsroutine einrichten. Eine Funktion, die als Signal-Bearbeitungsroutine fungieren soll, muss einen einzigen Parameter vom Typ int und einen void-Rückgabetyp definieren. Wenn ein Prozess ein Signal empfängt, wird die Signal-Bearbeitungsroutine mit der Kennnummer des Signals als Argument aufgerufen.
Um Signale abfangen und mit einer geeigneten Signal-Bearbeitungsroutine bearbeiten zu können, muss der Programmierer dem Betriebssystem mitteilen, daß es bei jedem Auftreten des betreffenden Signals für das Programm die zugehörige Signal- Bearbeitungsroutine aufrufen soll. Zwei Funktionen gibt es, mit denen man unter Unix eine Signal-Bearbeitungsroutine verändern oder untersuchen kann: signal() und sigaction(), die beide in der Header-Datei signal.h definiert sind. Die zweite Funktion, sigaction(), ist die aktuellere und wird auch häufiger eingesetzt. Sie ist wie folgt definiert:
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
Im Erfolgsfall liefert die Funktion 0 zurück, im Fehlerfall -1. Der erste Parameter von sigaction() ist die Nummer des Signals, dessen Verhalten Sie verändern oder untersuchen wollen. Man übergibt dem Parameter aber nicht die tatsächliche Signal-Nummer, sondern die zugehörige symbolische Konstante - also beispielsweise SIGINT statt der Zahl 2. Der zweite und der dritte Parameter sind Zeiger auf eine sigaction-Struktur. Diese Struktur ist in signal.h definiert:
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
Indem Sie dem zweiten Parameter der sigaction()-Funktion einen Zeiger auf eine korrekt eingerichtete sigaction-Struktur übergeben, können Sie das Verhalten für das zugehörige Signal verändern. Indem Sie einen Zeiger auf eine solche Struktur als dritten Parameter übergeben, fordern Sie die sigaction()-Funktion auf, die Daten, die das aktuelle Verhalten zu dem Signal bestimmen, in die übergebene sigaction-Struktur zu kopieren. Beiden Parametern kann man auch NULL- Zeiger übergeben.
Es ist also möglich, das aktuelle Verhalten zu ändern, sowie das aktuelle Verhalten zu untersuchen, ohne es zu ändern, das aktuelle Verhalten zu untersuchen und vor dem Ändern abzuspeichern, so daß es später wieder hergestellt werden kann.
int sigemptyset(sigset_t *set);Das letzte Element der Struktur, sa_restorer, wird heute nicht mehr verwendet.
Beispiel: Ein einfaches Beispiel zur Behandlung von Signalen.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
static int BEENDEN = 0;
void sig_bearbeiter(int sig)
{
printf("Signal %d empfangen. Programm wird beendet.\n", sig);
BEENDEN = 1;
}
int main(void)
{
struct sigaction sig_struct;
sig_struct.sa_handler = sig_bearbeiter;
sigemptyset(&sig_struct.sa_mask);
sig_struct.sa_flags = 0;
if (sigaction(SIGINT,&sig_struct,NULL) != 0)
{
puts ("Fehler beim Aufruf von sigaction!") ;
exit (1);
}
puts("Programm gestartet, beenden mit [Strg]+[C].");
while (BEENDEN == 0)
{
puts("Programm läuft.");
sleep(1);
}
puts("Erstmal aufraeumen.");
sleeep(5);
puts("Fertig!");
return 0;
}
Wurde die Signal-Bearbeitungsroutine korrekt eingerichtet, gibt das Programm in
eine Meldung aus und tritt in die Schleife des Hauptprogramms ein. Solange die
Variable BEENDEN gleich 0 ist, gibt die
while-Schleife die Meldung "Programm läuft." aus und
legt sich jeweils für eine Sekunde schlafen.
Wenn die Signal-Bearbeitungsroutine sig_bearbeiter() aufgerufen wird, gibt sie die Meldung "Signal 2 empfangen. Programm wird beendet." auf den Bildschirm aus und setzt danach den Wert der statischen Variablen BEENDEN auf 1. Nur das führt zum Beeenden und nicht das Betätigen von [Ctrl]+[C]. Da Programm könnte auch einfach weiterlaufen und die Benuterunterbrechung ignorieren. Hier die Ausgabe eines Beispiel-Laufs:
Beenden mit [Strg]+[C]. Programm läuft. Programm läuft. Programm läuft. Signal 2 empfangen. Programm wird beendet. Erstmal aufraeumen. Fertig!
In der Folge werden weitere Betriebssystemfunktionen beschrieben, die im Zusammenhang mit der Steuerung von Prozessen von Bedeutung sind.
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
void tick (int dummy) // nur Wecker neu aufziehen
{ alarm(1); }
void beenden(int signal_nummer)
{ // Signal-Bearbeitungsroutine
char c;
if (signal_nummer == SIGINT)
{
printf("Prozeß wirklich beenden ?");
c = getchar();
if (c == 'j' || c == 'J') exit(1);
else return;
}
else
{
printf("unerwartetes Signal %d\n");
exit(1);
}
}
int main(void)
{
signal(SIGINT,beenden);
signal(SIGALRM,tick);
alarm(1); // Wecker aufziehen
for (;;)
{
pause(); // auf Signal warten
putchar('.');
};
}
Das Hauptprogramm plant für das Signal "SIGINT" (z.B. Drücken
von Ctrl-C) die Bearbeitungsroutine "beenden" und für das Signal
"SIGALRM" (Timer-Signal) die Routine "tick" ein. Im Anschluss daran wird
der Timer mit "alarm(1)" auf 1 Sekunde gesetzt.
Möchte man wissen, welche Signalbearbeitung vor dem Aufruf von "signal" eingestellt ist, dann muss man den Rückgabewert von "signal" auswerten. Dieser repräsentiert die "alte" Signalreaktion. Damit kann beispielsweise eine Signalbearbeitung vorübergehend modifiziert werden, um sie im Anschluss wieder auf den vorherigen Mechanismus zurückzusetzen. Mit void (*old)(int) = signal(SIGINT,beenden); holt man die "alte" Signalreaktion und mit signal(SIGINT,old); wird sie wieder eingesetzt.
Die folgende Tabelle fasst die Funktionen für Signale zusammen:
| exit | Beendet den Prozess. Prototyp: void exit (int status); Parameter:status: Status der zurückgegeben wird (0 = OK) |
| fork | Starten einen neuen Prozess. Prototyp: int fork(void); Rückgabewert: 0 an Kindprozess, Prozess-ID (PID) des Kindes an Elternprozess, -1 bei Fehler. |
| getpid | Liefert die Prozess-ID (PID) des aufrufenden
Prozesses bzw. -1 bei einem Fehler. Prototyp: int getpid(void); |
| getppid | Liefert die Prozessidentifikationsnummer des Vaterprozesses
(PPID) bzw. -1 bei einem Fehler. Prototyp: int getppid(void); |
| kill | Das Signal sig wird durch diese Funktion
an den Prozess mit der Prozessidentifikationsnummer pid geschickt. Includes: #include <signal.h> Prototyp: int kill(int pid, int sig); Parameter:pid des Empfänger-Prozesses. |
| pause | Der Prozess wird angehalten und wartet auf ein Signal. Prototyp: int pause(void); |
| signal | Diese Funktion bindet das Signal sig an einen Signal Handler. Includes: #include <signal.h> Prototyp: void signal(int sig, int *sighand); Parameter: sig: Signal, das an den Signal Handler gebunden werden soll. *sighand: Signal Handler der ausgeführt werden soll. |
| sleep | Der aufrufende Prozess blockiert für eine bestimmte Zeit. Prototyp: unsigned sleep(int sec); Parameter: int sec: Dauer in Sekunden. |
| wait | Es wird auf die Beendigung eines Sohnprozesses gewartet. Haben
einer oder mehrere Sohnprozesse bereits terminiert, so kehrt
der Aufruf sogleich zurück. Ist dies nicht der Fall wird
auf die Beendigung des nächsten Sohnprozesses gewartet. Prototyp: int wait(int *statusp); Parameter: *statusp: Zeiger auf Variable, in der der Terminierungsstatus des Sohnes zurückgegeben wird. Benötigt man den Rückgabestatus nicht, kann 0 als Parameter benutzt werden. Rückgabewert: PID des terminierten Sohnes bzw. -1 wenn kein Sohnprozess existiert oder bereits terminierte Söhne durch frühere wait(void)-Aufrufe entgegengenommen wurden. |
| waitpid | Es wird auf die Beendigung eines bestimmten
Sohnprozesses gewartet. Prototyp: int waitpid(int pid, int *statusp, int optionen); Parameter: *statusp wie bei wait. Ist der Wert von pid > 0, handelt es sich um die PID des Prozesses, auf den gewartet werden soll. Bei pid == -1 wird auf die Beendigung eines beliebigen Sohnprozesses gewartet. Der Parameter optionen bestimmt wie und worauf gewartet werden soll. Er ist aber abhänig davon ob das System z.B. eine Job-Kontrolle unterstützt. |
Zum Schluss ein Reaktionstest in C.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <math.h>
#include <unistd.h>
clock_t start, ende, differenz, record=1000000;
/* Signal-Handler-Routinen */
void strgbackslash_faenger(int sig)
{
signal(SIGQUIT, SIG_IGN);
printf("......Die schnellste STRG-C Tastenfolge dauerte %7.3f\n",
record/(double)CLOCKS_PER_SEC);
exit(0);
}
void strgc_faenger(int sig)
{
/* Fuer die Dauer dieser Funktionsausführung muessen weitere */
/* SIGINT-Signale ignoriert werden. */
signal(SIGINT, SIG_IGN);
/* Gebrauchte Zeit berechnen und ausgeben */
ende = clock();
differenz = ende - start;
printf("Gebrauchte Zeit: %10.3f Sek\n", differenz/(double)CLOCKS_PER_SEC);
if (differenz < record)
{
record = differenz;
printf("...........Neuer Rekord %10.3f Sek\n", record/(double)CLOCKS_PER_SEC);
}
sleep(rand()%2+1);
printf("\nDruecke so schnell wie moeglich STRG-C.......\n");
start = clock();
/* Signal-Handler wieder fuer SIGINT installieren */
signal(SIGINT, SIG_IGN);
if (signal(SIGINT, strgc_faenger) == SIG_ERR)
{
printf("Fehler: SIGINT-Handler nicht installiert!\n");
exit(1);
}
}
int main(void)
{
/* Startwert für Pseude-Zufallszahlen erzeugen und */
/* Signal Handler installieren */
srand( time(NULL) );
if (signal(SIGQUIT, strgbackslash_faenger) == SIG_ERR)
{
printf("Fehler: SIGQUIT-Handler nicht installiert!\n");
exit(1);
}
signal(SIGINT, SIG_IGN);
sleep(rand()%2+1);
printf("Bitte merk Dir: Beenden mit STRG-\\\n");
printf("\nDruecke so schnell wie moeglich STRG-C.......\n");
if (signal(SIGINT, strgc_faenger) == SIG_ERR)
{
printf("Fehler: SIGINT-Handler nicht installiert!\n");
exit(1);
}
start = clock();
while (1);
return(0);
}
Das folgende Programmbeispiel erzeugt einen Semaphor, der auf 3 initialisiert wird, und 10 Sohnprozesse, die über den Semaphor synchronisiert werden. Die ersten drei Prozesse können sofort arbeiten. Alle anderen Prozesse müssen warten, bis ein anderer Prozess den Semaphor wieder inkrementiert.
Da bei verschiedenen UNIX-Varianten die Semaphor-Operationen unterschiedlich sind, läuft das Beispiel nur unter Linux.
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else
/* according to X/OPEN we have to define it ourselves */
union semun
{
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
unsigned short int *array; /* array for GETALL, SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
};
#endif
struct sembuf sem_p[1]; /* Strucktur fuer */
/* P-Operation auf Semaphor */
struct sembuf sem_v[1]; /* Strucktur fuer */
/* V-Operation auf Semaphor */
int main(void)
{
int prozess_pid[10]; /* Feld um die PID´s der */
/* Sohn-Prozesse zu speichern */
int anzahl; /* Anzahl der Sohn-Prozesse */
int semid; /* ID der Semaphorengruppe */
ushort initarray[1]; /* Initialisierungsfeld */
ushort outarray[1]; /* Ausgabefeld */
union semun para;
union semun para2; /* Variablen zum Arbeiten unter Linux */
para.array = initarray;
para2.array = outarray;
initarray[0] = 3;
semid = semget(IPC_PRIVATE,1,IPC_CREAT|0777);
/* Erzeugung einer Semaphorgruppe mit einem Semaphor */
semctl(semid,0,SETALL,para);
sem_p[0].sem_num = 0; /* Vorbereitung der P-Operation */
sem_p[0].sem_op = -1;
sem_p[0].sem_flg = 0;
sem_v[0].sem_num = 0; /* Vorbereitung der V-Operation */
sem_v[0].sem_op = 1;
sem_v[0].sem_flg = 0;
printf("\nZum Starten und Beenden bitte Eingabetaste druecken\n\n");
while (getchar() != '\n');
for (anzahl=0; anzahl<10; anzahl++) /* 10 Sohn-Prozesse */
{ /* werden erzeugt */
if((prozess_pid[anzahl] = fork()) == 0)
{
printf("Kunde %i betritt den Laden \n",anzahl);
semop(semid,sem_p,1); /* Semaphor dekrementieren */
printf("Kunde %i wird bedient \n",anzahl);
sleep(5);
printf("Kunde %i verlaesst den Laden\n",anzahl);
semop(semid,sem_v,1); /* Semaphor inkrementieren */
exit(0);
}
}
while(getchar() != '\n');
for (anzahl=0; anzahl<10; anzahl++) /* terminiere die restlichen */
kill(prozess_pid[anzahl],9); /* Sohn-Prozesse */
semctl(semid,0,IPC_RMID,para);
return 0;
}
Die Ausgabe des Programms könnte folgendermaßen aussehen:
Zum Starten und Beenden bitte Eingabetaste druecken Kunde 0 betritt den Laden Kunde 0 wird bedient Kunde 1 betritt den Laden Kunde 1 wird bedient Kunde 2 betritt den Laden Kunde 2 wird bedient Kunde 3 betritt den Laden Kunde 4 betritt den Laden Kunde 5 betritt den Laden Kunde 6 betritt den Laden Kunde 7 betritt den Laden Kunde 8 betritt den Laden Kunde 9 betritt den Laden Kunde 0 verlaesst den Laden Kunde 2 verlaesst den Laden Kunde 1 verlaesst den Laden Kunde 5 wird bedient Kunde 4 wird bedient Kunde 3 wird bedient Kunde 3 verlaesst den Laden Kunde 4 verlaesst den Laden Kunde 5 verlaesst den Laden Kunde 7 wird bedient Kunde 6 wird bedient Kunde 8 wird bedient Kunde 8 verlaesst den Laden Kunde 6 verlaesst den Laden Kunde 7 verlaesst den Laden Kunde 9 wird bedient Kunde 9 verlaesst den Laden
Beispiel für die Synchronisation durch Signale:
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void sighand(void) /* Signal Handler wird beim Eintreffen */
{ /* des Signales SIGUSR1 ausgefuehrt */
signal(SIGUSR1,&sighand);
/* Hier wird die Bindung des Signals */
/* an den Signal Handler sighand() erneuert. */
puts("Signalhandler aktiv!\n");
}
int main(void)
{
int vater_pid, prozess1_pid, prozess2_pid; /* PIDs der Soehne */
signal (SIGUSR1,&sighand); /* Bindung des Signals SIGUSR1 */
/* an den Signal Handler sighand() */
if ((prozess1_pid = fork()) == 0) /* Sohnprozess 1 wird erzeugt */
{ /* und gestartet */
vater_pid = getppid(); /* Sohnprozess erfragt die */
/* PID des Vaters */
printf("Sohn 1 laeuft\n");
sleep(3);
kill(vater_pid,SIGUSR1); /* Dem Vaterprozess wird das */
/* Signal SIGUSR1 gesendet */
printf("Sohn 1 terminiert\n");
exit(0);
}
if ((prozess2_pid = fork()) == 0) /* Sohnprozess 2 wird */
{ /* erzeugt und gestartet */
printf("Sohn 2 gestartet - wartet\n");
pause(); /* Sohnprozess 2 wartet */
/* auf ein Signal */
printf("Sohn 2 terminiert\n");
exit(0);
}
printf("Vater wartet auf Signal von Sohn 1\n");
pause();
printf("Vater: Signal von Sohn 1, kille Sohn 2\n");
kill(prozess2_pid,SIGUSR1);
putchar('\n');
return(0);
}
Die Ausgabe des Programms:
Vater wartet auf Signal von Sohn 1 Sohn 1 laeuft Sohn 2 gestartet - wartet Sohn 1 terminiert Signalhandler aktiv! Vater: Signal von Sohn 1, kille Sohn 2 Signalhandler aktiv! Sohn 2 terminiert
Die Verwendung von Spinlocks hat diverse Nachteile. Es wird keine Reihenfolge festgelegt, so dass einige Prozesse ggf. sehr lange warten müssen. Nach Freigabe des kritischen Bereiches besitzt jeder Prozess, auch jener, der den kritischen Bereich gerade verlassen hat, die selbe Wahrscheinlichkeit den kritischen Bereich als nächstes zu erhalten. Der größte Nachteil ist jedoch, daß die Prozesse nicht in den Zustand blockiert übergehen sondern immer wieder die while-Schleife durchlaufen, was zu einer höheren Belastung des Systems führt.
Unbenannte Pipe
Die (unbenannte) Pipe ist eingeschränkt.Ihre Lebensdauer
ist abhängig von der Lebensdauer der Prozesse die mit ihr
arbeiten. Sind all diese Prozesse beendet, wird die Pipe gelöscht.
Die Kommunikation über eine unbenannte Pipe ist nur für Prozesse
möglich, die im gleichen Prozeßbaum liegen.
Mit dem pipe()-Aufruf besitzt ein Prozess zunächst eine
Pipe zu sich selbst, aus der er mit Filehandle 0 Daten lesen kann.
Mit dem Filehandle 1 kann er Daten in diese Pipe schreiben.
Sinnvoll wird das erst, wenn der Vaterprozess durch einen
fork()-Aufruf einen Sohnprozess erzeugt, der mit dem
Vaterprozess Daten austauscht. Dieser Sohnprozess erbt die Pipe
seines Vaters. Die Richtung des Datenstromes wird
dadurch beeinflußt welcher Prozess die Lese-bzw. Schreibseite
der Pipe schließt.
Sollen zwei Söhne durch eine unbenannte Pipe miteinander kommunizieren, müssen folgende Schritte ausgeführt werden.
Das folgende Beispiel demonstriert, wie eine Shell prinzipiell vorgeht, wenn sie eine "Prozeß-Pipeline" ausführt. Angenommen, das Kommando ls | sort wird eingegeben. Dann läuft - vereinfacht dargestellt - der folgende Mechanismus ab:
int Pipe[2];
int status;
char *parls[] = { "/bin/ls", NULL };
char *parsort[] = { "/usr/bin/sort", NULL };
int main(void)
{
...
pipe(Pipe); // Pipe erzeugen
if (fork() == 0) // erster Sohn: "ls"
{
dup2(Pipe[1],1); // Pipeausgabe->Standardausgabe
close(Pipe[0]); // Pipeeingabe nicht benötigt
execve("/bin/ls",parls,NULL);
}
else
{
if (fork() == 0) // zweiter Sohn: "sort"
{
dup2(Pipe[0],0); // Pipeeingabe->Standardeingabe
close(Pipe[1]); // Pipeausgabe nicht benötigt
execve("/usr/bin/sort",parsort,NULL);
}
else // Vater (Shell)
{
close(Pipe[0]);
close(Pipe[1]);
wait(&status);
wait(&status);
}
}
...
}
| close | Schließt ein Schreib-oder Leseende einer Pipe. Prototyp: int close(int fd); Parameter: fd: Lese-bzw. Schreibdeskriptor einer Pipe |
| mkfifo | Erzeugt eine benannte Pipe. Prototyp: int mkfifo (char *name, int mode); Parameter: *name: Name bzw. Pfad der Pipe, mode: Bitmaske für Zugriffsrechte auf die Pipe. Die Positon und Bedeutung dieser Bits sind so wie beim numerischen chmod-Kommando (z.B. 0755 [führende Null wg. Oktalangabe]). Rückgabewert: 0 bei erfolgreicher Ausführung, sonst -1. |
| open | öffnet eine Pipe bzw. Datei. Prototyp: open (char *name, int flag, int mode); Parameter: *name: Name bzw. Pfad der Pipe, flag: Bitmuster für Zugriff auf die Pipe (O_RDONLY Lesezugriff, O_WRONLY Schreibzugriff, O_NONBLOCK Prozessverhalten) Wird O_NONBLOCK nicht angegeben (Normalfall), blockiert der Leseprozess, bis ein anderer Prozess die Pipe zum Schreiben öffnet und umgekehrt. Rückgabewert: -1 bei Fehler oder Dateideskriptor für die Pipe. |
| pipe | Erzeugt eine unbenannte Pipe. Prototyp: int ipe (int fd[2]); Parameter: fd[2]: zwei Dateideskriptoren, die zurückgegeben werden, wobei fd[0] der Dateideskriptor für die Leseseite und fd[1] Dateideskriptor für die Schreibseite der Pipe ist. |
| read | Lesen der Daten aus einer Pipe. Ist die Pipe leer, blockiert die
Funktion. Prototyp: int read (int fd, char *outbuf, unsigned bytes); Parameter: fd: Diskriptor der Pipe, *outbuf: Zeiger auf den Speicherbereich, in dem die Daten gespeichert werden und bytes: Maximale Anzahl der Bytes, die gelesen werden. Rückgabewert: Anzahl der tatsächlich gelesenen Bytes, -1 bei einem Fehler und 0, wenn die Schreibseite der Pipe geschlossen wurde. |
| unlink | Löscht die benannte Pipe. Prototyp: int unlink (char *name);< Parameter: *name: Name/Pfad der Pipe. |
| write | Schreibt Daten in eine Pipe. Ist der Pipe-Buffer voll, blockiert
diese Funktion. Prototyp: int write (int fd, char *outbuf, unsigned bytes); Parameter: fd: Diskriptor der Pipe, *outbuf: Zeiger auf den Speicherbereich, in dem die zu schreibenden Daten stehen und bytes: Anzahl der Bytes, die geschrieben werden. |
Beispiel: Named Pipe für zwei getrennte Prozesse In Unix/Linux können benannte Pipes auch für die Kommunikation zwischen Prozesse eingesetzt werden, die nicht miteinander "verwandt" sind:
/* Empfaenger */
#include<stdio.h>
#include<signal.h>
#include <unistd.h>
#include<fcntl.h>
int main(void)
{
int ein; /* Hilfsvariable fuer Programmstart */
int hilf;
char outbuffer[2]; /* Buffer zum Auslesen der Pipe */
int fd; /* Dateideskriptor fuer Pipe */
int gelesen; /* speichert die Anzahl der gelesenen Bytes */
printf("Empfaengerprozess wurde gestartet\n\n");
do
{
fd = open("TESTPIPE",O_RDONLY); /* Oeffnen der Pipe zum Lesen */
if (fd == -1) printf("Prozess zum Schreiben in die Pipe starten!\n");
sleep(2);
}
while (fd == -1);
do
{
gelesen = read(fd,outbuffer,2); /* 2 Bytes werden ausgelesen */
if (gelesen != 0) printf("Lese %c aus der Pipe\n",outbuffer[0]);
sleep(2);
}
while (gelesen > 0);
unlink("TESTPIPE"); /* benannte Pipe wird geloescht */
return(0);
}
/* Sender */
#include<stdlib.h>
#include <unistd.h>
#include<stdio.h>
#include<fcntl.h>
int main(void)
{
int hilf;
char inbuffer[2]; /* Buffer zum Schreiben in die Pipe */
int fd; /* Dateideskriptor fuer Pipe */
system("mkfifo TESTPIPE -m 666"); /* benannte Pipe wird erzeugt */
printf("Sendeprozess wurde gestartet\n\n");
mkfifo("TESTPIPE",0666); /* benannte Pipe wird erzeugt */
fd = open("TESTPIPE",O_WRONLY); /* Oeffnen der Pipe zum */
for(hilf=0; hilf<10; hilf++) /* Zaehler zum Schreiben */
{
inbuffer[0] = (int)'0' + hilf;
inbuffer[1] = '\0';
write(fd,inbuffer,2); /* 2 Bytes werden geschrieben */
printf("Schreibe %c in die Pipe\n",inbuffer[0]);
sleep(1);
}
return(0);
}
Eine solche Queue kann mit Nachrichten verschiedenen Typs arbeiten. Der Typ wird durch die Anwendung bestimmt und ist einfach eine Zahl. Ein Prozess kann Nachrichten an die Warteschlange senden. Beim Erreichen der Kapazität der Schlange kann der Prozess per Parameter bestimmen, ob er blockieren will bis die Nachricht abzuliefern ist oder mit einem Fehler zurückkehren möchte. Auf der anderen Seite kann ein Prozess eine Nachricht bestimmten Typs anfordern. Auch hier kann der Prozess warten, bis er eine passende Nachricht bekommt, oder mit einer Fehlermeldung sofort zurückkehren.
Die folgenden Funktionen msgsnd() und msgrcv() verwenden eine Struktur msgbuf für ihre Nachrichten:
struct msgbuf
{
long mtype; /* von der Anwendung definierbar > 0 */
char mtext[1]; /* Nachrichtendaten beginnen hier */
};
Es kann als Typ eine beliebige Zahl größer Null verwendet werden,
die allein von der Applikation festgelegt werden. Für die eigenen
Nachrichten werden Sie im mtext vermutlich mehr als ein Zeichen
versenden wollen. Dazu definieren Sie sich eine eigene Struktur
mit entsprechend größerem Datenpuffer. Die Größe wird beiden
Funktionen als Parameter übergeben.
An Include-Dateien werden benötigt:
#include <sys/ipc.h> #include <sys/msg.h>
| msgget | Die Funktion legt eine Message Queue an. Prototyp: int msgget(key_t key, int msgflg); Parameter: key ist entweder eine Schlüsselzahl oder IPC_PRIVATE, msgflg kombiniert die Konstanten IPC_CREAT und IPC_EXCL und deren Oder-Verknüpfung mit neun Berechtigungsbits für den Eigner, die Gruppe und der Welt, wie sie vom Kommando chmod verwendet werden. Rückgabewert: -1 im Fehlerfall oder die Message-Queue-ID, die für die nächsten Aufrufe benötigt wird. |
| msgsnd | Versenden von Nachrichten. Prototyp: int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg); Parameter: msqid ist der Rückgabewert der Funktion msgget(). msgp ist die Adresse der Datenstruktur mit dem Nachrichtentyp und den Daten. msgsz ist so groß wie das Array mtext in der Datenstruktur für die Nachricht. msgflg kann mit der Optionen IPC_NOWAIT besetzt werden, wenn die Funktion bei einer übervollen Message-Queue nicht blockieren und warten soll, bis wieder Platz ist, sondern mit einem Fehler zurückkehren. |
| msgrcv | Nachrichten empfangen. Prototyp: int msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg); Parameter: msqid ist der Rückgabewert der Funktion msgget(). msgp ist die Adresse der Datenstruktur mit dem Nachrichtentyp und den empfangenen Daten. msgsz ist so groß wie das Array mtext in der Datenstruktur für die Nachricht. msgtyp legt fest, auf welchen Nachrichtentyp msgrcv() warten soll. Alle anderen Typen werden von msgrcv() ignoriert. Wird hier 0 angegeben, nimmt mgsrcv() jeden Typ entgegen. msgflg kann mit der Optionen IPC_NOWAIT besetzt werden, wenn die Funktion nicht blockieren und warten soll, bis eine Nachricht vorliegt, sondern bei leerer Queue mit einem Fehler zurückkehrt. |
| msgctl | Eigenschaften der Nachrichten verwaltet. Prototyp: int msgctl(int msqid, int kommando, struct msqid_ds *buf); Parameter: msqid ist der Rückgabewert der Funktion msgget(). Mit kommando können folgende Konstanten übergeben werden: IPC_STAT: Die Informationen über die Message Queue einlesen IPC_SET: ändere die Benutzerrechte in mode IPC_RMID: Zerstört die Message Queue und weckt alle darauf wartenden Prozesse |
Mit dem Kommando ipcs erhalten Sie einen Überblick über angeforderte Message-Queues.
Beispiel:
Das Programm rcv.c wartet auf eine Nachricht in einer Message-Queue.
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#define MSGSIZE 20
int main(void)
{
key_t Key = 666;
long Msgtyp = 4711;
int MsgID;
struct myMsg
{
long mtype;
char mtext[MSGSIZE];
} MsgData;
MsgID = msgget(Key, IPC_CREAT | 0666); /* Messagequeue oeffnen/erzeugen */
if (MsgID >= 0)
{
printf("Warte auf Message Type %ld\n", Msgtyp);
if (msgrcv(MsgID, &MsgData, MSGSIZE, Msgtyp, 0) == -1)
{ printf("Fehler in msgrcv\n"); }
else
{ printf("Daten empfangen: %s\n", MsgData.mtext); }
}
else
{ printf("Fehler in msgget\n"); }
return(0);
}
Das Programm snd.c sendet Nachrichten.
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#define MSGSIZE 20
int main(void)
{
key_t Key = 666;
long Msgtyp = 4711;
int MsgID;
struct myMsg
{
long mtype;
char mtext[MSGSIZE];
} MsgData;
MsgData.mtype = Msgtyp;
strncpy(MsgData.mtext, "Hello World", MSGSIZE); /* Datenpuffer fuellen */
MsgID = msgget(Key, IPC_CREAT | 0666); /* Messagequeue oeffnen/erzeugen */
if (MsgID >= 0)
{
printf("Sende Messagetyp %ld\n", MsgData.mtype);
if (msgsnd(MsgID, &MsgData, MSGSIZE, 0) == -1)
{ printf("Fehler in msgsnd\n"); }
else
{ printf("Daten gesendet: %s\n", MsgData.mtext); }
}
else
{ printf("Fehler in msgget\n"); }
return(0);
}
Die Message-Queue bleibt solange erhalten, bis ein Programm sie explizit per msgctl()
mit dem Kommando IPC_RMID entfernt, oder bis sie mit dem Befehl ipcrm gelöscht
wird (siehe man ipcrm, man ipcs).
An Include-Dateien werden benötigt:
#include <sys/ipc.h> #include <sys/shm.h>
| shmget | Legt den gemeinsamen Speicher an bzw. eröffnet ihn.
Prototyp: int shmget(key_t key, int size, int shmflg); Parameter: key ist entweder eine Schlüsselzahl oder IPC_PRIVATE. shmflg bildet eine Oder-Verknüpfung der Konstanten IPC_CREAT bzw. IPC_EXCL mit den neun Berechtigungsbits für den Eigner, die Gruppe und der Welt (oktal!, wie bei chmod). Rückgabewert: -1 im Fehlerfall oder die Shared-Memory-ID, die für die nächsten Aufrufe benötigt wird. |
| shmat | (shared memory attach) bindet den Speicher ein.
Prototyp: void *shmat(int shmid, const void *shmaddr, int shmflg); Parameter: shmid ist die von shmget() ermittelte ID. shmaddr ist normalerweise 0, dann sucht sich das System eine passende Stelle. shmflg ist entweder 0 oder SHM_RDONLY, wenn auf den Speicher nur lesend zugegriffen werden soll. Rückgabewert: Der Fehlerwert von shmat() ist -1 und nicht NULL, wie man erwarten sollte. Daher ergeben sich Abfragen wie: myPtr = shmat(shID, 0, 0); if (myPtr == (char *)-1) ... |
| shmdt | (shared memory detach) hebt die Speicherbindung wieder auf. Prototyp: int shmdt(const void *shmaddr); Parameter: shmaddr ist der Rückgabewert von shmat(). Rückgabewert: -1 im Fehlerfall, sonst 0. |
| shmctl | Bestimmte Eigenschaften des gemeinsamen Speichers verwaltet. Prototyp: int shmctl(int shmid, int kommando, struct shmid_ds *buf); Parameter: shmid ist der Rückgabewert der Funktion shmget(). Mit kommando können folgende Konstanten übergeben werden: IPC_STAT: Die Informationen über den Speicher einlesen IPC_SET: Ändere die Benutzerrechte in mode IPC_RMID: Markiere das Segment als zerstört |
Mit dem Kommandozeilenbefehl ipcs bekommen Sie, wie bei den Message-Queues, einen Überblick über die angeforderten Shared-Memory-Bereiche.
Beispiel: Das erste Programm erzeugt einen Shared-Memory-Block von 128 Bytes und schreibt dort ASCII-Zeichen hinein.
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#define MAXMYMEM 100
int main(void)
{
int ShmID;
key_t Key = 1234;
char *ShmPtr;
int i;
/* Shared Memory erzeugen */
ShmID = shmget(Key, MAXMYMEM, IPC_CREAT | 0666);
if (ShmID >= 0)
{
/* nun holen wir den Speicher */
ShmPtr = shmat(ShmID, 0, 0);
if (ShmPtr == (char *)-1)
{ printf("Fehler bei shmat\n"); }
else
{
for (i=0; i<95; i++)
ShmPtr[i] = (char)((int)' ' + i);
while (getchar() != '\n'); /* Warte auf Enter */
shmdt(ShmPtr);
}
}
else
{ printf("Fehler bei shget\n"); }
return(0);
}
Das zweite Programm unterscheidet sich wenig vom vorhergehenden. Da das erste
Programm den Speicher reserviert, braucht das zweite dies nicht zu tun, es
liest einfach den Inhalt des Speichers aus und gibt ihn auf dem Bildschirm aus.
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#define MAXMYMEM 100
int main(void)
{
int ShmID;
key_t Key = 1234;
char *ShmPtr;
int i;
/* Existierenden Shared Memory zugreifen */
ShmID = shmget(Key, MAXMYMEM, 0666);
if (ShmID >= 0)
{
ShmPtr = shmat(ShmID, 0, 0);
if (ShmPtr == (char *)-1)
{ printf("Fehler bei shmat\n"); }
else
{
for (i=0; i<95; i++)
putchar(ShmPtr[i]);
putchar('\n');
shmdt(ShmPtr);
}
}
else
{ printf("Fehler bei shget\n"); }
return(0);
}
Man kann die Programme nacheinander laufen lassen. Nach dem ersten
Programmlauf sieht man mit ipcs, daß der Shared-Memory-Block
noch vorhanden ist.
Sockets werden an anderer Stelle behandelt.
Zum vorhergehenden Abschnitt |
Zum Inhaltsverzeichnis |
Zum nächsten Abschnitt |