Teil 2
Programmtechnisch ist jedes Semaphor durch eine Variable vom Typ 'struct sem' repräsentiert. Der Wert der Komponente semval ist der Wert des Semaphors.
Wir unterscheiden hier zwei Operationen mit Semaphoren:
Ein Betriebsmittel sperren (exclusiven Zugriff erlangen) bedeutet, den Wert des Semaphors auf 0 zu setzen.
Ein Betriebsmittel freigeben, indem das Semaphor zurück- gesetzt wird. Der Wert semval wird dabei von 0 auf einen positiven Wert gesetzt.
Diese Operationen werden zu Beginn und am Ende der " kritischen Region " ausgeführt. Aus der nachfolgenden Definition der Funktion semop() kann man erkennen, daß semop() mit drei Parametern aufgerufen wird:
dem internen Namen des Semaphors (Returnwert der Funktion semget()),
einer Datenstruktur ( struct sembuf ) zur Manipulation des Semaphors,
der Anzahl der zu manipulierenden Semaphore.
Die Funktion semop() erlaubt oder sperrt den Zugriff
int semop(sema_id, operand, flag)
int sema_id;
struct sembuf *operand;
unsigned flag;
sema_id programminterner Name des Semaphors *operand bezeichnet einen Pointer auf ein Array flag gibt die Anzahl der Semaphore im Satz an
Rueckgabewert:
Fehler -1
sonst 0
struct sembuf
{
short sem_num; /* Nummer des Semaphors im Satz */
int sem_op; /* Operator */
ushort sem_flg; /* Flag */
}
Anmerkung zu DEC Ultrix:
Unter Ultrix ist ist der zweite Parameter nicht (struct sembuf*), sondern (struct sembuf**). Das ist ein Fehler im entsprechenden Header, denn zu übergeben ist (struct sembuf*). Außerdem ist fehlt in den Ultrix Headern die Union semun. Wer sein Programm noch unter anderen Unix-Derivaten (z. B. Linux oder Solaris) übersetzen möchte, wird u. a. auf diese Probleme stoßen. Der folgende Programmauszug kann helfen, diese zu umgehen:
#ifdef __DECCXX
union semun
{
int val;
struct semid_ds *buf;
ushort *array;
};
#define CAST_FOR_DEC (sembuf**)
#else
#define CAST_FOR_DEC
#endif /* __DECCXX */
...
void foo()
{
sembuf op;
...
semop(semID, CAST_FOR_DEC &op, 1);
...
}
__DECCXX is ein Präprozessormakro, das vom C++ von DEC (cxx) vorgegeben ist. Wenn der Präprozessor feststellt, daß diese Makro definiert ist, werden alle Quellcodezeilen und Präprozessoranweisungen übernommen bzw. ausgeführt.
Über die Wahl der Werte des zweiten Parameters kann der Programmierer die Arbeitsweise der Funktion semop() steuern. Der Parameter ist eine Variable vom Typ "Pointer auf Array von struct sembuf Komponenten".
Die Variable enthält drei Komponenten:
sem_num gibt an, welches Semaphor aus dem ausgewählten Satz benutzt werden soll,
sem_op steuert, ob das Semaphor gesetzt oder zurückgesetzt werden soll. sem_op ist eine Zahl, deren Wert auf den Wert des Semaphors addiert wird.
sem_flg gibt an, wie der Prozeß reagiert, wenn das betreffende Semaphor bereits gesetzt ist:
0 --> der Prozeß wartet bis das Semaphor zurückgesetzt wird,
IPC_NOWAIT --> der Aufruf von semop() wird mit einem Fehler abgebrochen und das Programm läuft weiter.
Soll z. B. ein Semaphor mit dem Wert 1 auf 0 gesetzt werden, so muß sem_op den Wert -1 haben. Soll das Semaphor am Ende der kritischen Region zurückgesetzt werden, muß sem_op gleich 1 sein.
So ergeben sich einige Aktivitäten, die in einem Programm durchgeführt werden müssen, bevor die Funktion semop() genutzt werden kann.
struct sembuf feld[1]; /* Array definiert */
struct sembuf *pointer_feld; /* Pointer definiert */
pointer_feld = feld; /* Pointer setzen */
In diesem Beispiel besteht der Semaphorsatz nur aus einem Semaphor und daher besteht das Array feld aus nur einer Komponte vom Typ struct sembuf. Im nächsten Schritt werden die Komponenten der Struktur belegt. Danach kann die Funktion semop() ausgeführt werden.
pointer_feld->sem_num = 0;
pointer_feld->sem_op = -1;
pointer_feld->sem_flg = 0;
return_value = semop(sem_id, pointer_feld, 1);
Wird die Funktion semop() mit obigen Werten aufgerufen, passiert folgendes : semop() prüft, ob der Wert des ersten Semaphors ( Nummer 0 ) größer oder gleich dem Absolutbetrag des Operators von pointer_feld -> sem_op ( hier Wert des Semaphors >= 1 ) ist. Wenn dies erfüllt ist, wird vom Wert des Semaphors der Wert von sem_op subtrahiert, das Semaphor damit auf 0 gesetzt. Wird die Funktion semop() mit sem_op = -1 aufgerufen und ist das Semaphor gesetzt, so wartet der Prozeß solange, bis der sperrende Prozeß das Semaphor freigibt. Alle Prozesse, die auf die Freigabe warten, konkurrieren nun um das Betriebsmittel. Wird dagegen die Komponente sem_flg der Variablen feld auf den Wert IPC_NOWAIT gesetzt ( pointer_feld -> sem_flg = IPC_NOWAIT; ), so wird der Aufruf der Funktion semop() mit einem Fehler abgebrochen und das Programm läuft weiter.
Allgemein gilt:
Ist sem_op < 0, so soll das Semaphor gesperrt werden. Ist der Wert des Semaphors größer oder gleich dem absoluten Wert von sem_op, so gilt das Semaphor als frei. Der Absolutwert von sem_op wird dann vom Wert des Semaphors abgezogen. Ist der Wert des Semaphors aber kleiner als der Absolutbetrag, so gilt das Semaphor als gesperrt und semop() wird solange suspendiert, bis die Ressource wieder freigegeben ist. Ist aber als Flag IPC_NOWAIT gesetzt, so beendet sich die Funktion mit einem Fehler als Returnwert und das Programm kann weiterarbeiten.
Befindet sich der Prozeß in der kritischen Region kann, er auf das Betriebsmittel zugreifen. Diese Phase ist, wie bereits erwähnt, möglichst kurz zu halten. Am Ende seiner Arbeit, innerhalb der kritischen Region, muß der Prozeß das entsrechende Betriebsmittel wieder freigeben. Dazu wird die Funktion semop() erneut aufgerufen. Als Operator wird nun eine 1 übergeben. Diese 1 wird auf den Wert des Semaphors addiert. Das Semaphor hat nun wieder den Wert 1 und andere Prozesse können nun um das betreffende Betriebsmittel konkurrieren.
pointer_feld->sem_op = 1;
return_value = semop(sem_id, pointer_feld, 1);
Allgemein wird mit sem_op > 0 das Semaphor freigegeben. Dazu wird der Wert von sem_op zum Wert des Semaphors addiert.
Das Kontrollieren und Steuern der Semaphore mit der Funktion semctl()
Ein Semaphor wird durch die Datenstrukturen struct sem und struct semid_ds beschrieben. Die Werte dieser Variablen bilden den Status des Semaphors.
Mit der Funktion semctl() kann der Status eines Semaphors kontrolliert werden.
int semctl(sema_id, nummer, command, puffer)
int sema_id;
int nummer;
int command;
union semun
{
int val;
struct semid_ds *buf;
ushort array;
} puffer;
sema_id ist der programminterne Name des Semaphors und wird von semget() als Rueckgabewert geliefert. nummer ist die Nummer des Semaphors im Satz. command ist ein Kontrollkommando und kann einen der folgenden Werte annehmen:
SETVAL setzt Wert des Semaphors
GETVAL liefert Wert des Semaphors
GETPID liefert PID des letzten Prozesses, der auf das Semaphor zugriff.
GETNCNT liefert Anzahl der Prozesse
GETZCNT liefert Anzahl der Prozesse, die auf den Wert 0 warten.
GETALL liefert Werte aller Semaphore im Satz.
SETALL setzt Werte aller Semaphore im Satz.
IPC_STAT liefert Status eines Semaphors
IPC_SET setzt Status eines Semaphors
IPC_RMID löscht Semaphor
puffer ist eine Datenfläche (Variable)
Rückgabewert:
>= 0 : bei Erfolg, abhängig von command:
GETVAL -> Wert von semval;
GETPID -> Wert von sempid;
GETNCNT -> Wert von semncnt;
GETZCNT -> Wert von semzcnt;
-1 : Fehler,
-> ungültige Semaphor-ID;
-> command ungültig;
-> Zugriffsrechte unzureichend;
Die Funktion semctl() wird mit vier Parameter aufgerufen. Der erste Parameter identifiziert das Semaphor. Der zweite Parameter gibt die Nummer des zu bearbeitenden Semaphors an.Das erste Semaphor in einem Satz hat die Nummer 0. Der dritte Parameter enthält ein Kontrollkommando, das die Funktion und Wirkungsweise der Funktion semctl() steuert.
Es gibt zwei Klassen von Kommandos (Operationen):
Status des Semaphors abfragen ( lesen ). Werte des Semaphors werden abgefragt und im vierten Parameter gespeichert oder als Returnwert an das Programm geliefert.
Status der Semaphore ändern ( schreiben ). Werte aus dem vierten Parameter werden gelesen und in die Variable des Semaphors geschrieben.
Der vierte Parameter ist als Variable vom Typ 'union' definiert. Abhängig vom Operator im dritten Parameter wird diese Variable als Integer, Pointer oder Array interpretiert.
Folgende Operationen sind möglich:
Werte verwalten
Der Wert des Semaphors wird mit SETVAL gesetzt. Der Wert wird aus dem vierten Parameter gelesen und wird als Integer inter- pretiert. Mit GETVAL wird der Wert des Semaphors gelesen und als Return- wert von semctl() zurückgeliefert.
Beispiel:
Eine Variable puffer wird definiert und auf den Wert 3 gesetzt. Die Funktion semctl() wird mit dem Operator SETVAL aufgerufen und setzt das Semaphor sema_id auf den Wert 3 ( sema_id ist der Returnwert eines semget()-Aufrufes ).
union semun puffer; /* Definition der Variablen puffer */
puffer.val = 3; /* Speicherfläche wird als */
/* Integer interpretiert */
semctl(sema_id, 0, SETVAL, puffer);
Status abfragen
Hierzu dienen die Kommandos IPC_STAT und IPC_SET. Der vierte Parameter wird als Pointer auf eine Variable vom Datentyp semid_ds interpretiert. Die aktuellen Werte des Semaphors werden in die Variablen geschrieben (IPC_STAT) oder durch die Werte der Variablen ersetzt (IPC_SET).
Achtung: Diese Variable muß definiert werden, der Pointer allein reicht nicht! Sind mehrere Semaphore in einem Satz zusammengefaßt, so wird der vierte Parameter als Array interpretiert (GETALL oder SETALL)
Zugriffsrechte ändern