logoor-weiss.gif
LM-Miniabo
claim.gif
Linux Magazin Linux User Easy Linux International Linux Community
      Anzeigen
Datenrettung
Übersetzungsagentur
Services New York
Günstige Linux Bücher
Handy Shop


Server, Linux-Software, Hardware, Linux-Bücher
verkaufen & kaufen
Kleinanzeigen & Preisvergleich

Notebooks | Drucker | PC-Systeme
Automarkt | Münzen
Wohnmobile | Grafikkarten Branchenbuch

Hier kaufen Unternehmen
Akku
Arbeitsspeicher
Beamer
Computer
CPU
Druckerkabel
Drucker
DVD-Brenner
Firewall
Festplatte
Hardware
Grafikkarte
Monitor
Kopierpapier
Netzteil
Mainboard
Notebook
Motherboard
Router
Netzwerkkarte
Scanner
Software
Server
SUSE Linux
Switch
TFT Monitor
Tastatur
USB Kabel
Toner
Werkzeug

Ersatzakku APC Akku Compaq
BELKIN LIEBERT RBC5 RBC6
RBC7 RBC8 RBC9 Batteriekit
APC Batterie RBC11 RBC12 USV
RBC14 RBC22 USV Batterie APC
RBC23 RBC24 Batterietausch
RBC25 RBC27 RBC31 RBC43
RBC44 Smartcell RBC2 RBC4
SYBATT DP320-E SUDP6000i

Akku Pulsar Evolution 3000 MGE

APC Smart UPS kaufen SUA750i
SUA1500i SUA1500RMI2U
SUA2200RMI2U SUA3000RMI2U

BB Battery BP12-6 BP5-12
BP7-12 BP7.2-12 Bleiakku
BP12-12 HR9-6 HR5.5-12 Batterie
HR5.8-12 Batterie HR9-12 Akku

CSB Akku GP1270 GP12110 USV

ColdFusion Hosting Webhosting
Matratzen Lattenrost Matratze
Nussknacker Nutcracker


Erschienen im Linux-Magazin 03/1999

Systemprogrammierung - Teil 9

Die Ampel regelt alles

von Wolfgang Hetzler


Systemprogrammierung gilt als schwierig, da ihre Inhalte dem Beutzer sehr oft abstrakt gegenüber treten im Gegensatz zu praktischen Aufgabenstellungen, deren Sinn plastisch vor Augen steht. Daß diese Abstraktheit nicht sein muß und die Systemcalls eine wohldefinierte Schnittstelle zum Kernel darstellen, will diese Serie zeigen. Diesmal geht es um Semaphore.

In dieser Folge beschäftigen wir uns mit Semaphoren (Ampeln), die sowohl als Singular als auch im Plural den konkurrierenden Zugriff auf gemeinsame Betriebsmittel synchronisieren können. Leider sind, wie schon Rochkind bemerkte, die Semaphore unter Unix (und damit natürlich auch für Linux) äußerst komplex angelegt. Wir werden uns hier auf die binären Semaphore (Zustände Rot/Grün) beschränken. Zudem werde ich zeigen, wie sie effektiv implementiert werden können und wie sie den Verkehr regeln. Der Gedanke des binären Semaphors wird duch die Funktionen p() und v() realisiert, die wir uns wie folgt vorstellen können:

p(int semaphor) {
  while(semaphor <= 0);
  --semaphor;
 }

v(int semaphor) {
  ++semaphor;
}

Die Funktion p() wartet auf die Freigabe des Semaphors (der Wert geht auf 1) und setzt ihn auf Rot (der Wert geht auf 0), die Funktion v() setzt das Semaphor auf Grün (der Wert geht auf 1), dies impliziert für die Gestaltung zweier Prozesse, die auf ein gemeinsames Betriebsmittel zugreifen, folgende Abläufe (siehe Listing 1):

Listing 1: Prozeß-Ablauf
process_0() { 
  p(); // Versuch, das Semaphor zu erhalten 
  do_some_critical_stuff();
  v(); // gib das Semaphor frei
} 
	
process_1() { 
  p(); // Versuch, den Semaphor zu erhalten
  do_some_critical_stuff(); 
  v(); // gib das Semaphor frei 
  } 

Aufgrund der Prozeßgestaltung wird nur einer der beiden das Semaphor erhalten, der andere wird in der Funktion p hängen bleiben und erst seine kritische Tätigkeit, die sich auf ein gemeinsames Betriebsmittel bezieht, aufnehmen können, wenn der andere das Semaphor freigegeben hat. Natürlich muß das Semaphor von einer dritten Instanz (hier das Betriebssystem) korrekt verwaltet werden - dies leisten die entsprechenden Systemcalls, deren Prototypen ich zunächst kurz vorstelle (siehe Abb. 1):

Abb 1: Die Semaphore-Prototypen
int semget(key_t key, int nsems, int semflg);
int semop( int semid, struct sembuf *sops, int nsops);
int semctl();

Der Systemcall semget legt unter dem Namen key eine Anzahl von Semaphoren an, die mit nsems übergeben wird, semflg regelt den Zugriff auf die Semaphore. semop gestattet das Manipulieren der Semaphore, wobei für jedes Semaphor eine Struktur sembuf, die die Befehler zur Manipulation enthält, übergeben werden muß, nsops liefert die Anzahl. semctl wird wie gewohnt zur Information bzw. zum Löschen benutzt. Ich schreibe zunächst die beiden Funktion v() und p() (siehe Listing 2 und 3):

Listing 2: v.cpp
#include <sys/types.h> 
#include <sys/ipc.h>
#include <sys/sem.h>

int v(int sid) {
  struct sembuf sb[1]; 
  sb[0].sem_num = 0; 
  sb[0].sem_op = 1; 
  sb[0].sem_flg = SEM_UNDO; 
  return semop(sid,sb,1); 
}

Übergeben wird beim Aufruf von v() die id des Semaphors. Da wir jeweils nur ein binäres Semaphor ansprechen, benötigen wir exakt eine Struktur, die wir als Array anlegen, semop erwartet einen Zeiger auf die Strukturen. Die Nummer des Semaphors ist 0, der Befehl ist 1 - das impliziert, daß dieser Wert auf den aktuellen Wert des Semaphors addiert wird, das Flag signalisiert eine Rücknahme aller Operationen auf dieses Semaphor bei Termination. Abschließend wird semop aufgerufen und der Wert dieses Systemcalls zrückgliefert. Als nächstes schreibe ich die Funktion p() (siehe Listing 3):

Listing 3: p.cpp
#include <sys/types.h>			
#include <sys/ipc.h>
#include <sys/sem.h> 

int p(int sid) {
  struct sembuf sb[1];
  sb[0].sem_num = 0;
  sb[0].sem_op = -1;
  sb[0].sem_flg = SEM_UNDO;
  return semop(sid,sb,1);
}

Die Funktion p() hat in derBefehlskomponente die -1, das impliziert, daß der ausführende Prozeß blockiert, wenn der Wert des Semaphors durch eine Subtraktion negativ wird, ansonsten geht es weiter im Ablauf. Zur Initialisierung des Semaphors benutzen wir die Funktion initsem(), die ich hier kurz wiedergebe (siehe Listing 4):

Listing 4: initsem.cpp
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int initsem(int semid,int val) {
  semctl(semid,0,SETVAL,val);
}

Übergeben wird die id des Semaphors und der Initialisierungswert, bei einem binären Semaphor also 0 oder 1, der anschließend mit semctl eingestellt wird. Im Listing 5 ist der serverseitige Erwerb des Semaphors sowie ein entsprechender Client zu sehen:

Listing 5: msgbuf.h
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
					
// der Server
main() {
  int sem_id; 
  sem_id = semget(100,1,IPC_CREATE|0666); 
  initsem(sem_id,1); 
  p(sem_id); 
  // critical_section 
  v(sem_id); 
} 

// der client
main() {
  int sem_id; 
  sem_id = semget(100,1,0666); 
  p(sem_id); 
  // critical_section; 
  v(sem_id); 
} 	

Der Server legt das Semaphor an und initialisiert es mit 1, um es anschließend mit der p()-Operation zu erwerben und begibt sich in seine critical-section, um anschließend für alle weiteren das Semaphor auf Grün zu setzen. Der Client blockiert in der p()-Operation, falls das Semaphor vom Server nocht nicht via der v()-Operation auf Grün gesetzt worden ist, um anschließend in seine critical-section zu gehen und nachfolgend wieder grünes Licht zu geben.

Blockieren meint hier, daß ein Prozeß solange gestoppt wird, bis ein bestimmtes Ereignis (hier grünes Licht, oder das Semaphor kann dekrementiert werden, ohne das es negativ wird) eintritt. Natürlich ist unter diesen Voraussetzungen jederzeit auch eine totale Blockade aller Prozesse möglich - der sogenannte Deadlock. Zum Abschluß zeige ich den Server und den Client einer kleinen Anwendung, die serverseitig Files an einen Client tranportiert. Der Name der Datei wird dabei an den Server via Messages übertragen und die Datei wird im Bereich Shared Memory übergeben. Zur Synchronisation verwenden Client und Server Semaphore. Zunächst also der Server (siehe Listing 6):

Listing 6: ipcs.cpp
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <stdio.h>
			
#define SSEMKEY 101
#define KEY 100
#define RSEMKEY 102
#define KSEMKEY 103
#define MKEY 104

struct buffer { 
  int type;
  int count;
  char buffer[1024];
}; 
						
struct proto {
  int count;
  char buffer[1024];
};
			
struct buffer buf;
int fd,n,ssemkey,key,rsemkey,ksemkey,mkey;
int transfer_file(char *);

main(int argc, char **argv) {
  ssemkey=semget(SSEMKEY,1,IPC_CREAT|0666);
  rsemkey=semget(RSEMKEY,1,IPC_CREAT|0666);
  ksemkey=semget(KSEMKEY,1,IPC_CREAT|0666);
  mkey=msgget(MKEY,IPC_CREAT|0666);
  key = shmget(KEY,sizeof(struct proto),
               IPC_CREAT|0666);
  while(1) {
    initsem(ksemkey,1);
    msgrcv(mkey,(msgbuf *)& buf, 
           sizeof(buf.buffer)+sizeof(int),
           0,MSG_NOERROR);
    transfer_file(buf.buffer);
  }
}

int transfer_file(char * buffer) { 
  struct proto *shmp;
  fd = open(buffer,0);
  initsem(ssemkey,1);
  initsem(rsemkey,0);
  shmp = (struct proto *)shmat(key,0,0);
  while(1) {
    p(ssemkey);
    shmp->count = read(fd,shmp->buffer,1024);
    if(!shmp->count) {
      v(rsemkey);
      break;rsemkey 
    } 			
    v(rsemkey);
   } 	
  shmdt(key);
  return 1;
} 

Zur Begriffsdefinition: rsemkey repräsentiert das Semaphor, das den Empfang von Datenblöcken synchronisiert, ssemkey realisiert diesen Sachverhalt für das Versenden von Datenblöcke und ksemkey stimmt das Verhalten der Clients ab. mkey ermöglicht den Zugriff auf die Message-Queue und key entspricht dem Shared-Memory-Bereich.

Der Server begibt sich in eine Endlosschleife und setzt die Semaphore für die Kunden auf 1, ein Zugriff auf Dienstleistungen des Servers ist damit möglich. Anschließend versucht er eine Nachricht zu lesen, die dem Namen der zu sendenden Datei entspricht und ruft seine Funktion transfer_file zum Übertragen der Datei auf. transfer_file öffnet die Datei, initialisiert das Sendesemaphor mit 1 und das Empfangssemaphor mit 0, realisiert den Zugriff auf den Shared-Memory Bereich und begibt sich zum Übertragen in eine Endlossschleife.

In der Schleife versucht der Server, das Sendesemaphor zu erlangen, was ja auch mühelos klappt, da er es gerade auf 1 gesetzt hat. Anschließend versucht er 1024 Bytes in den Shared-Memory Bereich zu lesen. Der Rückgabewert von read landet ebenfalls in Shared-Memory, checkt den Rückgabewert für eine korrekte EOF-Behandlung, die zum Schleifenabbruch führen muß und gibt das Empfangssemaphor frei und bleibt in der nächsten Runde beim Versuch, das Sendesemaphor zu erlangen, hängen, da es ja auf Rot = 0 steht. Betrachten wir zum Verständinis die Client-Seite (siehe Listing 7).

Listing 7: ipcc.cpp
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
			
#define SSEMKEY 101
#define KEY 100
#define RSEMKEY 102
#define KSEMKEY 103
#define MKEY 104

struct buffer {
  int type; 
  int count;
  char buffer[1024];
};
			
struct proto {
  int count;
  char buffer[1024];
};
			
main(int argc, char **argv) { 
  int ssemkey,rsemkey,key,mkey,ksemkey;
  ssemkey=semget(SSEMKEY,1,0);
  rsemkey=semget(RSEMKEY,1,0);
  ksemkey=semget(KSEMKEY,1,0);
  mkey=msgget(MKEY,0);
  key = shmget(KEY,1024,0);			
  struct buffer buf;
  struct proto *shmp;
  gets(buf.buffer);
  p(ksemkey);
  buf.type=1;
  msgsnd(mkey,&buf,sizeof(buf.buffer)+sizeof(int),0);
  shmp = (struct proto *)shmat(key,0,0);
  while(1) { 
    p(rsemkey);
    if( shmp->count == 0)
    break;
    write(1,shmp->buffer,shmp->count);
    v(ssemkey);
  }
  shmdt((char *)shmp);
  v(ksemkey);
}

Der Client erlangt Zugrif auf die Resourcen in Analogie zum Server, liest von der Standardeingabe den Namen einer Datei und versucht, das Semaphor für den Zugriff auf den Server zu erlangen. Ist das Semaphor auf Rot, blockiert der Prozeß, bis das Signal auf Grün geschaltet wird, anschließend sendet der Client den Dateinamen via Message an den Server und erlangt Zugriff auf den Shared-Memory Bereich. Die while-Schleife dient der Übertrgung der Datei und wird ebenfals als Endlosschleife konstruiert, in der der Client wechselseitig Zugriff auf das Empfangssemaphor zu erlangen versucht und nach Empfang das Sendesemaphor, das ja der Server via p-Operation zu erlangen versucht, über die v-Operation freigibt.

Zu beachten ist auch die Tatsache, daß über die Struktur proto eine minimale Protokollvereinbarung zwischen Client und Server vorliegt: Eine Sendung im Shared-Memory Bereich besteht aus Daten und der Information, wieviel Nutzdaten effektiv übertragen worden sind. Nach Abschluß des Transfers gibt der Klient das Semaphor für den Zugriff auf den Server wieder frei. Das Semaphor für den Zugriff auf den Server ist nicht von Bedeutung, da er ja iterativ arbeitet und somit ein anderer Klient keinen Durchgriff erlangen kann.

Das Konzept der Semaphore greift natürlich nur, wenn alle Beteiligten sich an die Spielregeln halten. Da aber bei Client/Server- Programmierung die Beteiligten in der Regel identisch sind, dürfte das kein Problem darstellen. Soweit zu Semaphoren. Die nächsten Folgen dieser Reihe beschäftigen sich mit Kommunikationsstrukturen in Netzwerken.

Infos

[1] M. Beck, H. Böhme et al: Linux-Kernel-Programmierung, Addison-Wesley, 1997
[2] Rochkind: Unix-Programmierung für Fortgeschrittene, Hanser, 1985
[3] Bentson,R.: Inside Linux, Seattle, SSC 1996
[4] Brown, Chris: UNIX distributed programming, Prentice Hall
[5] Robbins, K.A:,Robbins,Steven: Practical Unix Programming,Prentice Hall

Der Autor

Wolfgang Hetzler arbeitet seit 1985 als Dozent an einem privaten Institut für Fort- und Ausbildung im EDV-Bereich und als Lehrbeauftragter an der FH-Frankfurt im Bereich Ingenieur-Informatik. Linux ist ihm seit der Version 0.9xx bekannt und eine Quelle ständiger Auseinandersetzung. Zu erreichen ist er unter het@het.gg.uunet.de .

Copyright © 1998 Linux-Magazin Verlag

Dieser Online-Artikel kann Links enthalten, die auf nicht mehr vorhandene Seiten verweisen. Wir ändern solche "broken links" nur in wenigen Ausnahmefällen. Der Online-Artikel soll möglichst unverändert der gedruckten Fassung entsprechen.




Druckerfreundliche Version | Feedback zu dieser Seite | © 2006 Linux New Media AG | Last modified: 2005-07-28 23:21

Partner-Sites: [LinuxUser] [EasyLinux] [Linux-Community] [Linux Events] [OpenBytes] [Linux Magazine] [Linux Magazin Romania] [Linux Magazine Poland] [Linux Magazine Brasil] [Linux Magazine Spain]