9.7 Shared Memory
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.
9.7.1 Ein Shared-Memory-Segment erstellen oder öffnen – shmget()
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:
|
IPC_PRIVATE – Öffnen eines privaten Schlüssels |
|
IPC_CREAT – Damit wird ein noch nicht existierender Schlüssel erzeugt. |
|
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.
9.7.2 Ein Shared-Memory-Segment abfragen, ändern oder löschen – shmctl()
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):
|
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. |
|
IPC_SET – Setzen der GID/UID und Zugriffsrechte, die sich in der Struktur shmid_ds in der Adresse buf befinden. |
|
IPC_RMID – Löschen eines Shared-Memory-Segments |
|
SHM_LOCK – Sperren eines Shared-Memory-Segments (nur Superuser) |
|
SHM_UNLOCK – Aufheben einer Sperre (logischerweise auch nur Superuser) |
|
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.
9.7.3 Ein Shared-Memory-Segment anbinden (attach) – shmat()
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.
|
9.7.4 Ein Shared-Memory-Segment loslösen – shmdt()
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.
9.7.5 Client-Server-Beispiel – Shared Memory
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
|