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.7 Shared Memory  downtop

Shared Memory ist ein vom Kernel verwalteter Speicherbereich, der von mehreren Prozessen gelesen und beschrieben werden kann. Es muss dabei lediglich beachtet werden, dass die einzelnen Prozesse untereinander abgeglichen, also synchronisiert werden müssen. Außerdem ist diese Form der Interprozesskommunikation die schnellste von allen, da kein Kopieren von Clientprogrammen zum Serverprogramm (oder auch umgekehrt) nötig ist, weil ein gemeinsamer Speicherbereich verwendet wird. Damit es nicht vorkommt, dass mehr als ein Prozess aus diesem Segment lesend und vor allem nicht schreibend zugreift, verwendet man häufig dazu Sperren (Record Locks) oder Semaphore. Ein solches Shared-Memory-Segment existiert auch nach der Beendigung seines Erzeugers, sofern nicht das Flag IPC_RMID angegeben wurde.


Rheinwerk Computing

9.7.1 Ein Shared-Memory-Segment erstellen oder öffnen – shmget()  downtop

Wollen Sie ein Shared-Memory-Segment erzeugen oder ein bereits existierendes öffnen, dann müssen Sie die Funktion shmget() verwenden:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flag);

Als erstes Argument erhält diese Funktion, wie schon bei den anderen SysV-Interprozesskommunikationen, die »magische« Nummer key. Mit size geben Sie die Mindestgröße des neu anzulegenden Shared-Memory-Segmentes an. Gewöhnlich sind hierfür die Limits für die Mindestgröße in der symbolischen Konstante SHMMIN mit 1 und die max. Größe in SHMMAX mit 131072 angegeben. Wollen Sie hingegen ein bereits existierendes Segment öffnen, dann kann für size der Wert 0 angegeben werden. Auch bei den Flags gilt dasselbe wie schon bei den Message Queues und den Semaphoren:

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 (ein Shared-Memory-Segment) angelegt wird und nicht ein bereits existierendes mit derselben Kennung. Existiert eine Kennung bereits, bricht die entsprechende Funktion – hier shmget() – mit einem Fehler ab (errno = EEXIST).

Auch hierbei sollten Sie bei den Flags die entsprechenden Zugriffsrechte setzen, damit Sie überhaupt Zugriff auf ein Shared-Memory-Segment haben. Der Rückgabewert der Funktion shmget() ist bei Erfolg die ID des gemeinsamen Speicherbereichs, die Sie bei den weiteren Funktionen benötigen, oder bei einem Fehler -1.


Rheinwerk Computing

9.7.2 Ein Shared-Memory-Segment abfragen, ändern oder löschen – shmctl()  downtop

Mit der Funktion shmctl() können Sie den Status eines Shared-Memory-Segmentes abfragen oder ändern. Aber auch zum Löschen eines Segments wird diese Funktion verwendet. Hier die Syntax:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shm_id, int kommando, struct shmid_ds *buf);

Das erste Argument ist die ID des Shared-Memory-Segmentes, worauf sich die folgende Operation beziehen soll. Diese Kennung haben Sie bei Erfolg als Rückgabewert der Funktion shmget() erhalten. Mit kommando legen Sie die Aktion des durchzuführenden Kommandos fest. Hierbei können folgende Angaben gemacht werden (entsprechende Zugriffsrechte vorausgesetzt):

gp  IPC_STAT – Status des Shared-Memory-Segmentes abfragen. Der Status befindet sich in der Adresse der Struktur shmid_ds (drittes Argument von shmctl), die von jedem Shared-Memory-Segment angelegt wird.
gp  IPC_SET – Setzen der GID/UID und Zugriffsrechte, die sich in der Struktur shmid_ds in der Adresse buf befinden.
gp  IPC_RMID – Löschen eines Shared-Memory-Segments
gp  SHM_LOCK – Sperren eines Shared-Memory-Segments (nur Superuser)
gp  SHM_UNLOCK – Aufheben einer Sperre (logischerweise auch nur Superuser)
gp  IPC_INFO – (nur Linux) Damit können Informationen eines Shared-Memory-Segments erfragt werden.

Hinweis   Das Abfragen und Setzen der einzelnen SysV-Interprozesskommunikationen mit den ...ctl()-Funktionen wurde recht kurz und oberflächlich behandelt – was Ihnen aber, wenn Sie das Prinzip verstanden haben, kein allzu großes Problem bereiten sollte, weil es fast analog zu den bereits vorgestellten IPC-Mechanismen funktioniert. In der Regel läuft es immer nach demselben Schema ab. Als erstes Argument geben Sie die Kennung an, als zweites Argument das Kommando, und das letzte Argument ist meistens die Adresse einer für das SysV-Konzept entsprechenden Struktur, worauf sich das Kommando bezieht.


Bei Erfolg gibt diese Funktion 0, ansonsten bei einem Fehler -1 zurück.


Rheinwerk Computing

9.7.3 Ein Shared-Memory-Segment anbinden (attach) – shmat()  downtop

Im nächsten Schritt müssen Sie das erzeugte Shared-Memory-Segment an einen Prozess anbinden. Dies erledigen Sie mit der Funktion shmat():

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shm_id, const void *adresse, int flag);

Als erstes Argument benötigen Sie wieder die ID des Segmentes, an die Sie den Adressraum anbinden wollen. Mit adresse geben Sie an, an welcher Adresse im Shared-Memory-Segment Sie »sich anbinden« wollen. Es sei hierbei aus Portabilitätsgründen empfohlen, dass Sie das dem Kernel überlassen und hierfür NULL angeben. Sollten Sie die Adresse dennoch selbst angeben wollen, dann sei hierfür die Manual Page empfohlen. Geben Sie als Flag SHM_RDONLY an, wird das Segment nur zum Lesen angebunden, ansonsten zum Lesen und Schreiben.


Hinweis   Nach einem fork()-Aufruf wird das Shared-Memory-Segment auch an den Kindprozess weitervererbt und kann dabei ebenso verwendet werden. Bei einem exec()-Aufruf hingegen weiß der neue Prozess nichts mehr von dem Segment. Und bei einem exit()-Aufruf werden alle angebundenen Segmente vom Prozess losgelöst.


Bei Erfolg gibt die Funktion shmat() die Anfangsadresse des Shared-Memory-Segmentes zurück oder bei einem Fehler -1.


Hinweis   Sofern Sie die Funktion shmat() mit den FreeBSD-Versionen 4.0 bis 5.2 noch vor dem Erscheinungsdatum 04.02.2004 verwenden, haben Sie hierbei eine Sicherheitslücke, die Sie mit einem Patch beheben sollten.

Dabei kann durch einen Fehler in der Verwaltung des Referenzzählers weiterhin auf die Segmente zugegriffen werden, obwohl diese längst gelöst wurden. Ein Angreifer verwendet diese Schwachstelle, um Zugriff auf den Speicher anderer Prozesse zu kommen.



Rheinwerk Computing

9.7.4 Ein Shared-Memory-Segment loslösen – shmdt()  downtop

Wenn ein Shared-Memory-Segment nicht mehr von einem Prozess benötigt wird, sollte man die Anbindung wieder aufheben. Das Loslösen (detach) wird mit der Funktion shmdt() erledigt:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *adresse);

Damit lösen Sie die Adresse des Shared-Memory-Segmentes, die Sie als Rückgabewert der Funktion shmat() erhalten haben. Loslösen ist übrigens nicht mit löschen (IPC_RMID) gleichzusetzen, da das Segment ja noch von anderen Prozessen benutzt werden könnte und somit eine Löschung nicht dem Wunsch des Programmierers entspräche. Bei Erfolg gibt shmdt() 0, ansonsten bei einem Fehler -1 zurück.


Rheinwerk Computing

9.7.5 Client-Server-Beispiel – Shared Memory  toptop

Im Folgenden soll auch hierzu wieder ein einfaches Client-Server-Beispiel vorgestellt werden. Ein Server richtet ein Shared-Memory-Segment ein und wartet auf eine Nachricht von einem Clientprozess, der in dieses Shared-Memory-Segment etwas schreibt, und gibt diese Nachricht aus. Der Clientprozess wird mit der ID des Shared-Memory-Segments gestartet, das der Server erzeugt hat. Um es einfach zu halten, wird eine einfache Zeichenkette dazu verwendet. Zusätzlich verwenden die Prozesse Semaphore zum Sperren eines kritischen Codebereichs – weshalb hierfür eine eigene Headerdatei mit den Funktionen der Semaphoren verwendet wird. In dieser Headerdatei wird des Weiteren die Funktion signal() mit dem neuen Signalkonzept definiert, die zur Behandlung benötigt wird, wenn der Server mit SIGINT unterbrochen wird.

Zuerst die gemeinsame Headerdatei:

/* shm_header.h */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define SHMDATASIZE 1024
#define BUFFERSIZE (SHMDATASIZE - sizeof(int))
#define SN_EMPTY 0
#define SN_FULL  1
/* ---------- Bei BSD-UNIXen auskommentieren ------------ */
#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
/* -------------------------------------------------------- */
static void locksem   (int semid, int semnum);
static void unlocksem (int semid, int semnum);
static void waitzero  (int semid, int semnum);
static int safesemget (key_t key, int nsems, int semflg);
static int safesemctl (int semid, int semnum, 
                       int cmd, union semun arg);
static int safesemop  (int semid, struct sembuf *sops,
                       unsigned nsops);
static int DeleteSemid = 0;
static int DeleteShmid = 0;
static void locksem (int semid, int semnum) {
   struct sembuf sb;
   sb.sem_num = semnum;
   sb.sem_op = -1;
   sb.sem_flg = SEM_UNDO;
   safesemop (semid, &sb, 1);
   return;
}
static void unlocksem (int semid, int semnum) {
   struct sembuf sb;
   sb.sem_num = semnum;
   sb.sem_op = 1;
   sb.sem_flg = SEM_UNDO;
   safesemop (semid, &sb, 1);
}
static void waitzero (int semid, int semnum) {
   struct sembuf sb;
   sb.sem_num = semnum;
   sb.sem_op = 0;
   sb.sem_flg = 0;   
   safesemop (semid, &sb, 1);
   return;
}
static int safesemget (key_t key, int nsems, int semflg) {
   int retval;
   retval = semget (key, nsems, semflg);
   if (retval == -1)
      printf ("Semaphor-Schlüssel %d, nsems %d konnte"
              " nicht erstellt werden", key, nsems);
   return retval;
}
static int 
safesemctl (int semid, int semnum, int cmd, union semun arg) {
   int retval;
   retval = semctl (semid, semnum, cmd, arg);
   if (retval == -1)
      printf ("Fehler: Semaphor mit ID %d, semnum %d, "
              "Kommando %d\n", semid, semnum, cmd);
   return retval;
}
static int
safesemop (int semid, struct sembuf *sops, unsigned nsops) {
   int retval;
   retval = semop (semid, sops, nsops);
   if (retval == -1)
      printf ("Fehler: Semaphor mit ID %d (%d Operation)\n",
               semid, nsops);
   return retval;
}
typedef void (*sighandler_t)(int);
static sighandler_t
my_signal(int sig_nr, sighandler_t signalhandler) {
   struct sigaction neu_sig, alt_sig;
   neu_sig.sa_handler = signalhandler;
   sigemptyset (&neu_sig.sa_mask);
   neu_sig.sa_flags = SA_RESTART;
   if (sigaction (sig_nr, &neu_sig, &alt_sig) < 0)
      return SIG_ERR;
   return alt_sig.sa_handler;
}

Jetzt zur Server-Implementierung:

/* server_shm.c */
#include "shm_header.h"
static void delete (void) {
   int res;
   printf ("\nServer wird beendet - Lösche Semaphor %d.\n",
            DeleteSemid);
   if (semctl (DeleteSemid, 0, IPC_RMID, 0) == -1) {
      printf ("Fehler beim Löschen des Semaphors.\n");
   }
   /* Wenn Serverprozess beendet wird - */
   /* Segment automatisch löschen       */
   res = shmctl (DeleteShmid, IPC_RMID, NULL);
   if (res == -1)
      printf ("Fehler bei shmctl() shmid %d, Kommando %d\n",
          DeleteShmid, IPC_RMID);   
   return;
}
static void sigdelete (int signum) {
   exit(EXIT_FAILURE);
}
static void server (void) {
   union semun sunion;
   int semid, shmid;
   int res;
   void *shmdata;
   char *buffer;
   /* Ein Semaphor erstellen */
   semid = safesemget (IPC_PRIVATE, 2, SHM_R | SHM_W);
   DeleteSemid = semid;
   /* Semaphor beim Beenden entfernen */
   atexit (&delete);
   /* Signalhandler einrichten */
   my_signal (SIGINT, &sigdelete);
   /* Semaphor initialisieren */
   sunion.val = 1;
   safesemctl (semid, SN_EMPTY, SETVAL, sunion);
   sunion.val = 0;
   safesemctl (semid, SN_FULL, SETVAL, sunion);
   /* Ein Shared-Memory-Segment einrichten */
   shmid = shmget (IPC_PRIVATE, SHMDATASIZE,
             IPC_CREAT | SHM_R | SHM_W);
   if (shmid == -1)
      printf ("Fehler bei key %d, mit der Größe %d\n",
               IPC_PRIVATE, SHMDATASIZE);
               
    DeleteShmid = shmid;
   /* Shared-Memory-Segment anbinden */
   shmdata = shmat (shmid, NULL, 0);
   if (shmdata == (void *) -1)
      printf ("Fehler bei shmat(): shmid %d\n", shmid);
   /* Kennung am Anfang des Segments schreiben */
   *(int *) shmdata = semid;
   buffer = shmdata + sizeof (int);
   printf ("Server läuft mit Shared-Memory-ID %d\n", shmid);
   while (1) {
      printf ("Warte ...");
      fflush (stdout);
      /* Sperren und Warten */
      locksem (semid, SN_FULL);
      printf ("\n... fertig.\n");
        /* Ein Client hat etwas geschrieben */
      printf ("Nachricht erhalten: %s\n", buffer);
      /* Sperre aufheben */
      unlocksem (semid, SN_EMPTY);
   }
   return;
}
int main (int argc, char **argv) {
   server ();
   printf("--- Server-Ende ---\n");
   return EXIT_SUCCESS;
}

Als Letztes die Client-Implementierung:

/* client_shm.c */
#include "shm_header.h"
static void clientwrite (int shmid, int semid, char *buffer) {
   printf ("Warte ...");
   fflush (stdout);
   /* Sperre setzen - Client will schreiben */
   locksem (semid, SN_EMPTY);
   printf ("\n... fertig\n");
   printf ("Eingabe machen: ");
   fgets (buffer, BUFFERSIZE, stdin);
   unlocksem (semid, SN_FULL);
   return;
}
static void client (int shmid) {
   int semid;
   void *shmdata;
   char *buffer;
   /* Shared-Memory-Verbindung zu Server */
   shmdata = shmat (shmid, NULL, 0);
   if (shmdata == (void *) -1) {
      printf ("Fehler bei shmat(): shmid %d\n", shmid);
      return;
   }
   semid = *(int *) shmdata;
   buffer = shmdata + sizeof (int);
   printf("Client: Shared-Memory-ID: %d, Semaphor-ID: %d\n",
            shmid, semid);
   while (1) {
      char input[3];
      printf ("\n\nMenu\n1. Eine Nachricht verschicken\n");
      printf ("2. Client-Ende\n");
      fgets (input, sizeof (input), stdin);
      switch (input[0]) {
      case '1':
         clientwrite (shmid, semid, buffer);
         break;
      case '2':
         exit (EXIT_FAILURE);
         break;
      }
   }
   return;
}
int main (int argc, char **argv) {
   if (argc < 2) {
      printf ("Aufruf: %s Shared-Memory-ID\n", *argv);
   }
   else {
      client (atoi (argv[1]));
   }
   return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$ gcc -o server_shm server_shm.c
$ gcc -o client_shm client_shm.c
$./server_shm 
Server läuft mit Shared-Memory-ID 360939526
Warte ...
---[andere Konsole bspw. tty1]---
$./client_shm 360939526
Client: Shared-Memory-ID: 360939526, Semaphor-ID: 12345
Menu
1. Eine Nachricht verschicken
2. Client-Ende
1
Warte ...
... fertig
Eingabe machen: Hallo, das geht durch den gemeinsamen Speicher
Menu
1. Eine Nachricht verschicken
2. Client-Ende
---[beim Server]---
$./server_shm
Server läuft mit Shared-Memory-ID 360939526
Warte ...
... fertig.
Nachricht erhalten: Hallo, das geht durch den gemeinsamen Speicher
Warte ...
---[andere Konsole bspw. tty2]---
$./client_shm 360939526
Client: Shared-Memory-ID: 360939526, SemaphorID: 12345
Menu
1. Eine Nachricht verschicken
2. Client-Ende
1
Warte ...
... fertig
Eingabe machen: Eine weitere Nachricht
Menu
1. Eine Nachricht verschicken
2. Client-Ende
---[beim Server]---
$./server_shm
Server läuft mit Shared-Memory-ID 360939526
Warte ...
... fertig.
Nachricht erhalten: Hallo, das geht durch den gemeinsamen Speicher
Warte ...
... fertig.
Nachricht erhalten: Eine weitere Nachricht
Warte ...
STRG+C
Server wird beendet - Lösche Semaphor 12345
 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
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.


Nutzungsbestimmungen | Datenschutz | Impressum

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

Cookie-Einstellungen ändern