ein Kapitel zurück                                           ein Kapitel weiter

Um es gleich vorwegzunehmen, die nächsten 3 Kaptitel wurden nur zur Ergänzung hinzugefügt. Daher fällt der Umfang wesentlich bescheidener aus und konzentriert sich nur auf das Wesentliche.

Die System V-IPC sind IPC von Unix-Zeiten, die von POSIX-Kompatiblen Systemen wie Linux und FreeBSD mittlerweile heute auch unterstützt werden. Dabei handlet es sich um Semaphore, Message Queues und Shared Memory. Da diese IPC, bis auf Shared Memory, eher selten verwendet werden hält sich der Inhalt dieser IPC etwas bescheidener.

Mit den Semaphoren haben wir eine weitere Möglichkeit Prozesse zu synchronisieren. Dies wurde bei Einführung von Semaphoren recht praktisch und einfach gelöst. Man nehme eine nichtnegative Zahl. Dies Zahl zeigt jetzt in Verbindung mit den Semaphoren das momentan kein kritische Codeabschnitt zu schützen ist. Nun kommen wir in einen Kritischen Codeabschnitt. Also dekremtieren wir die Zahl und das soll zeigen das wir in einem Kritischen Codeabschnitt sind. Der Wert der Zahl hat jetzt 0 und für alle anderen Prozessen heißt es nun warten da sie das Signal für einen kritischen Codebereich gelesen haben. Sie (die Prozesse) warten also bis die Zahl wieder inkrementiert wird.

Damit das ganze auch funktioniert benötigen wir einige Systemcalls die uns das Betriebsystem auch wieder bereitstellt.

Zuerst die Funktion um eine Sempaphore zu Öffnen bzw. eine neue zu Erzeugen............

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int flag);  

Wenn wir eine Semaphore einrichten müssen wir einen sogenannten Schlüssel angeben. Dieser Schlüssel hilft uns z.B. wenn ein anderer Prozess die Kennung - ID ,die die Funktion semget bei Erfolg zurückliefert , nicht kennt trotzdem auf das Objekt zugreifen mit Hilfe des Schlüssels key. Also alle Prozesse die den Schlüssel key kennen können auf auf das Objekt zugreifen das wir mit shmget erzeugen. Folgende Möglichkeiten haben wir einen Schlüssel zu erzeugen.......

  • Mit der Konstante IPC_PRIVATE. Das bedeutet das diesem Objekt kein Schlüssel zugeordnet ist.

  • Wir geben selbst einen Wert eines noch nicht existierenden Schlüssels an. Wichtig ist dabei das sie als flag IPC_CREAT und IPC_EXCL setzen. Denn falls das Objekt schon existiert gibt diese Funktion errno=EEXIST zurück.
  • Wir erzeugen einen Schlüssel mit der Funktion....

    key_t ftok(char *pfadname, char projektbezeichner);  

    Diese Funktion wandelt den Pfadnamen einer existierenden Datei zusammen mit dem Projektbezeichner in einem IPC-Schlüssel vom Typ key_t um. (Mehr dazu unter man ftok)

Wollen wir eine Verbindung mit einem bereits existierenden Objekt aufnehmen müssen wir den gleichen Schlüssel verwenden und dürfen als flag auf keinen Fall IPC_CREAT setzen.

Wenn z.B. mehrere verschieden Prozesse auf ein Objekt zugreifen müssen haben sie folgende Möglichkeiten den Schlüssel für diese Prozesse bekannt zu machen..........

  • Sie Speichern diesen in einer Datei
  • Alle Prozesse benutzen die selbe Headerdatei in dem sich dieser Schlüssel befindet.......



#ifndef _KEY_H_
#define _KEY_H_

#define UNIVERSAL_KEY 123456

#endif  

Mit nsems geben sie die Anzahl der Semaphore in der Menge an. Wenn sie eine Sempaphore öffnen und nicht erstellen gilt hier den Wert 0 anzugeben.

Um eine Semaphore nach Ihrem Status zu Fragen oder den Status zu ändern oder aber zu Löschen haben wir die Funktion.....

int semctl(int kennungs_id, int semnum, int kommando, union semun arg);  

Die kennungs_id haben sie als Rückgabewert der Funktion semget erhalten und legt fest auf welche Semaphoremenge wir semctl anwenden. Mit semnum spezifizieren wir einen bestimmten Semaphorewert aus der Menge. Auf diesen Semaphorewert gilt dann auch eins der folgenden Kommandos was beim gleichnamigen Parameter einsetzten.......

  • IPC_STAT - Abfagen der Struktur semid_ds die in sich dieses mal in der union semun befindet..............

    union semun{
       int val;                            /*für kommando=SETVAL*/
       struct semid_ds * buf; /*für kommandoIPC_STAT und IPC_SET*/
       ushort *arrays;              /*für kommando GETALL und SETALL*/
    }  

  • IPC_SET - Setzen der Zugriffrechte
  • IPC_RMID - Löschen der Semaphorenmenge
  • GETVAL - Abfragen des Wertes einer Semaphorenmenge. (siehe union semun -> var) Wert steht dann im 2. Parameter der Funktion semctl -> semnum
  • SETVAL - Setzen des Wertes einer Semaphorenmenge (siehe union semun -> var) Gesetzt wird der Wert den sie als semnum (2.Parameter von semctl) angeben.
  • GETPID - Abfragen welcher PID zuletzt auf semnum zugegriffen hat.
  • GETNCNT - Gibt zurück : Prozesse die Warten bis Wert der Semaphorevariable größer als 0 sind .
  • GETZCNT - Gibt zurück : Prozesse die Warten bis Wert der Semaphorenvariable gleich 0 sind.
  • GETALL - Abfragen aller Werte der Semaphorenvariablen.
  • SETALL - Setzen aller Werte der Semaphorenvariablen.

Genaueres entnehmen sie dazu aus der man-Page von semctl.

Jetzt noch die Funktion womit wir Operationen auf unsere Semaphorenmenge durchführen können..........

int semop(int semid, struct sembuf semoparray[], size_t nsops);  

Der Variablen semid geben sie die Semaphormenge an auf der sie semop anwenden wollen. struct sembuf semoparry ist eine Adresse eine Arrays für die Semaphorenoperationen.......

struct sembuf{
   ushort sem_num;  /*Nummer der Semaphorvariablen*/
    short sem_op;      /*Operation*/
   short sem_flag;     /*IPC_NOWAIT, SEM_UNDO*/
}  

Mit nsops geben sie Anzahl der Operationen an die sie in semoparry angegeben haben.

Folgende 3 Möglichkeit haben sie für sem_op in der Struktur sembuf......

  • sem_op > 0
    Dekremtieren einer Semaphorenvariablen (freigeben)
  • sem_op < 0
    Setzen einer Semaphorvariablen (anfordern)
  • sem_op==0
    Warten bis Semaphorvariable gleich 0 ist

SEMUNDO
Dieses Flag ist zu setzen damit sich bei Beendigung eines Prozesses sicherzustellen das die gesetzten Semaphoren wieder zurückgesetzt werden. Dies Flag können sie in struct sembuf setzen. Mit der Funktion semctl können sie den undo-Zähler (SETVAL oder SETALL) wieder auf 0 setzen.

Nun wollen wir uns mal ein paar Beispiele ansehen. Den das Thema Semaphore ist recht komplex und was bringt die beste Theorie wenn man es nicht in der Praxis einsetzen kann.

/*Download:sem1.c*/
#include <stdio.h> #include <sys/types.h> #include <sys/sem.h> #include <sys/ipc.h> /*Musste ich einbauen da bei mir keine union semun vorhanden war*/ #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) /*union semun ist in <sys/sem.h> definiert*/ #else /*Wir müssen es selbst definieren (siehe man-Page semctl)*/ union semun{ int val; struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; } ; #endif int main() { key_t key; int sem_id; struct sembuf lock_it; union semun options; int i; key = ftok(".", 'a'); /*Wir erzeugen eine neue Semaphore*/ sem_id = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666); if(sem_id == -1) { perror("semget"); exit(0); } printf("Semaphore id=%d\n", sem_id); options.val = 1; /*Wir setzen option.val in union semun auf 1*/ i=semctl(sem_id, 0, SETVAL, options); if(i == -1) { perror("semctl"); exit(0); } /*Wir fragen ab ob der Wert der Semaphorvariable ungleich 0 ist*/ /*Wir haben ihn ja gerade zuvor auf 1 gesetzt*/ if (semctl(sem_id, 0, GETVAL, 0) == 0) { printf("Der Wert der Semaphorvariable darf nicht 0 sein........\n"); exit(1); } i = semctl(sem_id, 0, GETVAL, 0); printf("Der Wert der Semaphorvariable des Indexes 0 ist %d (1=unlock,0=lock)\n", i); /*Jetzt setzen wir die Semaphorevariable auf 0 für lock*/ lock_it.sem_num = 0; /*Nummer der Semaphorvariable in der Menge*/ /*Operation = -1 Damit wird die Semaphorvariable val subtrahiert*/ lock_it.sem_op = -1; lock_it.sem_flg = IPC_NOWAIT; /*Prozess wird nicht suspendiert*/ /*Jetzt wollen wir den Prozess sperren*/ if (semop(sem_id, &lock_it, 1) == -1) { printf("Kann die Semaphore nicht sperren\n"); exit(1); } /*Testen wir wieder die Semaphorevariable müsste 0 sein*/ i = semctl(sem_id, 0, GETVAL, 0); printf("Der Wert der Semaphorvariable des Indexes 0 ist %d (1=unlock,0=lock)\n", i); /*Jetzt wollen wir den Prozess wieder freigeben*/ lock_it.sem_num = 0; /*Nummer der Semaphorvariable in der Menge*/ /*Wir dekremtieren die Semaphorvariable wieder um den Wert 1*/ lock_it.sem_op = 1; lock_it.sem_flg = IPC_NOWAIT;/*Wieder sofort ausführen es wird nicht gewartet*/ /*Jetzt wollen wir wieder unlocken*/ if ((i=semop(sem_id, &lock_it, 1)) == -1) { printf("Kann die Sperre der Semaphore nicht mehr aufheben......\n"); exit(1); } /*Müsste wieder 1 sein*/ i = semctl(sem_id, 0, GETVAL, 0); printf("Der Wert der Semaphorvariable des Indexes 0 ist %d (1=unlock,0=lock)\n", i); /*Wir löschen den Semaphor*/ i=semctl(sem_id, 0, IPC_RMID, 0); if(i == -1) perror("semct...IPC_RMID"); return 0; }

Folgende Schritte durchläuft unser Programm. Zuerst erzeuge wir einen Schlüssel. Dann erzeugen wir eine Semaphore und überprüfen auf Fehler. Wir setzen die Semaphorvariable auf 1 und geben dies auf dem Bildschirm aus. Anschließend dekrementieren wir die Semaphorvariable und geben wiederum den Wert auf dem Bildschirm aus (0). Zu guter letzt inkrementieren wir die Semaphorvariable wieder und geben dies wiederum auf dem Bildschirm aus. Zum Schluss löschen wir die Semaphore wieder. Jetzt haben sie gesehen wie man mit Semaphore einsetzen kann.

Nun wollen wir die Sempaphore man in Action einsetzen. Wir kreiern 3 Prozesse mit fork und schreiben jeweils 4x pro Prozess in die selbe Datei. Um zu verhindern das ein Prozess nicht in die Datei schreiben kann während gerade ein anderer Prozess in diesem schreibt, benutzen wir Semaphore zum synchronisieren.......

/*Download:sem2.c*/
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/wait.h> /*Musste ich einbauen da bei mir keine union semun vorhanden war*/ #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) /*union semun ist in <sys/sem.h> definiert*/ #else /*Wir müssen es selbst definieren (siehe man-Page semctl)*/ union semun{ int val; struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; } ; #endif #define NUM_PROCS 3 /* Anzahl der Prozesse die wir ausführen */ #define SEM_ID 250 /* kennungs-id für Semaphore */ #define FILE_NAME "PID-Buchfuehrung" /* Name der Datei in wir schreiben */ void update_file(int sem_set_id, char* file_path, int number) { struct sembuf sem_op; FILE* file; /*Wir sperren jetzt den Berreich*/ sem_op.sem_num = 0; /*Nummer der Semaphorvariable in der Menge*/ sem_op.sem_op = -1; /*Wir subtrahieren den Wert der Semaphorvariable*/ sem_op.sem_flg = 0; semop(sem_set_id, &sem_op, 1); /*Wir führen die Operation aus*/ printf("Kritischer Berreich : lock\n"); /*Dieser Berreich ist nun gesperrt für andere Prozesse*/ file = fopen(file_path, "a+"); if (file) { fprintf(file, "%d\n", number); printf("%d\n", number); fclose(file); } sleep(1); /*Wir trödeln ein bisschen*/ sem_op.sem_num = 0; /*Nummer der Semaphorvariable in der Menge*/ sem_op.sem_op = 1; /*Wir addieren den Wert der Semaphorvariable wieder*/ sem_op.sem_flg = 0; semop(sem_set_id, &sem_op, 1); /*Wir führen die Operation aus*/ /*Jetzt müsste die Funktion zum öffnen und schreiben in die Datei */ /*file wieder für einen anderen Prozess zur Verfügung stehen*/ printf("Fertig : unlock\n"); } /*Diese Funktion wird von jedem Prozess einmal aufgerufen. Diese ruft wiederum*/ /*die Funktion update_file je 3x pro Prozesse auf. Lässt dabei aber ein wenig*/ /*Zeit verstreichen um die Prozesse "durchzumischen" */ void do_child_loop(int sem_set_id, char* file_name) { pid_t pid = getpid(); int i, j; for (i=0; i<=3; i++) { update_file(sem_set_id, file_name, pid); sleep(1); } } int main() { int sem_set_id; /* kennungs_id*/ union semun sem_val; int child_pid; /* PID von Kindprozess */ int i, rc; /*Wir erstellen ein Semaphore mit mit dem Schlüssel 250 und Zugriffrechtefür den Eigentümer*/ sem_set_id = semget(SEM_ID, 1, IPC_CREAT | 0600); if (sem_set_id == -1) { perror("semget"); exit(1); } /* Wir initialisieren die Semaphorvariable mit dem Wert 1*/ sem_val.val = 1; rc = semctl(sem_set_id, 0, SETVAL, sem_val); if (rc == -1) { perror("main: semctl"); exit(1); } /* Wir erzeugen NUM_PROCS Prozesse*/ for (i=0; i<=NUM_PROCS; i++) { child_pid = fork(); switch(child_pid) { case -1: perror("fork"); exit(1); case 0: do_child_loop(sem_set_id, FILE_NAME); /*Kindprozess*/ exit(0); default: break; /*Elternprozess*/ } }/*Ende for*/ /* Elternprozess wartet bis alle Kindprozesse fertig sind*/ for (i=0; i<NUM_PROCS; i++) { int child_status; wait(&child_status); } printf("Alle Prozesse sind beendet\n"); fflush(stdout); return 0; }

Ein Tipp nach am Rande. Anstatt zu schreiben.........

sem_op.sem_num = 0;
sem_op.sem_op = 1;
sem_op.sem_flg = 0;
semop(sem_set_id, &sem_op, 1);  

...können sie die Struktur sembuf auch direkt initialisieren.....

struct sembuf sem_op[1] = { 0, 1, 0};  

Hier schnell ein Programmbeispiel was dies demonstriert......

/*Download:sem3.c*/
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <unistd.h> #include <stdio.h> #define KEY ((key_t) 11111) #define COUNT 20 #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) /*union semun ist in <sys/sem.h> definiert*/ #else /*Wir müssen es selbst definieren (siehe man-Page semctl)*/ union semun{ int val; struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; } ; #endif struct sembuf unlock[1] = { 0, 1, 0}; struct sembuf lock[1] = { 0, -1, 0}; int main() { int i, semid; union semun sem_val; if ( (semid = semget(KEY, 1, 0666 | IPC_CREAT)) < 0) exit(0); sem_val.val = 1; semctl(semid, 0, SETVAL, sem_val); for (i = 0; i < COUNT; i++) { sleep(1); if (semop(semid, &lock[0], 1) < 0) exit(0); printf("1"); fflush(stdout); sleep(1); if (semop(semid, &unlock[0], 1) < 0) exit(0); printf("0"); fflush(stdout); } printf("\n"); if (semctl(semid, 0, IPC_RMID, (struct semid_ds *) 0) < 0) exit(0); exit(0); }

Die Ausgabe '1' soll heißen Sperre ein und '0' Sperre aus. fflush verwenden wir hier da printf ja Zeilengepuffert ist.

ein Kapitel zurück          nach oben           ein Kapitel weiter


© 2001,2002 Jürgen Wolf