Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 9 IPC – Interprozesskommunikation
  gp 9.1 Unterschiedliche Interprozesskommunikations-Techniken im Überblick
    gp 9.1.1 (Namenlose) Pipes
    gp 9.1.2 Benannte Pipes (FIFO-Pipes)
    gp 9.1.3 Message Queue (Nachrichtenspeicher)
    gp 9.1.4 Semaphore
    gp 9.1.5 Shared Memory (gemeinsamer Speicher)
    gp 9.1.6 STREAMS
    gp 9.1.7 Sockets
    gp 9.1.8 Lock Files (Sperrdateien)
    gp 9.1.9 Dateisperren (Record Locking)
  gp 9.2 Gründe für IPC
  gp 9.3 Pipes
    gp 9.3.1 Eigenschaften von Pipes
    gp 9.3.2 Pipes einrichten – pipe()
    gp 9.3.3 Eigenschaften von elementaren E/A-Funktionen bei Pipes
    gp 9.3.4 Standard-E/A-Funktionen mit pipe
    gp 9.3.5 Pipes in einen anderen Prozess umleiten
    gp 9.3.6 Filterprogramm erstellen mithilfe einer Pipe
    gp 9.3.7 Einrichten einer Pipe zu einem anderen Prozess – popen()
    gp 9.3.8 Mail versenden mit Pipes und Sendmail
    gp 9.3.9 Drucken über eine Pipe mit lpr
    gp 9.3.10 Benannte Pipes – FIFOs
  gp 9.4 System-V-Interprozesskommunikation
    gp 9.4.1 Gemeinsamkeiten der SysV-Mechanismen
    gp 9.4.2 Ein Objekt einrichten, eine Verbindung herstellen und das Objekt wieder löschen
    gp 9.4.3 Datenaustausch zwischen nicht verwandten Prozessen
  gp 9.5 Semaphore
    gp 9.5.1 Lebenszyklus eines Semaphors
    gp 9.5.2 Ein Semaphor öffnen oder erstellen – semget()
    gp 9.5.3 Abfragen, Ändern oder Löschen der Semaphormenge – semctl()
    gp 9.5.4 Operationen auf Semaphormengen – semop()
    gp 9.5.5 Semaphore im Vergleich mit Sperren
  gp 9.6 Message Queues
    gp 9.6.1 Eine Message Queue öffnen oder erzeugen – msgget()
    gp 9.6.2 Nachrichten versenden – msgsnd()
    gp 9.6.3 Eine Nachricht empfangen – msgrcv()
    gp 9.6.4 Abfragen, Ändern oder Löschen einer Message Queue – msgctl()
  gp 9.7 Shared Memory
    gp 9.7.1 Ein Shared-Memory-Segment erstellen oder öffnen – shmget()
    gp 9.7.2 Ein Shared-Memory-Segment abfragen, ändern oder löschen – shmctl()
    gp 9.7.3 Ein Shared-Memory-Segment anbinden (attach) – shmat()
    gp 9.7.4 Ein Shared-Memory-Segment loslösen – shmdt()
    gp 9.7.5 Client-Server-Beispiel – Shared Memory


Rheinwerk Computing

9.5 Semaphore  downtop

Um es gleich von vornherein klarzustellen, die Semaphore nach System V haben nichts mit den Kernel-Semaphoren zu tun. Rein theoretisch könnten Sie Semaphore selbst nachbilden. Hierzu würden eine globale Variable und eine Funktion ausreichen. Will man nun einen kritischen Codebereich für andere Prozesse sperren, müsste die Funktion die globale Variable auf 0 setzen. Alle anderen Prozesse, die jetzt ebenfalls in diesen kritischen Bereich kommen, müssen nun warten, bis die globale Variable wieder einen positiven Wert bekommt (1). Dies ist natürlich nur rein theoretisch gesehen.

Meistens werden Semaphore binärer Natur verwendet (wie eben beschrieben). Das sind Semaphore, die man für gewöhnlich verwendet, um kritische Ressourcen zu schützen. Dazu reichen die zwei Zustände (bi) 1 und 0 aus. Es lassen sich aber auch allgemeine Semaphore verwenden – die dann allerdings die Werte 0, 1, 2, 3 ... n annehmen können. Der höchste Wert zeigt dann an, wie viele Prozesse den kritischen Bereich noch verwenden.

In System V sind die Semaphore mittlerweile mehr als nur globale Variablen geworden. Um mit diesen Semaphoren zu arbeiten, steht dem Entwickler ein ganzer Satz an Funktionen zur Verfügung. Es ist damit auch möglich, nicht nur auf einem, sondern auf einem ganzen Satz von Semaphoren Operationen durchzuführen.


Rheinwerk Computing

9.5.1 Lebenszyklus eines Semaphors  downtop

Der Titel ist ein wenig zweischneidig und soll Ihnen mehr oder weniger den üblichen Weg bzw. die Verwendung eines Semaphors zeigen. Hier die einzelnen Schritte.

gp  Zuerst wird ein Semaphor erzeugt, oder, falls es existiert, das bereits erzeugte wird geöffnet. Beides geschieht mit der Funktion semget(). Ein erzeugtes Semaphor sollte zu Beginn gleich auf einen positiven Wert (1) gesetzt werden – was die Funktion semctl() erledigt. Für alle anderen Prozesse, die ein bereits erzeugtes Semaphor verwenden, sollte dies unbedingt unterbleiben. Denn würde jeder Prozess das Semaphor auf einen positiven Wert setzen, so würde vielleicht ein Prozess, der auf einen anderen Prozess wartet, der gerade in einem kritischen Codeausschnitt arbeitet, ebenfalls Zugriff auf den kritischen Codeausschnitt bekommen.
gp  Wird jetzt eine Sperre für einen kritischen Codebereich benötigt, wird das Semaphor auf den Wert 0 gesetzt – dies erledigen Sie mit der Funktion semop().
gp  Wurde jetzt ein weiterer Prozess gestartet, der nun ebenfalls auf diesen kritischen Bereich zugreifen will, wird das Semaphor überprüft, das für die Synchronisation des Bereichs verantwortlich ist. Ist der Wert des Semaphors positiv, so kann der Prozess auf diesen Bereich zugreifen. Dabei dekrementiert logischerweise der Prozess dieses Semaphor, so dass hier wiederum kein anderer Prozess zugreifen kann. Ist hingegen das Semaphor gleich 0, wartet der Prozess so lange, bis der kritische Codebereich freigegeben wird – genauer, bis der Wert des Semaphors wieder positiv (1) ist.
gp  Ist ein Prozess mit dem kritischen Codebereich fertig, muss der Wert des Semaphors wieder auf 1 gesetzt werden, um es den anderen Prozessen zu gestatten, ebenfalls auf diesen Bereich zuzugreifen. Dies wird ebenfalls wieder mit der Funktion semop() gemacht.

Diesen einfachen Vorgang will ich Ihnen nun anhand eines Listings mit anschließender Erklärung demonstrieren:

/* sem.c */
#include <stdio.h>
#include <unistd. h.>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define LOCK       -1
#define UNLOCK      1
#define PERM 0666      /* Zugriffsrechte */
#define KEY  123458L     
static struct sembuf semaphore;
static int semid;
static int init_semaphore (void) {
   /* Testen, ob das Semaphor bereits existiert */
   semid = semget (KEY, 0, IPC_PRIVATE);
   if (semid < 0) {
      /* ... existiert noch nicht, also anlegen        */
      /* Alle Zugriffsrechte der Dateikreierungsmaske */
      /* erlauben                                     */
      umask(0);
      semid = semget (KEY, 1, IPC_CREAT | IPC_EXCL | PERM);
      if (semid < 0) {
         printf ("Fehler beim Anlegen des Semaphors ...\n");
         return -1;
      }
      printf ("(angelegt) Semaphor-ID : %d\n", semid);
      /* Semaphor mit 1 initialisieren */
      if (semctl (semid, 0, SETVAL, (int) 1) == -1)
         return -1;
   }
   return 1;
}
static int semaphore_operation (int op) {
   semaphore.sem_op = op;
   semaphore.sem_flg = SEM_UNDO;
   if( semop (semid, &semaphore, 1) == -1) {
      perror(" semop ");
      exit (EXIT_FAILURE);
   }
   return 1;
}
int main (void) {
   int res;
   res = init_semaphore ();
   if (res < 0)
      return EXIT_FAILURE;
   printf ("Vor dem kritischen Codeausschnitt ...\n");
   semaphore_operation ( LOCK );
   /* Kritischer Codeausschnitt */
   printf ("PID %d verwendet Semaphor %d\n",
      getpid(), semid);
   printf ("Im kritischen Codeabschnitt ...\n");
   sleep (10);
   semaphore_operation ( UNLOCK );
   printf ("Nach dem kritischen Codeausschnitt ...\n");
   //semctl (semid, 0, IPC_RMID, 0);
   return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

tty1 $ gcc -o sem sem.c
tty1 $ ./sem
Semaphor-ID : 1048577
Vor dem kritischen Codeausschnitt ...
PID 3582 verwendet Semaphor 1048577
Im kritischen Codeabschnitt ...
[inzwischen vor Ablauf der 10 Sekunden in eine weitere Konsole]
tty2 $ ./sem
Semaphor-ID : 1048577
Vor dem kritischen Codeausschnitt ...
[Wartet auf Freigabe des Semaphors von PID 3582]
[ inzwischen tty0 ]
Nach dem kritischen Codeausschnitt ...
$ tty1
[Jetzt ist das Semaphor frei für tty1]
PID 3583 verwendet Semaphor 1048577
Im kritischen Codeabschnitt ...
[nach 10 Sek.]
Nach dem kritischen Codeausschnitt ... 

Sie können hierzu jetzt beliebig viele Prozesse starten. Es kann aber immer nur ein Prozess in dem kritischen Codebereich zugreifen. Einen Schönheitsfehler hat dieses Beispiel allerdings dann doch noch. Geben Sie in der Kommandozeile Folgendes ein:

$ ipcs
Schlüssel shmid      Besitzer Rechte  Bytes  nattch   Status
0x0000000 131074     tot      777   196608    2        dest
0x0000000 1971978243 root     644   110592    2        dest
0x0000000 1975975940 root     644   110592    2        dest
0x0000000 1981513733 root     644   110592    2        dest
----- Semaphorfelder -----
Schlüssel  SemID      Besitzer   Rechte     nsems
0x0001e242 1048577    tot        666        1
----- Nachrichtenwarteschlangen -----
Schlüssel  msqid      Besitzer   Rechte     used-bytes   messages

Hier sind momentan nur die Semaphorfelder von Interesse. Das Semaphor bleibt also nach Beendigung aller Prozesse erhalten. Von »Hand« können Sie dieses folgendermaßen entfernen (die ID sei 1048577):

$ ipcrm -s 1048577

Das dürfte aber bei sehr vielen angelegten Semaphoren ein Problem werden. Die folgende (auskommentierte) Zeile würde diesen Vorgang zwar für Sie übernehmen:

//semctl (semid, 0, IPC_RMID, 0);

hätte aber zur Folge, dass nach Beendigung des ersten Prozesses das Semaphor gelöscht wird, und alle anderen Prozesse hätten diesbezüglich kein Semaphor mehr zur Verfügung. Daher verwendet man auch hier in der Praxis das Server-Client-Prinzip. Der Server erzeugt ein Semaphor und kümmert sich auch wieder darum, dass dies beseitigt wird. Die Clients greifen nur auf dieses eine Semaphor zu. Aber jetzt erst zu den Beschreibungen der einzelnen Funktionen im Listing.

Zuerst benötigen Sie, um ein Semaphor anzulegen, die magische Nummer, die hier einfach mit 123458L definiert wurde. Anhand dieser Nummer werden die Semaphore eindeutig im System identifiziert.


Rheinwerk Computing

9.5.2 Ein Semaphor öffnen oder erstellen – semget()  downtop

Die erste Funktion, die im Listing aufgerufen wurde, war init_semaphore(). Zuerst wird versucht, mit der Funktion semget() ein bereits erstelltes Semaphor zu öffnen.

#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/sem.h>
int semget(key_t key, int n_sems, int flag);

Als erstes Argument bekommt semget() die magische Nummer. Das zweite Argument entspricht der Anzahl der Semaphore im Satz, und mit dem letzten Argument können die Zugriffsprivilegien vergeben werden. Bei den Flags können Sie im Prinzip drei Angaben machen, was ebenso auf die Message Queues und die Shared Memories zutrifft.

gp  IPC_PRIVATE – Öffnen eines privaten Schlüssels
gp  IPC_CREAT – Damit wird ein noch nicht existierender Schlüssel erzeugt.
gp  IPC_EXCL – Dieses Flag wird in der Regel mit dem bitweisen ODER-Operator hinter IPC_CREAT angefügt. Damit gehen Sie sicher, dass wirklich ein neues Objekt (Semaphor) angelegt wird und nicht ein bereits existierendes mit derselben Kennung. Existiert bereits eine Kennung, bricht die entsprechende Funktion – hier semget() – mit einem Fehler ab (errno == EEXIST).

Beim ersten semget()-Aufruf geben Sie als erstes Argument den magischen Schlüssel an, als zweites Argument 0, was bedeutet, dass Sie eine bereits existierende Semaphormenge öffnen wollen. Als letztes Argument geben Sie IPC_PRIVATE an – also das Öffnen eines bereits existierenden Schlüssels. Bei erfolgreichem Funktionsaufruf gibt semget() die Semaphor-ID zurück, die Sie in der Konsole mit dem Tool ipcs ermitteln können. Schlägt die Funktion hingegen fehl, was beim ersten Aufruf der Fall sein sollte, so gibt semget() -1 zurück. Somit springt das Programmbeispiel mit seiner Ausführung in die if-Verzweigung.

Darin wird nun versucht, eine neue Semaphormenge zu erstellen:

semid = semget (KEY, 1, IPC_CREAT | IPC_EXCL | PERM);

Auch hier wird als erstes Argument die magische Kennung übergeben, als zweites Argument die Anzahl der Semaphormenge (1). Und zu guter Letzt die Flags IPC_CREAT, IPC_EXCL und, sehr wichtig, die Zugriffsrechte auf die Semaphore. Vergessen Sie die Zugriffsrechte oder setzen Sie diese falsch, werden Sie keinen Zugriff auf die Semaphormenge haben. Zurückgegeben wird hier bei Erfolg die Semaphor-ID.


Rheinwerk Computing

9.5.3 Abfragen, Ändern oder Löschen der Semaphormenge – semctl()  downtop

Im nächsten Schritt setzen Sie den Wert der Semaphorvariablen auf den Wert 1. Dieser Vorgang ist ebenfalls von Bedeutung. Würden Sie die Semaphore gleich zu Beginn auf -1 setzen, dann müssen dies die Clientprogramme bzw. das Programm, das auf die Semaphormenge zugreifen will, ebenso berücksichtigen. Gewöhnlich setzt man den Wert allerdings auf 1. Ein negatives Setzen der Semaphorvariablen zu Beginn entspricht etwa dem Sinn bei einer Bahnschranke, die nur öffnet, wenn ein Auto durchfahren will, was ja gewöhnlich umgekehrt ist.

Das Setzen, aber auch Abfragen oder Löschen einer Semaphormenge lässt sich alles mit der Funktion semctl() realisieren:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semphoren_ID, int sem_num,
           int kommando, union semun arg);

Als erstes Argument legen Sie die Semaphore fest, worauf sich semctl() beziehen soll. Die ID dazu haben Sie als Rückgabewert der Funktion semget() erhalten. Mit dem zweiten Argument spezifizieren Sie einen bestimmten Semaphorwert aus der Menge. sem_num ist auch bei einigen der kommando-Angaben von Bedeutung. Bevor auf die einzelnen Kommandos und deren Bedeutung eingegangen wird, zuerst noch die union semun, die wie folgt definiert ist:

#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;                 /* Werte für  SETVAL        */
     struct semid_ds *buf;    /* Puffer IPC_STAT, IPC_SET */
     unsigned short *array;   /* Array für GETALL, SETALL */
                              /* Linux specific part:     */
     struct seminfo *__buf;   /* Puffer für IPC_INFO      */
       };
#endif

Jetzt zu den einzelnen gängigen Kommandos, die als drittes Argument angegeben werden können, und deren Bedeutung. Für mehr Details sollten Sie allerdings die Manual Page lesen, da eine ausführlichere Erklärung weit über den Rahmen des (ohnehin schon umfangreichen) Kapitels hinausgehen würde.


Tabelle 9.1    Kommandoangaben für die Funktion semctl()

Kommando Bedeutung
IPC_STAT Hiermit kann die Struktur semid_ds angefragt werden. Der Inhalt befindet sich anschließend in der Adresse von buf.
IPC_SET Hiermit können Eigentümer und die Zugriffsrechte gesetzt werden.
IPC_RMID Löschen einer Semaphormenge
GETVAL Hiermit fragen Sie den Wert der Semaphorvariablen ab.
SETVAL Hiermit können Sie den Wert der Semaphorvariablen setzen.
GETPID Gibt die PID des Prozesses zurück, der zuletzt auf die Semaphorvariablen zugegriffen hat.
GETNCNT Abfragen der Prozesse, die warten, bis die Semaphorvariable größer als 0 wird
GETZCNT Abfragen der Prozesse, die warten, bis die Semaphorvariable gleich 0 ist
GETALL Abfragen der Werte aller Semaphorvariablen
SETALL Setzen aller Semaphorvariablen
IPC_INFO (nur Linux) Damit lassen sich Informationen zur entsprechenden Semaphor erfragen.

Der Rückgabewert der Funktion semctl() ist entweder bei Erfolg 0 oder, wurde eines der GET..-Kommandos verwendet, dann eben ein entsprechender Wert. Bei einem Fehler gibt semctl()-1 zurück.

Somit hätten Sie die Semaphore für den eigentlichen Arbeitsvorgang eingerichtet; wobei der weitere Vorgang keine Bäume mehr ausreißen dürfte.


Rheinwerk Computing

9.5.4 Operationen auf Semaphormengen – semop()  downtop

Eine Operation können Sie mit der Funktion semop()durchführen – wobei mit durchführen das Verändern der Semaphorvariablen gemeint ist.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf sem_array[], size_t n_op);

Mit dem ersten Argument legen Sie wieder fest, auf welcher Semaphormenge die Operationen mit der Funktion semop() durchgeführt werden sollen. sem_array ist eine Adresse eines Arrays von Semaphoroperationen. Folgende Elemente sind in dieser Struktur enthalten:

struct sembuf {
   unsigned short sem_num;  /* Semaphornummer der Menge  */
   short sem_op;            /* Semaphoroperation         */
   short sem_flg;           /* Flags: IPC_NOWAIT,SEM_UNDO  */
  }      

Mit dem dritten Argument von semop() geben Sie die Anzahl der Elemente im Array sem_array an, was durchaus mehr als, wie hier im Beispiel demonstriert, ein Element sein kann.

Für die Angabe von sem_op in der Struktur sembuf sind folgende drei Fälle zu unterscheiden:

gp  sem_op > 0 – Ist sem_op größer als 0, also 1, wird die Ressource für alle anderen Prozesse freigegeben.
gp  sem_op < 0 – Ist die Semaphorvariable kleiner als 0, also -1, dann ist der kritische Bereich für alle anderen Prozesse gesperrt. Will jetzt ein weiterer Prozess auf diese Ressource zugreifen, hängt dies davon ab, ob sem_flag, das dritte Element der Struktur sembuf, auf IPC_NOWAIT gesetzt wurde. Ist dies der Fall, schlägt der Funktionsaufruf semop() fehl. Ist IPC_NOWAIT nicht gesetzt, wartet der Prozess so lange, bis die Ressource wieder freigegeben wurde.
gp  sem_op == 0 – Wird die Semaphorvariable auf 0 gesetzt, kehrt semop() sofort wieder zurück. Wenn die Semaphorvariable ungleich 0 ist, hängt dies wiederum von sem_flag und IPC_NOWAIT ab. Ist IPC_NOWAIT gesetzt, beendet sich semop() mit einem Fehler. Ansonsten wartet der Prozess so lange, bis entweder sem_op gleich 0 ist oder das Semaphor gelöscht wurde.

Hilfreich ist auch das Flag SEM_UNDO. Wird nämlich ein Prozess vorzeitig beendet, wird damit sichergestellt, dass mithilfe eines undo-Zählers das vom Prozess gesetzte Semaphor wieder zurückgesetzt wird.

Anders als im Beispiel gesehen können Sie sembuf auch wie folgt mit Werten initialisieren:

struct sembuf semaphore_lock[1]   = { 0, -1, SEM_UNDO };
struct sembuf semaphore_unlock[1] = { 0, 1,  SEM_UNDO };
...
/* Ressource sperren */
semop(semid, &semaphore_lock[0], 1); 
...
/* Ressource freigeben */
semop(semid, &semaphore_unlock[0], 1 );

Um in diesem Beispiel die Funktion semaphore_operation() komplett zu machen, sollten Sie noch eine weitere symbolische Konstante 0 als ZERO definieren, womit Sie alle drei Möglichkeiten der Semaphorvariablen ausgeschöpft hätten. Mit

semaphore_operation ( LOCK );

sperren Sie einen kritischen Bereich. LOCK ist als -1 definiert. Und mittels

semaphore_operation ( UNLOCK );

geben Sie die Ressource wieder frei, da UNLOCK als 1 definiert wurde.


Rheinwerk Computing

9.5.5 Semaphore im Vergleich mit Sperren  toptop

Semaphore sind den Sperren ja nicht unähnlich, weshalb ein Vergleich hier lohnt. Auf beiden Seiten hat man einen Vorteil. Die Verwendung von Sperren (Record Locking) gestaltet sich wesentlich einfacher. Dafür ist der Geschwindigkeitsvorteil ganz klar auf der Seite der Semaphore. Somit bleibt es letztendlich dem Entwickler selbst überlassen, wofür er sich entscheidet.

 << zurück
  
  Zum Katalog
Zum Katalog: Linux-UNIX-Programmierung
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Linux-Server






 Linux-Server


Zum Katalog: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Katalog: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Katalog: Shell-Programmierung






 Shell-
 Programmierung


Zum Katalog: Linux Handbuch






 Linux Handbuch


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de