PROGRAMMIEREN MIT UNIX/Linux-SYSTEMAUFRUFEN UNIX/Linux-Interprozesskommunikation, zugehörige Systemaufrufe und Kommandos 7. UNIX/Linux-Shared Memory 8. (Benannte) Pipes
UNIX/Linux-IPC-Mechanismen Nachrichtenbasierter Informationsaustausch: 6. 1. Nachrichten(warte)schlangen ( message queues ) ç System V IPC 8. 2. Sockets ç BSD-UNIX 7. 9. 10. Speicherbasierter Informationsaustausch: 4. Gemeinsame Speicherbereiche ( shared memory ) ç System V IPC 5. Pipes (Named Pipes und FIFOs) ç Version 7 Signale: 6. Asynchrone UNIX-Signale ç Version 7 4. Synchronisationsmechanismen: 7. UNIX-Semaphore ç System V IPC Folien-Nr.: 7/8-2
Speicherbasierter Informationsaustausch Kommunikation über Shared Memory ist eine Form des Speicher basierten Informationsaustausches Prozess 1 Befehle Prozess 2 Befehle Daten Daten gemeinsame Daten Prinzip: Zugriff auf gemeinsame Speicherbereiche (und damit auf gemeinsame Daten) Implementierung (Realisierung): è 2 oder mehr Prozesse haben einen Teil des Datenbereichs gemeinsam Folien-Nr.: 7/8-3
8. UNIX/Linux-Shared Memory Programmieren mit UNIX/LINUX-Systemaufrufen 7. UNIX/Linux-Shared Memory Systemaufrufe: shmget(...) legt neues Speichersegment an oder greift auf existierendes Speichersegment zu shmat(...) hängt Speichersegment an Adressraum des aufrufenden Prozesses an ( shared memory attach ) shmdt(...) entfernt Speichersegment aus dem Adressraum des aufrufenden Prozesses ( shared memory detach ) shmctl(...) führt verschiedene Steuerungsfunktionen aus ( shared memory control ) Folien-Nr.: 7/8-4
Systemaufruf shmget(..) (1) legt neues Speichersegment an oder greift auf existierendes Speicher- Segment zu Verwendung im Programm (Prinzip): int <id_shm>; <id_shm> = shmget(<key>, <size>, <flag>); Konkretes Beispiel: int id; id = shmget(15, 100, IPC_CREATE 0700); Parameter Symbol Typ Bedeutung Wert Interpretation <id_shm> int Rückkehrwert 0 O.K. auf <id_shm>: Identifikator des Speichersegments -1 Feh- Systemaufruf fehlgeschlagen ler <key> long numerischer Eintrag: Schlüssel IPC_PRIVATE à UNIX erzeugt Schlüssel selbst Folien-Nr.: 7/8-5
Systemaufruf shmget(..) (2) legt neues Speichersegment an oder greift auf existierendes Speicher-Segment zu Verwendung im Programm (Prinzip): Konkretes Beispiel: int <id_shm>; <id_shm> = shmget(<key>, <size>, <flag>); Parameter Symbol Typ Bedeutung int id; id = shmget(15, 100, IPC_CREATE 0700); Interpretation <size> int Größe des Anzahl Byte Speichersegments <flag> int Wirkung des Aufrufs Beispieleintrag: IPC_CREATE 0644 à Anlegen neuer Warteschlange mit Zugriffsrechten: rw r - - r - - wichtig: Zugriffsrechte unbedingt spezifizieren Beispieleintrag: 0 (Null) à Zugriff auf vorhandenes Segment Folien-Nr.: 7/8-6
Programmbeispiel 1: shmget.c #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> /* Laenge des Segments = Laenge einer integer-variablen */ #define SHM_SEGSIZE sizeof(int) main() { int id_shm; id_shm = shmget (IPC_PRIVATE, SHM_SEGSIZE, IPC_CREAT 0644 ); printf("id_shm = %d\n", id_shm); } Folien-Nr.: 7/8-7
Systemaufruf shmat(...) ( shared memory attach ) (1) fügt Speichersegment zum Adressraum des aufrufenden Prozesses hinzu Verwendung im Programm (Prinzip): int *<shm_ptr>, <id_shm>; <shm_ptr> = shmat(<id_shm>, <addr>, <flag>); Konkretes Beispiel: int *ptr, id; ptr = shmat(id, 0, 0); Parameter Symbol Typ Bedeutung Wert Interpretation <shm_ptr> *int Rückkehrwert 0 O.K. Pointer auf die Anfangsadresse im Prozess-Adressraum, an die Speichersegment angebunden wurde -1 Feh- Systemaufruf fehlgeschlagen ler <id_shm> int Identifikator identifiziert Speichersegment (= Rückkehrwert des shmget()-aufrufs) Folien-Nr.: 7/8-8
Systemaufruf shmat(...) (2) fügt Speichersegment zum Adressraum des aufrufenden Prozesses hinzu Verwendung im Programm (Prinzip): int *<shm_ptr>, <id_shm>; <shm_ptr> = shmat(<id_shm>, <addr>, <flag>); Konkretes Beispiel: int *ptr, id; ptr = shmat(id, 0, 0); Parameter Symbol Typ Bedeutung Interpretation <addr> int * Adresse Adresse im Adressraum des aufrufenden Prozesses, der Segment zugeordnet wird empfohlene Angabe: 0 (NULL) à UNIX wählt selbst <flag> int Nutzungsart vorgesehene Nutzungsart des Speichersegments: Möglichkeiten: 0 (NULL) à Lesen und Schreiben erlaubt SHM_RDONLY à nur Lesen erlaubt ( read only ) (aufrufender Prozess darf nicht schreiben!) Folien-Nr.: 7/8-9
Programmbeispiel 2: shmat.c #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define SHM_SEGSIZE sizeof(int) main() { int id_shm, *shm_ptr; int pid[num_children ]; id_shm = shmget (2222, SHM_SEGSIZE, IPC_CREAT 0644 ); printf("id_shm = %d\n", id_shm); } shm_ptr = shmat (id_shm, 0, 0 ); printf("shm_ptr = %d\n", shm_ptr); Folien-Nr.: 7/8-10
Systemaufruf shmdt(...) ( shared memory detach ) entfernt Speichersegment aus Adressraum des aufrufenden Prozesses Verwendung im Programm (Prinzip): int <result>, *<shm_ptr>; <result> = shmdt(<shm_ptr>); Konkretes Beispiel: int x, *ptr; x = shmat(ptr); Parameter Symbol Typ Bedeutung Wert Interpretation <result> int Rückkehrwert 0 O.K. Systemaufruf erfolgreich -1 Feh- Systemaufruf fehlgeschlagen ler <shm_ptr> *int Pointer hier zur Identifikation des Speicher- Segments verwendet Folien-Nr.: 7/8-11
Struktur shmid_ds Verwaltungsstruktur für jedes Shared-Memory-Segment struct shmid_ds { struct ipc_perm shm_perm; /* Zugriffsberechtigungen, Eigentümer, Gruppe etc. */ struct anon_map *shm_amp; /* Zeiger auf anon -Tabelle für dieses Segment */ int shm_segsz; /* Größe des Segments in Byte */ unsigned short shm_lkcnt; /* Zähler für Anzahl der Sperren auf dieses Segment */ pid_t shm_lpid; /* Prozess-Nr. für letztes shmop(..) */ pid_t msg_cpid; /* Prozess-Nr. des Erzeugers */ unsigned long shm_nattch; /* Anzahl Prozesse, die dieses Segment verwenden */ unsigned short shm_cnattch; /* gleicher Wert wie auf shm_nattch, nur für shminfo */ }; time_t shm_atime; /* Zeit des letzten shmat(..) */ time_t shm_dtime; /* Zeit des letzten shmdt(..) */ time_t msg_ctime; /* Zeit, zu der diese Struktur zuletzt geändert */ Folien-Nr.: 7/8-12
Struktur ipc_perm ( ipc permissions ) spezifiziert Zugriffsrechte struct ipc_perm { uid_t uid; /* effektive Benutzernummer des Eigentümers */ gid_t gid; /* effektive Gruppennummer des Eigentümers */ uid_t cuid; /* effektive Benutzernummer des Erzeugers */ gid_t cgid; /* effektive Gruppennummer des Erzeugers */ }; mode_t mode; /* Zugriffsmodus */ unsigned long seq; /* Zähler der Verwendung der IPC-Struktur */ key_t key; /* Schlüssel */ Folien-Nr.: 7/8-13
Programmbeispiel 3: shm-ohne-synch.c #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/wait.h> #define MAXCOUNT 100000000 #define NUM_CHILDREN 4 #define SHM_SEGSIZE sizeof(int) main() { int i, id_shm, *shm_ptr, result, count = 0; int pid[num_children ]; id_shm = shmget (0x2222, SHM_SEGSIZE, IPC_CREAT 0644 ); printf("id_shm = %d\n", id_shm); Folien-Nr.: 7/8-14
Programmbeispiel 3: shm-ohne-synch.c shm_ptr = (int *)shmat(id_shm, 0, 0); printf("shm_ptr = %p\n", shm_ptr); *shm_ptr = 0; printf("inhalt des Segments *shm_ptr = %d\n", *shm_ptr); for (i = 0; i < NUM_CHILDREN; i++) { Folien-Nr.: 7/8-15
Programmbeispiel 3: shm-ohne-synch.c } for (i = 0; i < NUM_CHILDREN; i++) { pid[i] = fork(); printf("%d-ter Kindprozess erzeugt\n", i); if (pid[i] == -1) { printf("%d-ter Kindprozess nicht erzeugbar!\n", i); return 0; } if (pid[i] == 0) { while (*shm_ptr < MAXCOUNT ) { *shm_ptr += 1; count++; } printf("kind %d erhöhte Wert im Segment um %d\n", i, count); result = shmdt(shm_ptr); printf("kind %d: result = %d\n", i, result); return 0; } Folien-Nr.: 7/8-16
Programmbeispiel 3: shm-ohne-synch.c for ( i = 0; i < NUM_CHILDREN; i++) waitpid( pid[i], NULL, 0 ); printf("inhalt des Speichersegments am Ende: %d - MAXCOUNT = %d\n", \ *shm_ptr, MAXCOUNT); } Folien-Nr.: 7/8-17
Systemaufruf shmctl(...) ( shared memory control ) führt verschiedene Operationen auf einem Shared-Memory-Segment aus Aufruf im Programmtext: <result> = shmctl(<id_shm>, <cmd>, <*buf>); result (int) >0 à Systemaufruf erfolgreich -1 à Systemaufruf fehlgeschlagen id_shm (int) Identifikator des Speichersegments, siehe shmget(...) cmd (int) Kommando - siehe nächste Folie! *buf (struct shmid_ds) Zeiger auf eine Struktur vom Typ shmid_ds im Nutzeradressraum als Aufnahmebereich für Werte aus der msqid_ds-kernstruktur (vergleiche message queues ) Folien-Nr.: 7/8-18
Systemaufruf shmctl(...) (2) Aufruf im Programmtext: <result> = shmctl(<id_shm>, <cmd>, <*buf>);... cmd (int) Befehl zur Ausführung auf dem über id_shm identifizierten Sared-Memory-Segment Möglichkeiten: IPC_STAT: kopiert shmid_ds -Struktur aus dem UNIX-Kern in Anwenderpuffer, der durch *buf adressiert IPC_SET: kopiert aus Nutzerstruktur in Kernstruktur IPC_RMID: löscht Speichersegment SHM_LOCK: Shared-Memory-Segment im Arbeitsspeicher sperren (nur Superuser!) SHM_UNLOCK: Speichersegment entsperren (nur Superuser!) Folien-Nr.: 7/8-19
7. UNIX/Linux-Pipes Programmieren mit UNIX/LINUX-Systemaufrufen 8. Pipes sind älteste IPC-Form unter UNIX werden von allen UNIX-Systemen unterstützt und stellen noch immer gebräuchlichste Form der UNIX-IPC dar Einschränkungen: normale Pipes besitzen 2 Einschränkungen: 1. Daten fließen nur in eine Richtung ( Halbduplex-Pipes ) 2. Sie können nur zwischen Prozessen mit 1 gemeinsamen Vorfahren eingerichtet werden. (Normalerweise wird Pipe von Prozess erzeugt, der anschließend fork() aufruft [Elternprozess]. Pipe wird dann zur Kommunikation zwischen Eltern- und Kindprozess benutzt oder zur Kommunikation zwischen weiteren Prozessen der Familie. D.h. normale Pipes werden vererbt.) weitere Pipe-Typen: heute gibt es weitere Typen von Pipes, z.t. ohne die oben genannten Einschränkungen (Datenstrom-Pipes, FIFOs, benannte Datenstrompipes) Folien-Nr.: 7/8-20
Modellmäßige Vorstellung Pipe ( Rohr oder Röhre ) P1 schreibt <id_file>[1] P2 liest <id_file>[0] Folien-Nr.: 7/8-21
Benannte Pipes ( named pipes, FIFOs)... werden erzeugt durch das UNIX-Kommando mknod <pipe_name> p (auch: mkfifo) Mit benannten Pipes können auch Programme ohne Verwandtschaftsverhältnis gekoppelt werden, d.h. miteinander kommunizieren. Diese Pipes werden ebenfalls im Dateisystem angelegt, und gegenüber normalen Dateien durch ein vorangestelltes p gekennzeichnet. (mit Kommando ls al überprüfen) Benannte Pipes sind sinngemäß wie gewöhnliche Dateien zu behandeln, d.h. die Systemaufrufe open( ), close( ), read( ), write( ),... sind zu verwenden. Folien-Nr.: 7/8-22
Programmbeispiel : benpipe.c #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> char buffer[ ] = "TEST FUER NAMED PIPE"; main() { int id_pipe; int n; int rw; Folien-Nr.: 7/8-23
Programmbeispiel : benpipe.c printf("\nkurztest: *named pipe*\n\n"); // Anlegen einer named pipe (FIFO) rw = mkfifo( "p1", 0666 ); if (rw < 0) printf ("Fehler bei Pipe-Erzeugung\n"); id_pipe = open( "p1", 2 ); n = write( id_pipe, buffer, 20 ); printf("kontrolle: Anzahl der geschriebenen Bytes: %d\n\n", n); } Folien-Nr.: 7/8-24
Verwendung von Pipes zur Programmkopplung In UNIX-Eingabe-Kommandos kann die Einrichtung von Pipes zwischen Prozessen durch das Symbol spezifiziert werden. Das Kommando <programm_1> <programm_2>... <programm_n> bewirkt das Einrichten einer Pipe jeweils zwischen zwei durch gekoppelten Programmen (besser: den Prozessen, die diese Programme ausführen). hierbei werden die Ausgabedaten des vorderen Programms zu Eingabedaten des hinteren. Verfahrensweise gültig für System- und Nutzerprogramme Folien-Nr.: 7/8-25
Aufgabenstellung shared memory (Anregungen) 1. Erzeugen Sie mit shmget(..) Programmbeispiel: shmget.c ein neues Speichersegment, und untersuchen Sie mit ipcs (und geeigneten Parametern) wie die zugehörige Verwaltungsstruktur shmid_ds initialisiert wird! 2. Untersuchen Sie die Wirkung unterschiedlicher Parameterwerte für shmget(..)! Was passiert, wenn keine Zugriffsrechte angegeben sind was wenn nur Zugriffsrechte spezifiziert sind? 3. Verwenden Sie shmat(..) Programmbeispiel: shmat.c um das erzeugte Speichersegment an ihren Prozess anzufügen! Studieren Sie wieder mit ipcs (und geeigneten Parametern) die Auswirkungen auf die Verwaltungsstruktur shmid_ds! Experimentieren Sie ebenfalls mit unterschiedlichen Parameterwerten! Folien-Nr.: 7/8-26
Aufgabenstellung shared memory (Anregungen) 4. Beschäftigen Sie sich mit dem Programm shm-ohne-synch.c! Verschaffen Sie sich Klarheit darüber, was bei der Ausführung passieren wird! Führen Sie dann das Programm mehrfach aus und vergleichen Sie die Resultate! Interpretieren Sie! 5. Löschen Sie alle von Ihnen erzeugten IPC-Strukturen (mit ipcrm bzw. shmctl(..) )! Folien-Nr.: 7/8-27
Aufgabenstellung pipes (Anregungen) 1. Studieren und verwenden Sie das Programm benpipe.c! 2. Kontrollieren Sie, ob eine benannte Pipe erzeugt wurde! (z.b. mit ls l) Was ist mit den geschriebenen Bytes? 3. Öffnen Sie jetzt ein weiteres Kommandofenster für den Pfad, wo das Pipe erzeugt wurde. Versuchen Sie mit cat den Inhalt des erzeugten Pipe auszugeben. Falls das cat-kommando nicht abgeschlossen wird, starten Sie nochmals das übersetzte Programm benpipe (ohne vorher das Pipe vom vorigen Durchlauf zu löschen)! Erklären Sie! Folien-Nr.: 7/8-28
Kommando ipcs (vollständig) Benutzung: ipcs [-asmq] [-tclup] ipcs [-smq] -i <id> à siehe unten à Ausg. nur für durch Identifizierer <id> spez. Ressource ipcs -h à -h help (Infos über Kommandobenutzung) -m shared memory segments - q message queues - s semaphore arrays - a all (default, d.h. gleiche Ausgabe auch ohne Angabe von -a ) -t time (alle Zeitangaben) - c creator (Erzeuger und Eigentümer der Strukturen) - p (nicht für Semaphore) - u summary (zusammenfassende Informationen) - l limits (Maximalwerte für diese UNIX-Implementation) Folien-Nr.: 7/8-29
Kommando ipcrm (vollständig) Benutzung: ipcrm [-M <key> -m <id> - Q <key> -q <id> -S <key> -s <id>] à veraltete Form auch: ipcrm [ shm msg sem ] <id>... -M, -m shared memory segments - Q, -q message queues - S, -s semaphore arrays Folien-Nr.: 7/8-30