3 Prozesse und Threads

Transcription

3 Prozesse und Threads
Betriebssysteme
Folie 3 - 1
3 Prozesse und Threads
•
Programm
−
statische Folge von Anweisungen und Daten
−
befindet sich i.a. als Datei auf einer Festplatte
−
der Dateiinhalt entspricht den Regeln des Betriebssystems für
ausführbare Programme (exe-Format, elf-Format, ...)
•
Prozeß
−
dynamische Folge von Aktionen (Zustandsänderungen)
−
führt ein Programm auf einem Prozessor aus
−
wird vom Betriebssystem infolge eines Auftrags erzeugt
−
♦
Eingabe eines Kommandos von einem Benutzer
♦
Aufruf einer Systemfunktion von einem Prozeß
besitzt zugeordnete Speicherbereiche für den Programmcode,
seine statischen und dynamischen Daten sowie seinen Stack
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Systemfunktionen zum Erzeugen / Beenden eines Prozesses
−
Windows 95/98/NT/2000/... (Win32-API)
♦
♦
♦
♦
−
CreateProcess (...)
OpenProcess (...)
ExitProcess (...)
TerminateProcess (...)
UNIX
♦
♦
♦
♦
•
Folie 3 - 2
fork ( )
execlp (...), execvp (...), ...
exit (...)
kill (...)
Struktur eines Prozesses
Process
Resources
open files
locks
sockets
....
Virtual Address Space
main ()
Text
count ()
count ()
Identity
pid
uid
gid
....
prt_result ()
global_cnt
s_time
...
Data
Heap
Registers
PC
SP
...
Stack
j
i
p_cnt
return address
from count ()
...
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 3
Thread
−
sequentieller Ausführungsfaden innerhalb eines Prozesses
−
minimaler eigener Kontext
−
♦
CPU-Registersatz
♦
Stack
♦
Identifikationsnummer, Priorität, ...
alle Threads eines Prozesses benutzen denselben Adreßraum
sowie weitere Betriebsmittel des Prozesses gemeinsam
−
•
jeder Prozeß besitzt mindestens einen (initialen) Thread
Systemfunktionen zum Erzeugen / Beenden eines Threads
−
−
Windows 95/98/NT/2000/... (Win32-API)
♦
CreateThread (...)
♦
ExitThread (...)
♦
TerminateThread (...)
POSIX Threads (UNIX, ...)
♦
pthread_create (...)
♦
pthread_exit (...)
♦
pthread_kill (...)
♦
pthread_cancel (...)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
•
Folie 3 - 4
Java Threads (plattform-übergreifend)
♦
Erweiterung der Klasse Thread
♦
Implementierung der Schnittstelle runnable
♦
Aufruf der Methode start ( ) der Klasse Thread
♦
Aufruf der Methode exit ( ) der Klasse System
♦
Aufruf der Methode stop ( ) der Klasse Thread
Struktur eines Prozesses mit mehreren Threads (multithreaded
process)
Process
Virtual Address Space
Resources
main ()
open files
locks
sockets
....
Text
count ()
Identity
prt_result ()
pid
uid
gid
....
Data
Heap
main
Registers
Stack
PC
SP
...
Thread
Specific
Data
thread id
priority
...
Thread
Specific
Data
thread id
priority
...
Thread 1
Registers
PC
SP
...
Stack
Thread 2
Registers
PC
SP
...
Stack
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 5
Prozeßerzeugung unter UNIX
1) Betriebssystemkern muß eine Umgebung bereitstellen, in der das
Programm ausgeführt werden kann
2) der Prozeß besteht aus drei Bereichen:
−
instruction segment
−
user data segment
−
system data segment
3) das Programm wird zur Initialisierung des instruction segments
und user data segments benutzt
4) nach der Initialisierung besteht zwischen dem Prozeß und dem
Programm, das er ausführt, keine weitere Verbindung
5) der Prozeß kann weitere Betriebsmittel (mehr Speicher, neue Dateien, usw.) anfordern, die im Programm nicht vorhanden sind
•
mehrere parallel ablaufende Prozesse können mit demselben Programm initialisiert werden
•
der Betriebssystemkern kann Hauptspeicher sparen, wenn solche
Prozesse ein gemeinsames instruction segment verwenden
(die beteiligten Prozesse können die gemeinsame Nutzung nicht feststellen, da die
Segmente nur Lesezugriffe erlauben)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 6
Prozeßverwaltung
−
Prozesse stellen Verwaltungseinheiten bzw. Objekte dar
−
auf einem Multiprozessorsystem können Prozesse parallel
ausgeführt werden
−
auf einem Einprozessorsystem können sie durch eine geeignete
zeitliche Verschachtelung quasi-parallel ausgeführt werden
−
das Betriebssystem unterscheidet Anwendungsprozesse / Benutzerprozesse und Systemprozesse
−
Systemprozesse erbringen Betriebssystemleistungen, die aus
dem Betriebssystemkern ausgelagert wurden
−
Anwendungs- und Systemprozesse werden im Benutzermodus
(user mode) ausgeführt
−
im Benutzermodus sind nur nicht-privilegierte Befehle des
Prozessors erlaubt
−
der Betriebssystemkern wird im Systemmodus (system mode,
kernel mode) ausgeführt, in dem auch priviligierte Befehle des
Prozessors erlaubt sind
−
das Betriebssystem benötigt geeignete Datenstrukturen zur
Verwaltung der Prozesse
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 7
Prozeßobjekte unter Windows NT
−
Prozeßobjekte werden durch den Objektmanager erzeugt und
abgebaut
−
jedes Prozeßobjekt enthält einen Kopf, der unter der Verwaltung
des Objektmanagers steht
−
Aufbau des Prozeßobjekts
Objekttyp
Prozeß
Attribute
Prozeß-ID
Access Token
Basispriorität
Prozessorzugehörigkeit
Kontingentbeschränkung
Ausführungsdauer
I/O-Zähler
VM-Arbeitszähler
Exception / Debugging Ports
Beendigungsstatus
Dienste
Prozeß erzeugen
Prozeß öffnen
Prozeßinformationen einholen
Prozeßinformationen einstellen
Aktueller Prozeß
Prozeß beenden
VM reservieren / freigeben
VM lesen / schreiben
Virtuellen Speicher schützen
VM sperren und entsperren
VM-Informationen einholen
Virtuellen Speicher aktualisieren
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 8
3.1 Prozeß- / Thread-Zustände
•
Prozeßzustände und die zugehörigen Zustandsübergänge
new
create
I/O completion,
event occurs
ready
blocked
dispatch
timeout,
yield
running
I/O wait,
event wait
terminate
suspend
suspend
exit
resume
suspended
ready
•
resume
suspend
suspended
blocked
I/O completion,
event occurs
Thread-Zustände und -Zustandsübergänge in Java
born
sleep interval expires
start
I/O completion
notify or
ready
notifyAll
quantum
dispatch
expiration,
(assign a
interrupt,
processor)
yield
running
wait
waiting
sleep
sleeping
finished
dead
issue I/O
blocked
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 9
Prozeß- / Thread-Verwaltung basiert auf Zustandsmodell
−
Grundzustände, erweiterte Zustände, Pseudozustände
−
Übergänge zwischen den Zuständen
−
Prozeß / Thread kann einige Zustände im Laufe seines Lebens
mehrfach durchlaufen
•
typische Grundzustände
−
bereit (Synonyme: rechenwillig, rechenbereit, ausführbar,
ready, runable)
♦
Prozeß / Thread wartet nur auf Zuteilung eines Prozessors
♦
i.a. gibt es verschiedene Prioritätswarteschlangen
♦
neue Prozesse werden i.a. am Ende ihrer Prioritätswarteschlange eingefügt
−
aktiv (Synonyme: rechnend, running)
♦
Prozeß / Thread hat einen Prozessor zugeteilt bekommen
♦
auf einem Einprozessorsystem kann sich nur ein Prozeß /
Thread in diesem Zustand befinden
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 10
blockiert (Synonyme: wartend, blocked, waiting)
♦
Prozeß / Thread wartet auf ein Ereignis, z.B. Ein-/Ausgabe
♦
i.a. gibt es für verschiedene Wartebedingungen verschiedene
Warteschlangen
•
erweiterte Zustände
−
gibt es z.B. bei vielen UNIX-Systemen
−
suspendiert (Synonyme: ausgelagert, suspended)
♦
Prozeß / Thread wurde angehalten und i.a. auf externes
Speichermedium ausgelagert
♦
häufig über zwei Zustände realisiert: suspendiert blockiert
und suspendiert bereit
♦
warum wird ein Prozeß / Thread suspendiert ?
⋅
schlechte Systemleistung wegen kurzfristiger Überlast
⋅
Betriebssystem unterstützt keinen virtuellen Speicher, so
daß ganze Prozesse ein- / ausgelagert werden müssen
⋅
Benutzer will einen Prozeß stoppen, aber noch nicht
abbrechen
(bei vielen UNIX-Systemen z.B. mit <Ctrl-z> oder <Strg-z>; Fortsetzung
z.B. mit "fg" (im Vordergrund) oder "bg" (im Hintergrund))
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 11
wer kann einen Prozeß / Thread suspendieren ?
⋅
ein Prozeß / Thread (selbst) über entsprechende
Systemfunktionen
⋅
♦
der Benutzer / das "Betriebssystem"
Suspendierung durch Prozeß / Thread kann schwerwiegende
Folgen haben
⋅
Blockierung mehrerer Prozesse / Threads, falls der suspendierte Prozeß / Thread eine Betriebsmittelsperre hält
⋅
u.U. sogar Deadlock, falls die Funktion zur Fortsetzung
des Prozesses / Threads dieselbe Betriebsmittelsperre
benötigt
−
Zombie
♦
Zwischenzustand bevor ein Prozeß das System verläßt
♦
Prozeß hat seine Aufgabe erledigt und muß nur noch einen
Status an seinen Vaterprozeß liefern
♦
Zombie-Prozesse belegen nur noch einen Eintrag in der
Prozeßtabelle des Betriebssystems
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 12
Pseudozustände
−
Beginn (Synonyme: nicht existent, new, born)
♦
"notwendiger" Zustand, um einen Prozeß / Thread in das
System zu bringen
♦
−
wichtig ist nur der Zustandsübergang
Ende (Synonyme: nicht existent, exit, exited, dead)
♦
"notwendiger" Zustand, um einen Prozeß / Thread aus dem
System zu entfernen
♦
•
wichtig ist nur der Zustandsübergang
Zustandsübergänge
−
Beginn ⇒ bereit
♦
neuer Prozeß / Thread wird erzeugt
♦
Speicherbereiche und Datenstrukturen für neuen Prozeß
anlegen und initialisieren
(z.B. Codesegment, Datensegment, Prozeßkontrollblock, ...)
♦
Prozeß / Thread in Warteschlange "bereit" einketten
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
−
bereit ⇒ aktiv
♦
Prozeß / Thread wird einem freien Prozessor zugeteilt
♦
letzten Prozeß- / Thread-Kontext wiederherstellen
♦
Prozeß- / Thread-Zustand auf aktiv setzen
♦
Prozeß / Thread starten
aktiv ⇒ bereit
♦
Prozessor wird dem Prozeß / Thread entzogen
⋅
⋅
−
Folie 3 - 13
Zeitscheibe des Prozesses / Threads ist abgelaufen
Prozeß / Thread höherer Priorität wurde rechenwillig
♦
Prozeß- / Thread-Kontext retten
♦
Prozeß- / Thread-Zustand auf "bereit" setzen und Prozeß /
Thread in Warteschlange "bereit" einketten
aktiv ⇒ blockiert
♦
Prozeß / Thread muß auf ein Ereignis warten
⋅
⋅
⋅
Beendigung einer Ein- / Ausgabeoperation
Beendigung eines erzeugten Prozesses / Threads
...
♦
Prozeß- / Thread-Kontext retten
♦
Prozeß- / Thread-Zustand auf "blockiert" setzen und Prozeß/
Thread in Warteschlange "blockiert" einketten
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 14
aktiv ⇒ Ende
♦
Prozeß / Thread verläßt das System
♦
Aufräumarbeiten durchführen (i.a. nur bei Prozessen)
♦
⋅
ggf. offene Dateien des Prozesses schließen
⋅
ggf. andere Betriebsmittel des Prozesses freigeben
alle Datenstrukturen und Speicherbereiche freigeben (i.a.
nur bei Prozessen)
−
blockiert ⇒ bereit
♦
Ereignis ist eingetreten
♦
Prozeß- / Thread-Zustand auf "bereit" setzen und Prozeß /
Thread in Warteschlange "bereit" einketten
−
blockiert ⇒ suspendiert blockiert, bereit ⇒ suspendiert bereit,
suspendiert blockiert ⇒ blockiert, suspendiert bereit ⇒ bereit
(i.a. nur bei Prozessen; suspendierte Threads werden i.a. über Zustand "blockiert" des
Prozesses verwaltet)
♦
nur in Ausnahmefällen (siehe Zustand "suspendiert")
♦
Prozeß- / Thread-Zustand ändern und Prozeß / Thread in
entsprechende Warteschlange einketten
♦
aufwendig, da Datentransport zwischen Haupt- und Hintergrundspeicher erforderlich
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 15
suspendiert blockiert ⇒ suspendiert bereit (i.a. nur bei Prozessen)
♦
Ereignis ist eingetreten
♦
Prozeß kann ausgeführt werden, sobald er in Hauptspeicher
geladen worden ist
♦
Prozeßzustand ändern und Prozeß in Warteschlange
"suspendiert bereit" einketten
−
aktiv ⇒ suspendiert bereit (nur bei Prozessen)
♦
kommt eigentlich nicht (mehr) vor
♦
könnte benutzt werden, wenn ein Prozeß der Warteschlange
"suspendiert blockiert" mit hoher Priorität rechenwillig wird,
der sehr viel Hauptspeicher benötigt und der aktuelle Prozeß
viel Hauptspeicher belegt
♦
Prozeßkontext retten
♦
Prozeßzustand ändern und Prozeß in Warteschlange
"suspendiert bereit" einketten
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 16
Prioritätsklassen in den Zuständen können über verkettete Listen
realisiert werden, z.B.
−
einfach verkettete Listen
priority
n
first last
count
process
...
0
−
first last
count
process
process
process
ringförmig doppelt verkettete Listen
priority
n
current
count
process
...
0
−
current
count
process
Vor- / Nachteile ?
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
process
process
Betriebssysteme
•
Folie 3 - 17
Warteschlangenmodelle für Prozeßzustände
−
Modell mit mehreren "blockiert"-Warteschlangen
partially executed
swapped-out processes
(in disk swap area)
swap in,
resume
new
process
ready
queue
swap out,
suspend
CPU
dispatch
terminated
process
...
CPU
time slice
expired
I/O queue
I/O
I/O request
wait for
termination
event
occurs
wait for
interrupt
fork a child
call sleep ()
...
...
blocked queues
−
Modell kann durch "bereit"-Warteschlangen erweitert werden
real-time
processes
ready
queues
system
processes
interactive
processes
CPU
dispatch
terminated
process
...
CPU
batch
processes
process with higher
priority is ready,
time slice expired,
I/O finished, ...
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 18
3.2 Prozeßkontrollblock
•
das Betriebssystem verwaltet für jeden Prozeß einen Prozeßkontrollblock, der alle relevanten Informationen über den Prozeß enthält
(Synonyme: Prozeßkontext, process control block, task control block
Abkürzungen: PCB, TCB)
•
typische Komponenten eines Prozeßkontrollblocks
−
−
−
Identifikatoren
♦
Prozeßnummer / -name
♦
Nummer / Name des Benutzers, der den Prozeß gestartet hat
♦
Gruppennummer
Stellung in der Prozeßhierarchie
♦
Prozeßnummer des Vaterprozesses
♦
Prozeßnummern der Sohnprozesse
Prozessor-Zustandsinformation
♦
Register der Programmierumgebung
(Datenregister, Adreßregister, Segmentregister, Indexregister, ...)
♦
Kontroll-, Steuer-, Statusregister
(System-/Benutzer-Stack-Register, Programmzähler, Prozessorstatuswort, ...)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
−
−
−
Folie 3 - 19
Informationen zur Prozeßsteuerung
♦
Prozeßzustand
♦
Priorität
♦
Alarmzustand
♦
Zeiger auf Vorgänger und / oder Nachfolger
♦
Zeiger auf Threads des Prozesses
♦
Informationen zur Interprozeßkommunikation
Rechte
♦
Zugriffsrechte auf Speicherbereiche, Dateien, ...
♦
Art der erlaubten Prozessorbefehle (priviligiert / nicht priviligiert)
Speicherverwaltung
♦
Zeiger auf Segment- und / oder Seitentabellen
♦
Zeiger auf Tabelle mit offenen Dateien
Betriebsmittelkonten
♦
Maximalwerte für erlaubte Betriebsmittel
♦
noch verfügbare Kontingente
♦
Abrechnungsdaten
(CPU-Zeiten im System- / Benutzermodus, Anzahl Seitenfehler, ...)
−
sonstiges (Uhren, Sperren, ...)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
•
Folie 3 - 20
Prozeßkontext wird manchmal aufgeteilt in
−
Hardware-Kontext (i.w. Abbild der Prozessorregister)
−
Benutzer-Kontext (i.w. Speicherabbild des Prozesses)
−
System-Kontext (betriebssysteminterne Verwaltungsdaten)
Prozeßtabelle enthält Verweise auf Prozeßkontrollblöcke
process control block
process
table
...
process identification
process state
process priority
pointer to parent process
pointer to child processes
CPU register
save area
pointers to locate
process's memory
pointers to allocated
resources
...
...
...
NULL
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 21
Beipiele
−
Linux
♦
Prozeßtabelle bis Version 2.2.x
(linux/kernel/sched.c)
struct task_struct *task [NR_TASKS] = {&init_task, };
♦
"Prozeßtabelle" ab Version 2.4.x
struct task_struct *init_tasks [NR_CPUS] = {&init_task, };
⇒ Prozeßtabelle wurde in Prozeßliste je CPU überführt
♦
Prozeßkontrollblock
(/usr/include/linux/sched.h)
struct task_struct {
volatile long state;
unsigned long flags;
...
long priority;
...
int has_cpu;
int processor;
int last_processor;
...
struct task_struct *next_task, *prev_task;
struct task_struct *next_run, *prev_run;
...
pid_t pid;
pid_t pgrp;
...
struct wait_queue
*wait_chldexit;
/* for wait4() */
...
struct sem_queue
*sem_sleeping;
...
struct files_struct *files; /* open file table
*/
struct mm_struct
*mm;
/* memory management info */
...
};
−
Solaris 8
♦
♦
♦
struct proc
struct _klwp
struct _kthread
(/usr/include/sys/proc.h)
(/usr/include/sys/klwp.h)
(/usr/include/sys/thread.h)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 22
Verweisstrukturen zur Verwaltung der Sohnprozesse
−
Linux
struct task_struct
parent
p_cptr
p_pptr
p_pptr
p_osptr
youngest
child
♦
p_pptr
p_osptr
child
p_ysptr
oldest
child
p_ysptr
jeder Prozeß wird zusätzlich in der Prozeßtabelle geführt
(bis Linux Version 2.2.x)
♦
jeder Prozeß befindet sich zusätzlich in der Prozeßliste
(auch bei Linux bis Version 2.2.x)
−
Solaris
struct proc
parent
p_parent
p_parent
p_psibling
youngest
child
p_sibling
p_child
p_parent
p_psibling
child
p_sibling
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
oldest
child
Betriebssysteme
•
Folie 3 - 23
Verweisstrukturen zur Verwaltung von Threads
−
Linux
♦
Threads werden im Betriebssystemkern nicht unterstützt
(zumindest nicht bis Version 2.4.x)
⇒ Betriebssystemkern enthält keine Verweisstrukturen für
Threads
♦
Threads werden mit clone ( ) erzeugt und wie Prozesse
verwaltet
⇒ es gibt immer einen Master-Thread, der z.B. für die
Signalbehandlung zuständig ist
⇒ jeder Thread hat eine eigene Prozeßnummer (PID)
(unter Solaris würde "getpid ( )" bei jedem Thread dieselbe PID des umfassenden Prozesses liefern)
⇒ das Kommando "ps" zeigt jeden Thread als eigenen
Prozeß an
⇒ Threads eines Prozesses können unterschiedliche
Benutzer- und Gruppennummern haben
(ist bei Solaris nicht möglich, da Threads keine Prozesse sind)
⇒ Threads haben Vater-Sohn-Beziehungen
(bei Solaris sind alle Threads gleichwertig)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 24
⇒ einige Ressourcen sind auf einige Threads beschränkt
(ist bei Solaris nicht möglich, da Threads innerhalb eines Prozesses laufen)
⋅
auf einen durch fork ( ) erzeugten Sohnprozeß kann
nur der Thread warten, der den Aufruf enthält
⋅
mit rlimit ( ) gesetzte Begrenzungen für Ressourcen
gelten nicht für alle Threads
⋅
clock ( ) liefert nur Zeit für aktuellen Thread und
nicht für gesamten Prozeß inklusive aller Threads
⋅
usw.
♦
Threads werden in der Thread-Bibliothek verwaltet
♦
Thread-Kontrollblock (.../internals.h)
typedef struct _pthread_descr_struct *pthread_descr;
...
struct _pthread_descr_struct {
union {
struct {
pthread_descr self;
/* Pointer to this structure
} data;
void *__padding[16];
} p_header;
pthread_descr p_nextlive, p_prevlive;
pthread_descr p_nextwaiting;
pthread_descr p_nextlock;
pthread_t p_tid;
/* Thread identifier
int p_pid;
/* PID of Unix process
int p_priority;
/* (== 0 if not realtime)
...
} __attribute__ ((aligned(32)));
♦
Threads verhalten sich wie Kernel-level Threads, da für
jeden Thread ein Prozeß erzeugt wird
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
Betriebssysteme
♦
Folie 3 - 25
Beispielprogramm zur Beschränkung der Ressource fork ( )
...
pid_t fork_pid;
/* ID of child process
*/
int main (void)
{
ThrID_t thr_id [2];
/* ID's of created threads
*/
thr_id [0] = parallel ((PtrFunc_t) thr_fork, 0);
thr_id [1] = parallel ((PtrFunc_t) thr_wait, 1);
join_all (thr_id, 2);
return 0;
}
void thr_fork (void)
{
printf ("Thread thr_fork: forking a child process.\n");
fork_pid = fork ();
switch (fork_pid)
{
case -1:
/* error: no process created */
perror ("fork failed");
errno = 0;
break;
case 0:
/* child process
*/
printf ("Child process: I'm sleeping for 2 seconds.\n");
sleep (2);
printf ("Child process: terminating.\n");
exit (0);
break;
default:
/* parent process
*/
printf ("Thread thr_fork: sleeping for 10 seconds.\n");
sleep (10);
printf ("Thread thr_fork: try to wait for my child.\n");
if (waitpid (fork_pid, NULL, WNOHANG) == -1)
{
perror ("Thread thr_fork");
errno = 0;
}
else
{
printf ("Thread thr_fork: child terminated.\n");
};
};
}
void thr_wait (void)
{
printf ("Thread thr_wait: sleeping for 5 seconds.\n");
sleep (5);
printf ("Thread thr_wait: try to wait for child process.\n");
if (waitpid (fork_pid, NULL, WNOHANG) == -1)
{
perror ("Thread thr_wait");
errno = 0;
}
else
{
printf ("Thread thr_wait: child terminated.\n");
};
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 26
⇒ Ausgabe unter Linux
Thread thr_fork: forking a child process.
Thread thr_fork: sleeping for 10 seconds.
Child process: I'm sleeping for 2 seconds.
Thread thr_wait: sleeping for 5 seconds.
Child process: terminating.
Thread thr_wait: try to wait for child process.
Thread thr_wait: No child processes
Thread thr_fork: try to wait for my child.
Thread thr_fork: child terminated.
⇒ Ausgabe unter Solaris
Thread thr_fork: forking a child process.
Child process: I'm sleeping for 2 seconds.
Thread thr_wait: sleeping for 5 seconds.
Thread thr_fork: sleeping for 10 seconds.
Child process: terminating.
Thread thr_wait: try to wait for child process.
Thread thr_wait: child terminated.
Thread thr_fork: try to wait for my child.
Thread thr_fork: No child processes
Aufgabe 3-1:
Erstellen Sie ein Programm, das die Beschränkung der Funktion clock ( )
unter Linux aufzeigt.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 27
Solaris
♦
Threads werden im Betriebssystemkern unterstützt
⇒ Betriebssystemkern enthält Verweisstrukturen für
Threads
♦
Prozeßkontrollblock verweist auf ringförmig verkette Liste
seiner Kernel-level Threads
♦
Kernel Thread verweist auf seinen Lightweight Process
(im Prinzip nur eine Datenstruktur)
struct proc
p_tlist
process
t_procp
must remain in
memory
lwp_procp
struct light_klwp weight
process
lwp_thread
t_next
... t_prev
t_lwp
kernel
thread
lightweight
process
lwp_
thread
t_next
t_prev
t_forw
t_back
t_lwp
kernel
thread
lightweight
process
lwp_
thread
t_next
t_prev
t_forw
t_back
can be
swapped
or paged
out
t_lwp
kernel
thread
t_next
t_prev ...
must remain in
memory
struct _kthread
Aufgabe 3-2:
Untersuchen Sie, wie die Daten auf die Strukturen struct proc, struct
_klwp und struct _kthread unter Solaris aufgeteilt werden.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 28
3.3 Dispatcher / Scheduler
•
Scheduler (Ablaufplaner) ist für die zeitliche Ablaufplanung der
Prozesse zuständig
−
erledigt seine Arbeit aufgrund einer gewählten Strategie
−
setzt Anfangspriorität der Prozesse fest
−
kann die Priorität aufgrund des Prozeßverhaltens ändern
−
wird z.B. aktiviert, wenn
♦
Prozesse erzeugt / beendet werden
♦
Prozeßeigenschaften (z.B. Priorität) geändert werden
♦
eine Zeit abgelaufen ist oder bestimmte Systemfunktionen
aufgerufen werden, die indirekt in die Prozeßverwaltung
eingreifen (z.B. zur Koordinierung / Synchronisation von Prozesssen)
•
Dispatcher (Prozeßumschalter, low-level scheduler) ist für den
Prozeßwechsel (task switch, context switch) zuständig
−
muß sehr effizient implementiert werden, da Prozeßwechsel im
10 bis 100 Millisekundenbereich vorkommen
−
wird z.B. aktiviert, wenn der aktive Prozeß
♦
unterbrochen wird (z.B. Ablauf der Zeitscheibe)
♦
auf ein Ereignis warten muß (z.B. Ende einer Ein-/Ausgabeoperation)
♦
freiwillig auf den Prozessor verzichtet (spezielle Systemfunktion)
♦
endet
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 29
Aufgaben des Dispatchers
−
sichert den (gesamten) Hardware-Kontext des bisher aktiven
Prozesses in dessen Prozeßkontrollblock
(VAX/VMS (1982) hat hierfür den Befehl SVPCTX (save process context). Die Intel
Pentium Familie führt diese Aktion automatisch bei einem Prozeßwechsel durch. Ein
Prozeßwechsel wird hier z.B. durchgeführt, wenn ein "CALL"-Befehl auf einen TSSDeskriptor (task state segment) in der globalen Deskriptortabelle ausgeführt wird.)
−
ändert den Zustand des Prozesses ("bereit", "blockiert", ...)
−
kettet den Prozeß in die entsprechende Warteschlange ein
(VAX/VMS hat hierfür den Befehl INSQUE (insert into queue). Die Intel Pentium
Familie hat keinen entsprechenden Befehl.)
−
"sucht" den nächsten zu aktivierenden Prozeß
(VAX/VMS unterstützt 32 Prioritätsklassen, in denen Prozesse derselben Priorität in
einer verketteten Liste gespeichert sind. Das "Compute Queue Status Longword" enthält für jede nicht leere Warteschlange ein auf "1" gesetztes Bit. Der Bit-Suchbefehl
"FFS" (find first set bit) liefert die nicht leere Warteschlange höchster Priorität und der
Befehl "REMQUE" (remove from queue) kettet den ersten Prozeßkontrollblock aus
der Warteschlange aus. Der neue Prozeß kann also mit zwei Befehlen gefunden werden. Die Intel Pentium Familie hat u.a. die Befehle "BSF" (bit scan forward) und
"BSR" (bit scan reverse), die dem Befehl "FFS" entsprechen. Sie hat keinen Befehl,
der dem Befehl "REMQUE" entspricht.)
−
ändert den Zustand des Prozesses von "bereit" auf "aktiv"
−
restauriert den (gesicherten) Hardware-Kontext des Prozesses
(VAX/VMS hat hierfür den Befehl LDPCTX (load process context). Die Intel
Pentium Familie führt diese Aktion automatisch bei einem Prozeßwechsel durch.)
−
"startet" den Prozeß
(Erfolgt i.a. automatisch durch die Rückkehr aus der Dispatcher-Routine, da die
Prozessorregister nach dem Kontextwechsel den früher unterbrochenen Zustand des
restaurierten Prozesses enthalten.)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 30
Aufgaben des Schedulers
−
Minimierung der Gesamtkosten des Systems
♦
möglichst hoher Durchsatz (Stapelbetrieb)
⇒ Verwaltungsaufwand reduzieren (wenig Prozeßwechsel)
♦
möglichst kurze Antwortzeiten (Dialogbetrieb)
⇒ häufige Prozeßwechsel
♦
garantierte Antwortzeiten (Echtzeitbetrieb)
⇒ widersprüchliche Anforderungen
⇒ Verhältnis Stapelbetrieb zu Dialogbetrieb wird voreingestellt
⇒ Scheduler kann Stapel- und Dialogbetrieb unabhängig voneinander optimieren
−
sollte die bisherigen Verläufe der Prozesse bei seinen Entscheidungen berücksichtigen
⇒ ordnet die Prozesse / Threads verschiedenen "bereit"Warteschlangen zu
(z.B. aufgrund von Prozeßklassen oder Warte- und Rechenzeiten)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 31
"bereit"-Warteschlangen unter Solaris
scheduling
order
class-specific
priorities
first
scheduler
classes
interrupts
RT max
0
real-time
class (RT)
system
class (SYS)
+ TS max
0
- TS max
last
−
timesharing
class (TS)
global
priorities
process
queues
169 highest
160
159
100
99
60
59
0
lowest
der Scheduler konvertiert klassen-spezifische Prioritäten in
globale Prioritäten
−
die globale Priorität ist für den Dispatcher maßgebend
−
ein Prozeß behält die CPU bis
♦
seine Zeitscheibe abgelaufen ist
♦
er sich selbst blockiert oder endet
♦
ein Prozeß höherer Priorität rechenwillig wird
−
Prozesse derselben Priorität benutzen die Round-Robin-Strategie
−
für alle Prozesse gilt i.a. eine Standard-Zeitscheibe
−
Echtzeitprozesse können prozeß-spezifische Zeitscheiben haben
−
Prioritäten von Echtzeitprozessen werden durch das System
nicht geändert (können nur vom Benutzer geändert werden)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 32
Prioritäten von Systemprozessen werden nur vom Betriebssystem verwaltet (können vom Benutzer nicht beeinflußt werden)
−
Prioritäten von Time-sharing-Prozessen
♦
haben benutzer- und system-orientierte Komponente
♦
system-orientierte Komponente wird dynamisch geändert
⋅
Priorität wird erhöht, wenn der Prozeß nur einen kleinen
Teil seiner Zeitscheibe nutzt
⋅
Priorität wird erniedrigt bei rechenintensiven Prozessen
⋅
die Länge der Zeitscheibe wird größer je kleiner die
Priorität ist
(wenn Prozesse geringer Priorität aktiv werden, dürfen sie länger arbeiten,
wenn sie nicht durch einen Prozeß höherer Priorität unterbrochen werden)
♦
benutzer-orientierte Komponente
⋅
kann vom Benutzer zwischen "-TS max" und "TS max"
geändert werden (i.a. -20 bis 20; siehe Kommando "nice")
♦
⋅
Standardpriorität: 0
⋅
Sohnprozesse erben die benutzer-orientierte Priorität
globale Priorität ergibt sich aus den beiden Komponenten
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 33
"bereit"-Warteschlangen unter Windows NT
scheduling
order
dispatcher
ready queues
priorities
31
first
process
queues
highest
real-time
16
15
variable
priorities
system
last
−
1
0
lowest
für "variable priorities"
♦
Priorität eines Prozesses / Threads wird jedesmal erniedrigt,
wenn er seine Zeitscheibe ausgenutzt hat
♦
Priorität eines blockierten Prozesses / Threads wird erhöht
♦
⋅
großer Wert, falls auf Tastatureingabe gewartet wurde
⋅
kleiner Wert, falls auf andere Ein- / Ausgabe gewartet
wurde
Verfahren führt mittelfristig zu einer "Dreiteilung"
⋅
⋅
⋅
♦
hohe Priorität für interaktive Prozesse / Threads
mittlere Priorität für E/A-orientierte Prozesse / Threads
geringe Priorität für rechenintensive Prozesse / Threads
Auswahl eines Prozesses / Threads aufgrund seiner Priorität
und Prozessorzugehörigkeit
⇒ falls ein ausgewählter Prozeß / Thread aufgrund der
Prozessorzugehörigkeit nicht auf einem freien Prozessor
ausgeführt werden kann, wird der Prozeß / Thread mit
der nächst geringeren Priorität ausgewählt
−
geringste Priorität hat der "idle"-Prozeß ("system")
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 34
"bereit"-Warteschlangen für Java-Threads
high priority
Priority 10
Priority 9
thr1
...
Priority 6
normal priority
Priority 5
thr2
thr6
thr3
thr5
...
thr n
Priority 4
Priority 3
Priority 2
low priority
Priority 1
thr4
−
alle Threads starten mit normaler Priorität 5
−
Threads erben die Priorität ihres Erzeugers
−
es gibt Implementierungen mit Zeitscheibenverfahren und rein
prioritätsgesteuerten Scheduling-Verfahren
(Threads derselben Prioritätsklasse werden im ersten Fall im Round-Robin-Verfahren
bearbeitet. Im zweiten Fall können sie solange arbeiten bis sie fertig werden, sich
selbst blockieren oder durch einen Thread höherer Priorität verdrängt werden)
Aufgabe 3-3:
a) Welchen Prioritätsklassen gehören die folgenden Prozesse unter
Solaris an: sched, pageout, init, Xsession, dtlogin
b) Wie funktioniert das Scheduling unter Linux (siehe linux/kernel/sched.c)?
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 35
Einteilung der Scheduling-Strategien
−
nicht unterbrechende Verfahren
(Prozeß bleibt solange aktiv, bis er sich selbst in den Zustand blockiert versetzt)
−
unterbrechende Verfahren
(dem Prozeß kann der Prozessor entzogen werden; notwendig bei Echtzeit-Betriebssystemen)
♦
Prozeß kann nur dann unterbrochen werden, wenn der
Prozessor im Benutzermodus arbeitet
(Standard bei älteren UNIX-Systemen)
♦
Prozeß kann auch dann unterbrochen werden, wenn der
Prozessor im Systemmodus arbeitet
(notwendig bei Echtzeit-Betriebssystemen)
−
Ausnutzung der Prozessoren bei Multiprozessorsystemen
♦
alle Prozessoren werden genutzt, wenn Arbeit vorhanden ist
♦
einzelne Prozessoren dürfen ungenutzt bleiben, obwohl noch
Arbeit vorhanden ist
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 36
Bewertung von Scheduling-Verfahren
−
−
CPU-Auslastung (utilization)
♦
Maß für die Auslastung eines Prozessors durch Prozesse
♦
sollte zwischen 40 und 90 Prozent liegen
Durchsatz (throughput)
♦
Anzahl der pro Zeiteinheit fertiggestellten Aufträge
♦
schwankt zwischen vielen Aufträgen pro Sekunde und
wenigen pro Tag, Woche oder Monat
(abhängig von der erforderlichen Rechenleistung der Aufträge)
−
Durchlaufzeit (turnaround time)
♦
Zeit zwischen Betreten und Verlassen des Systems für einen
Auftrag
♦
Summe aus Rechenzeit und verschiedenen Wartezeiten
(warten auf CPU in der "bereit"-Warteschlange, auf E/A-Ende in der "blockiert"Warteschlange, usw.)
−
−
Wartezeit (waiting time)
♦
Wartezeit in der "bereit"-Warteschlange
♦
nur diese Zeit kann vom Scheduler direkt beeinflußt werden
Antwortzeit (response time)
♦
wichtige Zeitspanne für interaktive Prozesse
♦
Zeit zwischen Eingabe und Reaktion auf Eingabe
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 37
mögliche Scheduling-Verfahren
1) First-Come-First-Served (FCFS), First-In-First-Out (FIFO)
(Prozesse werden in der Reihenfolge ihrer Ankunft ohne Unterbrechung bis zu ihrem
Ende bzw. ihrer Blockierung bedient)
−
das einfachste Verfahren
−
Implementierung über FIFO-Warteschlange
−
u.U. große Wartezeiten und damit schlechte Antwortzeiten
−
i.a. gute Auslastung der CPU
−
kann zu langen Warteschlangen mit E/A-intensiven Prozessen führen, wenn ein rechenintensiver Prozeß mit wenig
E/A-Operationen immer wieder die CPU blockiert
−
Beispiel:
Zum Zeitpunkt 0 treffen die Prozesse P1, P2 und P3 ein. Die
Ausführungszeiten der Prozesse betragen t1 = 24, t2 = 8 und
t3 = 3 Zeiteinheiten resp.. Die folgenden Gantt-Diagramme
geben die zeitliche Zuordnung der Prozesse auf einen Prozessor an, wenn sie in der Reihenfolge (P1, P2, P3) bzw. (P3,
P2, P1) ankommen.
0
5
10
S1
20
25
P1
0
S2
15
5
P3
10
30
P2
15
20
P2
25
35
P3
30
35
P1
Im Vergabeplan S1 wartet P1 gar nicht, während P2 und P3
24 bzw. 32 Zeiteinheiten warten. Im Mittel warten die Prozesse im Plan S1 (0+24+32)/3 = 56/3 Zeiteinheiten, während
sie im Vergabeplan S2 nur 14/3 Zeiteinheiten warten.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 38
2) FCFS mit Prioritätswarteschlangen
(Regel: rechenintensive Prozesse erhalten eine geringe Priorität und E/A-intensive
eine hohe Priorität. Prozesse derselben Prioritätsklasse werden in der Reihenfolge
ihrer Ankunft ohne Unterbrechung bis zu ihrem Ende bzw. ihrer Blockierung bedient. Prozesse können durch Prozesse höherer Priorität unterbrochen werden. Der
Scheduler kann die Prioritäten der Prozesse ändern.)
3) Shortest-Job-First (SJF), Shortest-Processing-Time (SPT)
(der Prozeß mit der kürzesten Rechenzeit wird als nächster ohne Unterbrechung
bedient; Kenntnis der Rechenzeit ist erforderlich; Prozesse mit gleicher Rechenzeit
werden nach FCFS bedient)
−
Verfahren ist optimal bezogen auf die mittlere Wartezeit für
eine vorgegebene Menge von Prozessen (Beweis siehe Literatur)
−
nicht geeignet für kurzfristiges Scheduling, da die Rechenzeiten i.a. nicht bekannt sind
−
wird häufig eingesetzt für langfristiges Scheduling im
Stapelbetrieb
(in diesem Fall wird das vom Benutzer vorgegebene Zeitlimit benutzt)
−
kann auch als "FCFS mit Prioritätswarteschlangen" implementiert werden
(die Priorität eines Prozesses könnte über den Kehrwert der geschätzten Rechenzeit bestimmt werden ⇒ je mehr Rechenzeit desto niedriger die Priorität)
4) Shortest-Remaining-Time (SRT), Shortest-RemainingProcessing-Time (SRPT)
(im Prinzip SPT mit Unterbrechung, d.h. falls ein neuer Prozeß eintrifft, dessen Rechenzeit geringer ist als die verbleibende Rechenzeit des gerade aktiven Prozesses,
dann wird ein Prozeßwechsel durchgeführt)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 39
5) Round-Robin (RR)
(der Prozeß darf den Prozessor für eine Zeitscheibe benutzen; falls er in dieser Zeit
nicht fertig wird, wird er unterbrochen und am Ende der Warteschlange eingereiht; es
handelt sich hier um ein zyklisches Verfahren ohne Prioritäten)
−
weit verbreitetes Verfahren für die meisten MultitaskingBetriebssysteme
−
gleichmäßige Aufteilung der CPU-Zeit auf die Prozesse
−
Größe der Zeitscheibe bestimmt die Leistung
♦
zu kleine Zeitscheibe
⇒ hoher Verwaltungsaufwand durch Prozeßwechsel
⇒ nominale Systemleistung wird u.U. nicht erreicht, da
Kontextwechsel durchgeführt wird, bevor CacheSpeicher ihre "Warmlaufphase" überwunden haben
♦
zu große Zeitscheibe
⇒ nähert sich dem FCFS-Verfahren, da mit zunehmender Zeitscheibe blockierende Systemaufrufe wahrscheinlicher werden
⇒ mittlere Wartezeiten und Antwortzeiten werden
größer
6) Shortest-Elapsed-Time (SET), Multilevel Feedback Queue
Scheduling
(im Prinzip RR mit Prioritätswarteschlangen; neuer Prozeß wird in die Warteschlange
höchster Priorität eingereiht und erhält hier eine Zeitscheibe; nutzt er die Zeitscheibe
aus, wird er in die Warteschlange nächst niedriger Priorität eingereiht; andernfalls
wird er blockiert und kommt irgendwann wieder in die Eingangswarteschlange; rechenintensive Prozesse sinken schnell in der Priorität)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 40
Fallbeispiel: Solaris benutzt ein 2-layer scheduling (nur bis Solaris 8)
process
process
user
level
thread
user
level
thread
software
context,
...
software context, open files,
credentials, address space,
process group, ...
user
level
thread
user
level
thread
lightweight
process
lightweight
process
lightweight
process
machine
state, ...
machine
state, ...
machine
state, ...
unbounded user threads
are scheduled within
the threads library
CPU
kernel level scheduler
and dispatcher
CPU
kernel
thread
−
kernel
thread
kernel
thread
Bounded Threads werden fest mit einem Lightweight Process
(LWP) und damit mit einem Kernel Thread verknüpft
⇒ dieser LWP steht für andere Threads nicht zur Verfügung
⇒ Scheduling erfolgt nur durch den Dispatcher im Betriebssystemkern
⇒ verbraucht Betriebssystemressourcen (z.B. Kernel-Stack)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 41
Unbounded Threads werden auf einem freien LWP ausgeführt
♦
Unbounded Threads werden in verschiedenen Prioritätswarteschlangen der Thread-Bibliothek verwaltet
♦
das Betriebssystem verwaltet Warteschlangen für LWPs
⇒ der Thread wird durch den Scheduler der Thread-Bibliothek einem LWP zugeordnet
⇒ der Dispatcher ordnet den LWP einer CPU zu
♦
nicht für jeden Unbounded Thread wird ein LWP erzeugt
♦
ein LWP kann im Laufe der Zeit verschiedene Unbounded
Threads eines Prozesses ausführen
♦
falls ein Prozeß viele rechenwillige Unbounded Threads hat,
erzeugt das Betriebssystem für den Prozeß weitere LWPs
−
Kernel Threads bilden den unterbrechbaren Echtzeitkern im
Betriebssystemkern (sie bearbeiten auch Unterbrechungen)
−
Lightweight Prozesse sind Kernel Threads mit zusätzlichen
Kontrollstrukturen zur Unterstützung der User-level Threads
(Kernel Threads sind keine LWPs, aber LWPs sind immer Kernel Threads)
−
LWPs werden innerhalb des Prozesses erzeugt und teilen sich
alle Betriebsmittel mit dem Prozeß
(mit dem Kommando "ps -eLc" werden alle LWPs eines Prozesses angezeigt)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 42
Aufgabe 3-4:
Gegeben seien die folgenden Prozesse:
Prozeß
Rechenzeit
Priorität
(in Zeiteinheiten)
P1
10
3
P2
1
1
P3
2
3
P4
1
4
P5
5
2
Die Prozesse sind in der Reihenfolge (P1, P2, P3, P4, P5) zum Zeitpunkt 0
angekommen.
a) Zeichnen Sie die Gantt-Diagramme, falls der Scheduler die Prozesse
nach dem FCFS-, SJF- bzw. RR-Verfahren (eine Zeitscheibe entspricht einer Zeiteinheit) auf einen Prozessor zuteilt. Wie sieht das
Gantt-Diagramm aus, wenn er ein nicht unterbrechendes Verfahren
mit Prioritätswarteschlangen (kleine Prioritätsnummer entspricht
hoher Priorität) benutzt?
b) Geben Sie die Durchlaufzeit für jeden Prozeß und die mittlere
Durchlaufzeit der Prozesse für jeden Vergabeplan an.
c) Geben Sie die Wartezeit für jeden Prozeß und die mittlere Wartezeit
der Prozesse für jeden Vergabeplan an.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 43
3.4 Prozeßsynchronisation
•
Prozesse können folgendermaßen ausgeführt werden
−
zeitlich verschachtelt (quasi-parallel) auf einem Einprozessorsystem
−
zeitlich überlappt (parallel) auf einem Multiprozessorsystem
−
zeitlich verschachtelt und überlappt
(heutiger Standard auf einem Multiprozessorsystem)
•
Beispiel: modifizierender Zugriff auf eine gemeinsame Variable
1. Ausführungsreihenfolge
Prozeß / Thread 1
Prozeß / Thread 2
liest Wert der Variablen i
erhöht Wert um 1
speichert neuen Wert in i
liest Wert der Variablen i
erhöht Wert um 1
speichert neuen Wert in i
2. Ausführungsreihenfolge
Prozeß / Thread 1
Prozeß / Thread 2
liest Wert der Variablen i
liest Wert der Variablen i
erhöht Wert um 1
speichert neuen Wert in i
erhöht Wert um 1
speichert neuen Wert in i
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
•
Folie 3 - 44
Problem des obigen Beispiels
−
Fehler kann auftreten, muß aber nicht auftreten
−
Fehler ist schwer reproduzierbar, da abhängig von Nebenbedingungen (z.B. Systemlast)
−
typischer Synchronisationsfehler, wenn ein Prozeß einen
anderen im Ablauf überholt (als race condition bekannt)
−
Programmbereiche, in denen solche Fehler entstehen können,
dürfen nicht unterbrochen werden (kritischer Abschnitt, critical section)
Arbeitsweise der Prozesse muß koordiniert werden
−
Koordinierung
♦
Beeinflussung der Abläufe paralleler Prozesse / Threads
♦
Steuerung von Wechselwirkungen (Konkurrenz / Kooperation)
♦
Mittel: Synchronisation / Kommunikation
♦
Ziel: deterministischer Ablauf
(globale Ergebnisse sind unabhängig von der Ablaufreihenfolge der Prozesse)
−
−
Synchronisation
♦
Ablauf wird in zeitliche Reihenfolge gebracht
♦
Reihenfolge kann zufällig oder vorbestimmt sein
Sperrsynchronisation / wechselseitiger Ausschluß (mutual exclusion)
♦
Menge von konkurrierenden Prozessen / Threads um ein
Betriebsmittel
♦
einer wird ausgewählt und alle anderen werden gesperrt
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 45
wechselseitiger Ausschluß wird i.a. durch Protokolle realisiert
−
Vorprotokoll sorgt für Auswahl eines Prozesses / Threads
−
Nachprotokoll sorgt für Freigabe des kritischen Bereichs
Process / Thread 1
Process / Thread 2
acquire lock
access data
release lock
acquire lock
access data
release lock
shared
data
acquire lock
access data
release lock
Process / Thread 3
•
Regeln für die Benutzung eines kritischen Abschnitts
1) Sicherheit: zu jeder Zeit darf sich höchstens ein Prozeß innerhalb des kritischen Abschnitts befinden
2) Lebendigkeit: wenn ein Prozeß in einen kritischen Abschnitt
eintreten will, wird ihm dies nach endlicher Zeit gestattet
⇒ jeder Prozeß verbringt nur eine endliche Zeit im kritischen
Abschnitt (keine Programmierfehler durch Endlosschleifen, ...)
⇒ das Protokoll garantiert Fairneß
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 46
Realisierungsmöglichkeiten für wechselseitigen Ausschluß
−
kurzfristige Deaktivierung des Unterbrechungssystems
(verhindert Unterbrechungen und damit Prozeßwechsel; sehr harte Methode, die nur in
Ausnahmefällen benutzt werden sollte)
−
Sperrvariablen (spin locks)
(ihr Wert gibt an, ob der kritische Bereich belegt oder frei ist; sie funktionieren nur
dann effizient und allgemeingültig, wenn sie über einen speziellen, unteilbaren Befehl
der Maschinensprache (z.B. "BSET" (test a bit and set, Motorola MC68x00 Familie),
"BST" (bit test and set, Intel Pentium Familie)) realisiert werden; Variablen werden
im Vorprotokoll abgefragt und im Nachprotokoll freigegeben; ggf. muß der Wert im
Vorprotokoll zyklisch abgefragt werden, bis der kritische Abschnitt frei ist (aktives
Warten, busy waiting), so daß Prozessorzeit verschwendet wird; es müssen aufwendige Algorithmen implementiert werden, wenn solche Maschinenbefehle nicht benutzt
werden können)
−
Semaphore
(integrieren eine Variable (eine Art "Ampel") und eine Warteschlange für Prozesse /
Threads, so daß aktives Warten vermieden wird; die Variable kann vom Typ boolean
bzw. bit sein (binäre Semaphore, mutex) oder vom Typ integer (allgemeine / zählende
Semaphore); Semaphore und die zugehörigen Protokolloperationen sind im Betriebssystem implementiert; die Operationen werden aus der Sicht der Prozesse atomar ausgeführt)
−
Monitore
(sind Bestandteil einer höheren Programmiersprache, so daß der Einsatz vom Compiler überwacht werden kann; stehen nur in wenigen Sprachen zur Verfügung)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 47
3.4.1 Globale Variablen
•
Synchronisation der Prozesse / Threads ohne Betriebssystem- /
Compiler-Unterstützung
(in Zukunft wird nur noch der Begriff Prozeß benutzt, obwohl die verschiedenen Algorithmen in den Beispielprogrammen tatsächlich über Threads realisiert werden, da sie auf
einfache Weise globale Variablen unterstützen)
•
Zugriff auf ein Betriebsmittel erfolgt in zwei Schritten:
1) Variable auf den Wert Betriebsmittel frei überprüfen
2) a) frei: Variable auf den Wert Betriebsmittel belegt ändern
und das Betriebsmittel nutzen
b) belegt: warten, bis das Betriebsmittel frei wird
•
zugehöriger Programmausschnitt
...
while (BetriebsmittelZustand == belegt)
{
;
}
BetriebsmittelZustand = belegt;
...
BetriebsmittelZustand = frei;
...
•
/* normales Programm
*/
/* warten
*/
/* Betriebsmittel nutzen
*/
/* normales Programm
*/
leider kann das Problem auf diese einfache Weise nicht gelöst
werden (siehe auch einführendes Beispiel am Anfang des Kapitels 3.4)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 48
möglicher Ablauf
−
das Betriebsmittel sei frei und Programm A sei aktiv
−
nachdem Programm A die while-Schleife erfolgreich durchlaufen hat, wird ein Prozeßwechsel durchgeführt
−
als nächstes wird Programm B aktiviert und durchläuft die
while-Schleife ebenfalls erfolgreich, da Programm A das
Betriebsmittel nicht mehr sperren konnte
−
Programm B sperrt das Betriebsmittel und benutzt es
−
innerhalb der Nutzungsphase wird auch Programm B der Prozessor entzogen und Programm A wird aktiviert
−
da Programm A die while-Schleife bereits erfolgreich durchlaufen hat und nicht weiß, daß ihm zwischenzeitlich der Prozessor entzogen worden ist, belegt und benutzt es das Betriebsmittel
ebenfalls
⇒ die Sperrsynchronisation durch eine globale Variable hat versagt
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 49
Lösung 1 für den wechselseitigen Ausschluß
/* Solution 1 for the synchronization of two concurrent workers
* with global variables.
*
* For Windows 98/NT/2000 only "/W3" should be used because "/W4"
* results in lots of warnings in Microsoft's header files.
*
* UNIX:
*
(g)cc [-DSunOS] [-DLinux] [-DIRIX] [-DCygwin]
*
-o glvar_1a glvar_1a.c parallel.c -lpthread
* Windows 98/NT/2000:
*
cl
/DWin32 /W3 glvar_1a.c parallel.c
(Microsoft)
*
bcc32 /DWin32 glvar_1a.c parallel.c
(Borland)
*
...
*
* File: glvar_1a.c
Author: S. Gross
* Date: 30.09.2004
*
*/
#include
#include
#include
#include
#include
#define
#define
#define
#define
<stdio.h>
<stdlib.h>
<time.h>
<assert.h>
"parallel.h"
NUM_THR
NUM_CYCLES
MAX_NTIME
MAX_CTIME
2
30
4
2
/*
/*
/*
/*
must
# of
max.
max.
(!) be 2
normal/critical cycles
time for normal work
time for critical work
*/
*/
*/
*/
void worker1 (int nr);
void worker2 (int nr);
/* turn: number of the worker which is allowed to enter the critical
*
section next
*/
int turn;
int main (void)
{
ThrID_t thr_id [NUM_THR];
/* ID's of concurrent workers
assert (NUM_THR == 2);
srand ((unsigned int) time ((time_t) NULL));
/* worker 1 is allowed to enter the critical section first
turn = 1;
/* start concurrent workers
thr_id [0] = parallel ((PtrFunc_t) worker1, 0);
thr_id [1] = parallel ((PtrFunc_t) worker2, 1);
/* wait until all concurrent workers have terminated
join_all (thr_id, NUM_THR);
return 0;
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
Betriebssysteme
Folie 3 - 50
/* Worker processes doing critical and non-critical work.
*
* input parameter:
nr
"name" of the worker
* output parameter:
none
* return value:
none
* side effects:
none
*
*/
void worker1 (int nr)
{
int i;
/* loop variable
for (i = 0; i < NUM_CYCLES; ++i)
{
printf ("Worker %d: doing normal work in cycle %d.\n", nr, i);
/* simulate some normal work
SLEEP ((unsigned int) rand () % MAX_NTIME);
printf ("Worker %d: try to enter critical section in cycle %d.\n",
nr, i);
while (turn == 2)
{
printf ("Worker %d: waiting in cycle %d.\n", nr, i);
SLEEP (1);
};
printf ("Worker %d: doing critical work in cycle %d.\n", nr, i);
/* simulate some critical work
SLEEP ((unsigned int) rand () % MAX_CTIME);
turn = 2;
/* release critical region
};
*/
*/
*/
*/
}
void worker2 (int nr)
{
int i;
/* loop variable
for (i = 0; i < NUM_CYCLES; ++i)
{
printf ("Worker %d: doing normal work in cycle %d.\n", nr, i);
/* simulate some normal work
SLEEP ((unsigned int) rand () % MAX_NTIME);
printf ("Worker %d: try to enter critical section in cycle %d.\n",
nr, i);
while (turn == 1)
{
printf ("Worker %d: waiting in cycle %d.\n", nr, i);
SLEEP (1);
};
printf ("Worker %d: doing critical work in cycle %d.\n", nr, i);
/* simulate some critical work
SLEEP ((unsigned int) rand () % MAX_CTIME);
turn = 1;
/* release critical region
};
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
Betriebssysteme
Folie 3 - 51
Diskussion der Lösung 1
•
Lösung garantiert wechselseitigen Ausschluß, da Prozeß i nur dann
in seinen kritischen Abschnitt eintritt, wenn turn == i ist
•
Lösung ist verklemmungsfrei, da der Prozeß, dessen Nummer in turn
gespeichert ist, immer weiterarbeiten kann
•
Lösung stellt sicher, daß kein Prozeß am Betreten des kritischen
Abschnitts gehindert wird, falls der unkritische Abschnitt normale
Arbeit nur eine endliche Zeit dauert
⇒ Lösung erfüllt Regeln zur Benutzung eines kritischen Abschnitts
⇒ aus den folgenden Gründen ist sie trotzdem nicht als Lösung des
Problems geeignet
1) die beiden Prozesse müssen ihre kritischen Abschnitte abwechselnd betreten, d.h. ein langsamer Prozeß kann einen schnellen
behindern
2) falls Prozeß worker1 den kritischen Abschnitt durchlaufen und
für Prozeß worker2 freigegeben hat, kann er ihn nie mehr betreten, wenn Prozeß worker2 inzwischen z.B. aufgrund eines Fehlers im unkritischen Bereich beendet wurde
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 52
Lösung 2 für den wechselseitigen Ausschluß
•
anstelle der gemeinsamen Variablen turn wird jedem der beiden
Prozesse worker_i eine globale Variable ci zugeordnet
...
/* c[i]
*
*
*/
int
state variable for worker i.
0: worker uses critical section or wants to use it
1: worker performs normal work
c [NUM_THR];
int main (void)
{
...
for (i = 0; i < NUM_THR; ++i)
{
c [i] = 1;
};
/* start concurrent workers
...
return 0;
}
void worker (int nr)
{
int other_nr,
i;
int ntime;
/* workers doing norm. work 1st */
*/
/* number of other worker
/* loop variable
/* normal work time
*/
*/
*/
...
other_nr = (nr + 1) % 2;
for (i = 0; i < NUM_CYCLES; ++i)
{
prt_msg ("Worker", nr, "doing normal work in cycle", i);
/* simulate some normal work
*/
SLEEP ((unsigned int) rand () % ntime);
prt_msg ("Worker", nr, "try to enter critical section in cycle", i);
c [nr] = 0;
while (c [other_nr] == 0)
{
prt_msg ("Worker", nr, "waiting in cycle", i);
SLEEP (1);
};
prt_msg ("Worker", nr, "doing critical work in cycle", i);
/* simulate some critical work
*/
SLEEP ((unsigned int) rand () % MAX_CTIME);
c [nr] = 1;
/* release critical region
*/
};
prt_msg ("Worker", nr, "#################### finished after "
"cycle", i - 1);
}
•
diskutieren Sie die Lösung
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 53
Lösung 3 für den wechselseitigen Ausschluß
•
Problem von Lösung 2 soll dadurch behoben werden, daß der Prozeß
kurzfristig darauf verzichtet, den kritischen Abschnitt zu betreten,
wenn die globale Variable des anderen Prozesses anzeigt, daß er im
kritischen Abschnitt ist bzw. ihn betreten will
•
Code zwischen COBEGIN und COEND wird parallel ausgeführt
int c1, c2;
/* ci = 0: P. ist im krit. Abschn. oder */
/*
will ihn betreten
*/
/* ci = 1: P. ist im "normalen" Progr. */
int main (void)
{
c1 = 1;
c2 = 1;
COBEGIN
worker1 (1);
worker2 (2);
COEND;
}
/* =========
void worker1 (int nr)
{
for (;;)
{
normale Arbeit;
c1 = 0;
while (c2 == 0)
{
c1 = 1;
...
c1 = 0;
};
kritischer Abschnitt;
c1 = 1;
};
}
void worker2 (int nr)
{
...
}
Hauptprogramm
========== */
/* beide Prozesse starten
/* ===========
/*
/*
/*
/*
/*
/*
/*
Prozess 1
*/
============ */
| Vorprotokoll
|
|
|
Verzicht auf krit. Abschnitt
|
einen Augenblick warten
|
neuer Versuch
|
/* | Nachprotokoll
/* ===========
Prozess 2
/* analog worker1
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
*/
*/
*/
============ */
*/
Betriebssysteme
•
Folie 3 - 54
Implementierung des obigen Programms
...
/* c[i]
*
*
*/
int
state variable for worker i.
0: worker uses critical section or wants to use it
1: worker performs normal work
c [NUM_THR];
int main (void)
{
...
for (i = 0; i < NUM_THR; ++i)
{
c [i] = 1;
};
/* start concurrent workers
...
return 0;
}
void worker (int nr)
{
int other_nr,
i;
int ntime;
/* workers doing norm. work 1st */
*/
/* number of other worker
/* loop variable
/* normal work time
*/
*/
*/
...
other_nr = (nr + 1) % 2;
for (i = 0; i < NUM_CYCLES; ++i)
{
prt_msg ("Worker", nr, "doing normal work in cycle", i);
/* simulate some normal work
*/
SLEEP ((unsigned int) rand () % ntime);
prt_msg ("Worker", nr, "try to enter critical section in cycle", i);
c [nr] = 0;
while (c [other_nr] == 0)
{
prt_msg ("Worker", nr, "!!!!!!!!!!!!!!! Critical section is "
"busy. Drop my request for cycle", i);
c [nr] = 1;
SLEEP (1);
prt_msg ("Worker", nr, "try again to enter critical section "
"in cycle", i);
c [nr] = 0;
};
prt_msg ("Worker", nr, "doing critical work in cycle", i);
/* simulate some critical work
*/
SLEEP ((unsigned int) rand () % MAX_CTIME);
c [nr] = 1;
/* release critical region
*/
};
prt_msg ("Worker", nr, "#################### finished after "
"cycle", i - 1);
}
•
diskutieren Sie die Lösung
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 55
Lösung 4: Algorithmus von Dekker
•
Kombination der ersten und dritten Lösung zum wechselseitigen
Ausschluß
int turn,
c1, c2;
int main (void)
{
turn = 1;
c1
= 1;
c2
= 1;
COBEGIN
worker1 (1);
worker2 (2);
COEND;
}
void worker1 (int nr)
{
for (;;)
{
sonstige Arbeit;
c1 = 0;
while (c2 == 0)
{
if (turn == 2)
{
c1 = 1;
while (turn == 2)
{
;
};
c1 = 0;
};
};
kritischer Abschnitt;
turn = 2;
c1
= 1;
};
}
void worker2 (int nr)
{
...
}
/* Nr. des Prozesses, der als nächster
/*
den krit. Abschnitt betreten darf
/* ci = 0: P. ist im krit. Abschn. o.
/*
will ihn betreten
/* ci = 1: P. ist im "normalen" Progr.
/* =========
Hauptprogramm
========= */
/* beide Prozesse starten
/* ===========
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
/* |
Prozess 1
*/
=========== */
Vorprotokoll
Prozess 2 hat Prioritaet
-> "hoeflich" sein
Verzicht auf krit. Abschnitt
warten
neuer Versuch
Nachprotokoll
/* ===========
Prozess 2
/* analog worker1
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
=========== */
*/
Betriebssysteme
Folie 3 - 56
Diskussion des Verfahrens von Dekker
•
das Verfahren ermöglicht eine korrekte Selbstverwaltung des
wechselseitigen Ausschlusses
−
Lösung 3 wird um die Variable turn aus Lösung 1 ergänzt
−
turn legt (vorab) fest, welcher Prozeß im Konfliktfall die höhere
Priorität hat und welcher höflich sein muß
•
die Probleme der ersten drei Lösungen beruhen darauf, daß ein
Prozeß nach jeder Operation unterbrochen werden kann
⇒ Überprüfung der Betriebsmittel-Variablen und ggf. Belegen des
Betriebsmittels muß in einer unteilbaren Operation erfolgen
•
alle Lösungen haben den Nachteil, daß die Prozesse aktiv auf ein Ereignis warten und damit sinnlos Prozessorleistung verschwenden
Aufgabe 3-5:
Implementieren Sie das Verfahren von Dekker analog zu den Programmen der ersten drei Verfahren in den Programmiersprachen C und Java.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 57
Aufgabe 3-6:
a) Gegeben sind sechs Prozesse P1, ..., P6 mit den Ausführungszeiten
τ1 = 2, τ 2 = 1, τ 3 = 3, τ 4 = 2, τ 5 = 1, τ 6 = 5. Skizzieren Sie für den
folgenden Programmausschnitt die Überlagerungsphasen (Aktivitätsphasen) der einzelnen Prozesse.
(zwischen BEGIN und END stehen sequentiell auszuführende Teile und zwischen
COBEGIN und COEND parallel ausführbare Teile)
COBEGIN
P1;
BEGIN
P2;
COBEGIN
P3;
P4;
COEND;
P5;
END;
P6;
COEND
b) Gegeben seien die folgenden Überlagerungsphasen der sechs Prozesse T1 , ..., T6 . Geben Sie die zugehörige Programmsequenz an. Verwenden Sie die Sprachelemente BEGIN, END, COBEGIN und
COEND.
T6
T5
T4
T3
T2
T1
t
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 58
Beurteilung des Lösungsansatzes "Globale Variable"
1) Programme hängen sehr stark voneinander ab
•
die Lösung muß die Anzahl der nebenläufigen Prozesse berücksichtigen
•
ein unabhängiger, getrennter Programmentwurf ist praktisch unmöglich
2) Verallgemeinerung auf n > 2 Prozesse ist schwierig (existiert aber)
3) es werden ineffiziente, aktive Warteschleifen benutzt (busy waiting)
4) es wird eine gemeinsame Variable turn benutzt, die von allen Prozessen beschrieben wird
•
u.U. Verletzung von Speicherschutzmechanismen
•
nicht geeignet für verteilte Systeme (Rechnernetze), da ein Prozeß auf Rechner A keine Variable auf Rechner B modifizieren
kann
5) fehleranfällig, da der wechselseitige Ausschluß durch den Anwendungsprogrammierer gewährleistet werden muß
⇒ es sind mächtigere Sprachmittel für die Programmierung nebenläufiger Programme erforderlich
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 59
3.4.2 Semaphore
•
Sprachmittel zur Koordinierung paralleler Programme (Dijkstra 1965)
−
stammt aus dem Griechischen: Zeichenträger, Signalmast, optischer Telegraph (z.B. in der Schiffahrt), ...
−
Semaphore sind in verschiedenen Betriebssystemen implementiert (UNIX, Windows NT, ...)
−
•
aktives Warten wird vermieden
Definition: Ein Semaphor s ist eine Variable, die nur nicht-negative
ganzzahlige Werte annehmen kann.
−
manchmal wird die Semaphor-Variable auch so definiert, daß sie
negative Werte annehmen darf (siehe z.B. Coffman/ Denning)
−
intern besteht ein Semaphor i.a. aus einer Zählervariablen und
einer Warteschlange
•
erlaubte Manipulationen:
−
einmalige Initialisierung der Variablen
−
unteilbare Elementaroperationen P(s) und V(s)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 60
Bemerkungen:
1) allgemeine Semaphor-Variablen werden z.B. verwendet, wenn
von einem Betriebsmittel mehrere identische Exemplare vorhanden sind
2) binäre Semaphor-Variablen können nur die Werte 0 und 1 annehmen und können besonders einfach implementiert werden
(werden häufig auch Mutex-Variablen (mutual exclusion) genannt)
3) die Auswahlstrategie für die wartenden Prozesse bei der VOperation ist i.a. FIFO (diese Strategie ist aber nicht zwingend
vorgeschrieben)
4) Semaphore und ihre Operationen sind normalerweise Bestandteil
des Betriebssystemkerns, so daß die Prozeßzustände einfach
durch die Prozeßverwaltung geändert werden können
5) Es darf nur eine Initialisierung des Semaphors s im Programm
erfolgen, weitere Zuweisungen oder Testoperationen mit s sind
verboten.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 61
Systemfunktionen für Semaphore
−
Windows 95/98/NT/2000/... (Win32-API)
Operation
−
Critical Section
Mutex
(prozeß-intern)
(prozeß-übergreifend)
Semaphore
Objekt erzeugen
InitializeCriticalSection (...)
CreateMutex (...)
OpenMutex (...)
CreateSemaphore (...)
OpenSemaphore (...)
initialisieren
-
-
beim Erzeugen
P (...)
EnterCriticalSection (...)
WaitForSingleObject (...)
WaitForSingleObject (...)
V (...)
LeaveCriticalSection (...)
ReleaseMutex (...)
ReleaseSemaphore (...)
Objekt freigeben
DeleteCriticalSection (...)
CloseHandle (...)
CloseHandle (...)
UNIX
Operation
System V IPC
POSIX IPC
POSIX IPC
(namenlose Semaphore)
(Semaphore mit Namen)
Objekt erzeugen
semget (...)
sem_init (...)
sem_open (...)
initialisieren
semctl (...)
beim Erzeugen
beim Erzeugen
P (...)
semop (...)
sem_wait (...)
sem_wait (...)
V (...)
semop (...)
sem_post (...)
sem_post (...)
Objekt freigeben
semctl (...)
sem_destroy (...)
sem_close (...)
sem_unlink (...)
Operation
POSIX Threads
Objekt erzeugen
pthread_mutex_init (...)
initialisieren
-
P (...)
pthread_mutex_lock (...)
V (...)
pthread_mutex_unlock (...)
Objekt freigeben
pthread_mutex_destroy (...)
usw.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 62
Skizze einer Implementierung (in C Notation):
#define NIL
#define MAX_SEMIDS
(struct queue *) NULL
32
typedef int semaphore;
struct { int
value;
struct queue *wait_ptr;
} sem_array [MAX_SEMIDS];
/* Zaehlervariable
/* Warteschlange der PCBs
*/
*/
int idx;
/* Index in "sem_array"
*/
semaphore init_sem (int value)
{
if (value >= 0)
{
idx = ...;
/* freien Eintrag im Feld suchen */
sem_array [idx].value
= value;
sem_array [idx].wait_ptr = NIL;
/* noch wartet kein Prozess */
}
else
{
fprintf (stderr, "....");
exit (-1);
};
return (semaphore) idx;
};
void P (semaphore s)
{
if (sem_array [(int) s].value > 0)
{
sem_array [(int) s].value -= 1;
/* krit. Abschnitt frei
}
else
{
/* Prozess stoppen; Zustand auf "blockiert" setzen; Prozess in
* Warteliste des Semaphors s eintragen
*/
};
};
void V (semaphore s)
{
if (sem_array [(int) s].wait_ptr == NIL) /* Warteschlange leer?
{
sem_array [(int) s].value += 1;
}
else
{
/* einen wartenden Prozess auswaehlen; Prozess in Zustand
* "rechenwillig" bringen, ...
*/
};
};
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
Betriebssysteme
•
Folie 3 - 63
System V IPC Semaphore in Linux
−
Semaphor-Datenstrukturen
(/usr/include/sys/sem.h → /usr/include/linux/sem.h)
#define SEMMNI
128
/* max # of semaphore identifiers
/* One semid data structure for each set of semaphores
struct semid_ds {
struct ipc_perm sem_perm;
/* permissions .. see ipc.h
time_t
sem_otime;
/* last semop time
time_t
sem_ctime;
/* last change time
struct sem
*sem_base;
/* ptr to first semaphore in array
struct sem_queue *sem_pending;
/* pending operations
struct sem_queue **sem_pending_last; /* last pending operation
struct sem_undo *undo;
/* undo requests on this array
ushort
sem_nsems;
/* no. of semaphores in array
};
−
*/
*/
*/
*/
*/
*/
*/
*/
*/
/* One semaphore structure for each semaphore in the system.
struct sem {
short
semval;
/* current value
short
sempid;
/* pid of last operation
};
*/
/* One queue for each semaphore set in the system.
struct sem_queue {
struct sem_queue
*next;
/* next entry in the queue
struct sem_queue **prev;
/* previous entry in the queue
struct wait_queue *sleeper;/* sleeping process
...
struct semid_ds
*sma;
/* semaphore array for operations
...
};
*/
Warteschlangen (/usr/include/linux/wait.h)
struct wait_queue {
struct task_struct *task;
struct wait_queue *next;
};
−
*/
Semaphor-Feld (linux/ipc/sem.c)
static struct semid_ds *semary[SEMMNI];
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
*/
Betriebssysteme
−
Folie 3 - 64
Ausschnitt aus der Semaphor-Verweisstruktur unter Linux
array with sem_nsems
semaphores
semary [SEMMNI]
struct
semid_ds *
&sem_pending
struct
semid_ds *
...
struct
semid_ds *
semval
struct sem
*sem_base
struct sem_queue
*sem_pending
struct sem_queue
**sem_pending_last
...
sempid
...
IPC_UNUSED
IPC_UNUSED
NULL
semval
sempid
array with sem_nsems
semaphores
...
semval
struct sem
*sem_base
struct sem_queue
*sem_pending
struct sem_queue
**sem_pending_last
...
sempid
...
*
...
struct
semid_ds *
...
semval
sempid
&next
&sem_pending
...
struct sem_queue
*next
struct sem_queue
**prev
struct wait_queue
*sleeper
struct semid_ds
*sma
...
...
*
struct task_struct *task
struct task_struct *task
struct wait_queue *next
struct wait_queue *next
...
...
NULL
struct sem_queue
*sem_sleeping
...
−
struct sem_queue
*next
struct sem_queue
**prev
struct wait_queue
*sleeper
struct semid_ds
*sma
...
NULL
&next
*
NULL
struct sem_queue
*sem_sleeping
...
Codeausschnitt (linux/ipc/sem.c)
/* Manage the doubly linked list sma->sem_pending as a FIFO:
* insert new queue elements at the tail sma->sem_pending_last.
*/
static inline void insert_into_queue (struct semid_ds *sma,
struct sem_queue *q)
{
*(q->prev = sma->sem_pending_last) = q;
*(sma->sem_pending_last = &q->next) = NULL;
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 65
Aufgabe 3-7:
Ein weiterer Prozeß will einen kritischen Abschnitt betreten, den die
zweite Semaphor-Kollektion schützt. Erweitern Sie die Warteschlange
um diesen Prozeß. Machen Sie deutlich, welcher Anweisungsteil des
obigen Codeausschnitts für welchen Verweis zuständig ist.
•
Lösung des wechselseitigen Ausschlusses mit Hilfe eines Semaphors
semaphore s;
int main (void)
{
s = init_sem (1);
COBEGIN
p1 ();
p2 ();
COEND;
}
/* ==========
Hauptprogramm
========= */
/* Semaphor erzeugen und initialisieren */
/* beide Prozesse starten
*/
void p1 (void)
{
for (;;)
{
sonstige Arbeit;
P (s);
kritischer Abschnitt;
V (s);
};
}
/* ============
Prozess 1
=========== */
void p2 (void)
{
for (;;)
{
sonstige Arbeit;
P (s);
kritischer Abschnitt;
V (s);
};
}
/* ============
Prozess 2
=========== */
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 66
Implementierung des obigen Programms
...
#define
#define
#define
#define
NUM_THR
NUM_CYCLES
MAX_NTIME
MAX_CTIME
6
30
4
2
/*
/*
/*
/*
# of
# of
max.
max.
concurrent threads
normal/critical cycles
time for normal work
time for critical work
*/
*/
*/
*/
void worker (int nr);
semaphore ex,
screen;
int main (void)
{
ThrID_t thr_id [NUM_THR];
int
i;
srand ((unsigned int) time ((time_t)
ex
= init_sem (1);
screen = init_sem (1);
/* start concurrent threads
for (i = 0; i < NUM_THR; ++i)
{
thr_id [i] = parallel ((PtrFunc_t)
};
/* wait until all concurrent threads
join_all (thr_id, NUM_THR);
rel_allsem ();
return 0;
/* exclusive access
*/
/* ID's of concurrent threads
/* loop variable
*/
*/
NULL));
/* initialize semaphores
*/
*/
worker, i);
have terminated
*/
/* release semaphore
*/
/* loop variable
*/
}
void worker (int nr)
{
int i;
for (i = 0; i < NUM_CYCLES; ++i)
{
prt_msg ("Worker", nr, "doing normal work in cycle", i);
/* simulate some normal work
*/
SLEEP ((unsigned int) rand () % MAX_NTIME);
prt_msg ("Worker", nr, "try to enter critical section in cycle", i);
P (ex);
prt_msg ("Worker", nr, "doing critical work in cycle", i);
/* simulate some critical work
*/
SLEEP ((unsigned int) rand () % MAX_CTIME);
V (ex);
};
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 67
neben dem wechselseitigen Ausschluß ist die Synchronisation von
Prozessen eine wichtige Aufgabe in der parallelen Programmierung
•
Beispiele
−
Erzeuger / Verbraucher-Problem (producer/consumer problem)
♦
ein Prozeß erzeugt Daten und ein anderer verbraucht
(bearbeitet) die Daten
(z.B. Datenaustausch zwischen Gerätetreiber und Verarbeitungsprogramm)
♦
Kopplung der Prozesse über einen gemeinsamen Puffer
♦
Zugriff muß synchronisiert werden
♦
♦
⋅
Erzeuger darf nicht in vollen Puffer schreiben
⋅
Verbraucher darf nicht aus leerem Puffer lesen
drei Varianten werden untersucht
⋅
Zwischenpuffer für ein Datenelement
⋅
unbeschränkt großer Zwischenpuffer
⋅
beschränkter Zwischenpuffer
die verfügbaren Datenelemente bzw. freien Pufferplätze
sollen über allgemeine Semaphore bestimmt werden
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 68
Leser / Schreiber-Problem (readers/writers problem)
♦
beliebig viele Prozesse dürfen gleichzeitig Daten lesen
♦
zu jedem Zeitpunkt darf nur ein Prozeß Daten modifizieren
(falls mehrere Prozesse gleichzeitig Daten schreiben, könnte es zu Dateninkonsistenzen kommen, z.B. bei Dateien, Datenbanken, ...)
♦
Lösung soll möglichst hohen Grad an Parallelarbeit erlauben, ohne die Exklusivität der Schreiber zu verletzen
♦
zwei Varianten können untersucht werden
1) Leser haben eine höhere Priorität als Schreiber
⋅
Zugriff auf Datenbanken / Dateien
(Online-Versandhauskataloge, Online-Wetterdienst, Kontoauszugdrucker, ...)
⋅
gelegentlicher Änderungsdienst
2) Schreiber haben eine höhere Priorität als Leser
⋅
Abbuchungs-, Informations-, Reservierungssysteme
(Platzreservierung in der Bahn oder im Flugzeug, Geldausgabegeräte
bei Banken, ...)
⋅
umgehende Aktualisierung des Datenbestands erforderlich
(Platz kann nur einmal gebucht werden, Kontostand muß immer
aktuell sein, ...)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 69
Problem der speisenden Philosophen (dining philosophers problem)
♦
Lösungsversuche liefern viele Erkenntnisse über Synchronisationsprobleme bei parallelen Abläufen
♦
Verklemmungsmöglichkeiten (deadlock) werden untersucht
♦
unbegrenzt langes Zurückstellen bei der Prozeßsynchronisation (starvation) wird untersucht und veranschaulicht
♦
hat darüber hinaus keine praktische Bedeutung
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Lösung 1 des Erzeuger / Verbraucher-Problems
semaphore voll, leer;
int main (void)
{
leer = init_sem (1);
voll = init_sem (0);
COBEGIN
erzeuger ();
verbraucher ();
COEND;
}
void erzeuger (void)
{
for (;;)
{
P (leer);
erzeuge;
V (voll);
};
}
void verbraucher (void)
{
for (;;)
{
P (voll);
verbrauche;
V (leer);
};
}
−
diskutieren Sie die Lösung
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 3 - 70
Betriebssysteme
•
Folie 3 - 71
Lösung 3 des Erzeuger /Verbraucher-Problems
−
−
beschränkte Puffergröße
Füllungsgrad des Puffers soll über Semaphore realisiert werden
#define puffergroesse
semaphore AnzSatz,
AnzFrei,
s;
4
/* Anzahl Saetze im Puffer
*/
/* Anzahl freie Plaetze im Puffer
*/
/* wechselseitiger Ausschluss auf Puffer */
int main (void)
{
AnzSatz = init_sem (0);
AnzFrei = init_sem (puffergroesse);
s
= init_sem (1);
COBEGIN
erzeuger ();
verbraucher ();
COEND;
}
void erzeuger (void)
{
for (;;)
{
empfange Datensatz;
P (AnzFrei);
/* ggf. auf freien Platz warten
P (s);
/* Puffer reservieren
speichere Datensatz in Puffer;
V (s);
/* Puffer freigeben
V (AnzSatz);
/* Verbraucher informieren
};
}
void verbraucher (void)
{
for (;;)
{
P (AnzSatz);
/* ggf. auf Datensatz warten
P (s);
/* Puffer reservieren
hole Datensatz aus Puffer;
V (s);
/* Puffer freigeben
V (AnzFrei);
/* Erzeuger informieren
bearbeite Datensatz;
};
}
Aufgabe 3-8:
Implementieren Sie das Programm in der Programmiersprache C.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
*/
*/
*/
Betriebssysteme
•
Folie 3 - 72
Lösung 1 des Leser / Schreiber-Problems: Leser haben höhere
Priorität als Schreiber
−
kein Leser darf zum Warten genötigt werden, wenn kein
Schreiber schreibt
−
kein Leser muß nur deshalb warten, weil ein Schreiber darauf
wartet, daß der letzte Leser fertig wird
−
Hinweise zum Programm
1) es wird ein Semaphor s für den wechselseitigen Ausschluß
der Schreiber benötigt
2) die Menge der Leser wird als Ganzes den Schreibern
gleichgestellt
3) eine globale Variable AnzLeser zählt die Anzahl der aktiven
Leser
4) der Zugriff auf die globale Variable AnzLeser wird durch das
Semaphor ex synchronisiert
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Programm
int
AnzLeser;
semaphore s, ex;
int main (void)
{
AnzLeser = 0;
s
= init_sem (1);
ex
= init_sem (1);
COBEGIN
leser (1);
...
leser (n);
schreiber (1);
...
schreiber (m);
COEND;
}
void leser (int i)
{
P (ex);
AnzLeser = AnzLeser + 1;
if (AnzLeser == 1)
{
P (s);
};
V (ex);
Daten lesen;
P (ex);
AnzLeser = AnzLeser - 1;
if (AnzLeser == 0)
{
V (s);
};
V (ex);
Daten verarbeiten;
}
void schreiber (int i)
{
Daten erzeugen;
P (s);
Daten schreiben;
V (s);
}
−
diskutieren Sie die Lösung
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 3 - 73
Betriebssysteme
•
Folie 3 - 74
Lösung 2 des Leser / Schreiber-Problems: Schreiber haben höhere
Priorität als Leser
−
Schreiber sollen absolute Priorität gegenüber Lesern haben
⇒ ein Schreiber soll so schnell wie möglich den kritischen Ab-
schnitt betreten
⇒ er muß eine eventuell vorhandene Leserwarteschlange nicht
berücksichtigen
(eine Leserwarteschlange kann entstehen, wenn mehrere Schreiber auf den
Zugriff zum kritischen Abschnitt warten)
−
ein Schreiber muß auf das Ende der Leseaktionen warten, die er
bei seiner Anmeldung vorfindet
(wenn ein Schreiber schreiben will, dürfen sich neue Leser dem Leser-Pool nicht mehr
zugesellen)
−
Programm
int
AnzLeser, AnzSchreiber;
semaphore s, ex, LeserWS, LeserSperre, exs;
int main (void)
{
AnzLeser
= 0;
AnzSchreiber = 0;
s
= init_sem (1);
ex
= init_sem (1);
LeserWS
= init_sem (1);
LeserSperre = init_sem (1);
exs
= init_sem (1);
COBEGIN
leser (1);
...; leser (n);
schreiber (1); ...; schreiber (m);
COEND;
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
void leser (int i)
{
P (LeserWS);
P (LeserSperre);
P (ex);
AnzLeser = AnzLeser + 1;
if (AnzLeser == 1)
{
P (s);
};
V (ex);
V (LeserSperre);
V (LeserWS);
Daten lesen;
P (ex);
AnzLeser = AnzLeser - 1;
if (AnzLeser == 0)
{
V (s);
};
V (ex);
Daten verarbeiten;
}
void schreiber (int i)
{
Daten erzeugen;
P (exs);
AnzSchreiber = AnzSchreiber + 1;
if (AnzSchreiber == 1)
{
P (LeserSperre);
};
V (exs);
P (s);
Daten schreiben;
V (s);
P (exs);
AnzSchreiber = AnzSchreiber - 1;
if (AnzSchreiber == 0)
{
V (LeserSperre);
};
V (exs);
}
−
diskutieren Sie die Lösung
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 3 - 75
Betriebssysteme
Folie 3 - 76
Aufgabe 3-9:
Modifizieren Sie die erste Lösung zum Leser/Schreiber-Problem, so daß
die Leser absolute Priorität erhalten. Implementieren Sie das Programm
in der Programmiersprache C.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 77
Problem der speisenden Philosophen (Dijkstra, 1971)
Fünf Philosophen leben gemeinsam in einem Haus. Ihr Leben besteht abwechselnd aus Denken, Essen und Schlafen. Während die
Resultate ihres Denkens und ihre Schlafgewohnheiten ohne Bedeutung sind, entsteht beim Essen ein echtes Problem: Die Philosophen
nehmen ihre Mahlzeiten an einem gemeinsamen runden Tisch ein,
an dem jeder seinen festen Platz mit einem eigenen Teller hat. Es
wird stets ein schwierig zu essendes Spaghetti-Gericht serviert, das
nur mit zwei Gabeln verzehrt werden kann. Zwischen je zwei Tellern
liegt jedoch nur eine Gabel.
Gesucht wird ein Algorithmus, der ohne Vorschrift individueller
Essenszeiten die Gabelverteilung so regelt, daß kein Philosoph verhungern muß. Selbstverständlich ist vorausgesetzt, daß ein essender
Philosoph gelegentlich satt wird, seine Gabeln säubert und sie
zurücklegt.
0
4
0
4
1
3
3
1
2
2
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 78
Lösung 1 des Philosophen-Problems
−
Ein hungriger Philosoph greift zuerst nach der rechten Gabel.
Hat er diese erhalten, so greift er nach der linken Gabel. Erhält er
diese ebenfalls, so darf er essen. In allen anderen Fällen muß er
warten.
−
diese Lösung ist nicht verklemmungsfrei
(Falls alle Philosophen gleichzeitig hungrig werden, und gleichzeitig ihre rechten Gabeln aufnehmen, erhält keiner seine linke Gabel, so daß alle Philosophen verhungern.)
−
Programm
#define n
5
semaphore Gabel [n];
int main (void)
{
int
i;
for (i = 0; i < n; ++i)
{
Gabel [i] = init_sem (1);
/* alle Gabeln frei
};
COBEGIN
Philosoph (0); ...; Philosoph (n-1);
COEND;
*/
}
void Philosoph (int i)
{
for (;;)
{
Philosoph denkt;
P (Gabel [i]);
P (Gabel [(i + 1) % n]);
Philosoph isst;
V (Gabel [i]);
V (Gabel [(i + 1) % n]);
};
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
/* rechte Gabel
/* linke Gabel
*/
*/
Betriebssysteme
−
Ausgabe des Programms phil_1
...
Philosopher 2: ttttttttttttttt try to get my forks
Philosopher 1: I'm thinking in cycle 12.
Philosopher 0: I'm eating
Philosopher 2: have my right fork
Philosopher 1: ttttttttttttttt try to get my forks
Philosopher 4: I'm eating
Philosopher 4: I'm thinking in cycle 13.
Philosopher 4: ttttttttttttttt try to get my forks
Philosopher 4: have my right fork
Philosopher 4: I'm eating
Philosopher 0: I'm thinking in cycle 13.
Philosopher 0: ttttttttttttttt try to get my forks
Philosopher 1: have my right fork
Philosopher 4: I'm thinking in cycle 14.
Philosopher 4: ttttttttttttttt try to get my forks
Philosopher 4: have my right fork
Philosopher 4: I'm eating
Philosopher 4: I'm thinking in cycle 15.
Philosopher 4: ttttttttttttttt try to get my forks
Philosopher 4: have my right fork
Philosopher 4: I'm eating
Philosopher 4: I'm thinking in cycle 16.
Philosopher 4: ttttttttttttttt try to get my forks
Philosopher 4: have my right fork
Philosopher 4: I'm eating
Philosopher 4: I'm thinking in cycle 17.
Philosopher 4: ttttttttttttttt try to get my forks
Philosopher 4: have my right fork
Philosopher 4: I'm eating
Philosopher 4: I'm thinking in cycle 18.
Philosopher 3: I'm eating
Philosopher 3: I'm thinking in cycle 7.
Philosopher 3: ttttttttttttttt try to get my forks
Philosopher 3: have my right fork
Philosopher 3: I'm eating
Philosopher 0: have my right fork
Philosopher 4: ttttttttttttttt try to get my forks
Philosopher 4: have my right fork
Philosopher 3: I'm thinking in cycle 8.
Philosopher 3: ttttttttttttttt try to get my forks
Philosopher 3: have my right fork
^C Signal SIGINT received. All semaphore id's will be released.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 3 - 79
Betriebssysteme
•
Folie 3 - 80
Lösung 1* des Philosophen-Problems
−
Erweiterung von Lösung 1: Falls ein Philosoph seine linke
Gabel nicht erhält, legt er seine rechte Gabel zurück
−
Diskussion der Lösung 1*
♦
schwierig, da Prozeßsysteme i.a. nicht reversibel sind
(z.B. automatische Blockade bei P-Operation)
♦
alle Philosophen könnten ihre rechten Gabeln im gleichen
Takt aufnehmen und zurücklegen
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 81
Lösung 2 des Philosophen-Problems
−
eine andere Erweiterung von Lösung 1: Der Zugriff auf die Gabeln wird exklusiv immer nur einem Philosophen gestattet
−
Programm
#define n
5
semaphore Gabel [n],
ex;
int main (void)
{
int
i;
for (i = 0; i < n; ++i)
{
Gabel [i] = init_sem (1);
/* alle Gabeln frei
*/
};
ex = init_sem (1);
/* Gabelzugriff erlaubt*/
COBEGIN
Philosoph (0); ...; Philosoph (n-1);
COEND;
}
void Philosoph (int i)
{
for (;;)
{
Philosoph denkt;
P (ex);
P (Gabel [i]);
P (Gabel [(i + 1) % n]);
V (ex);
Philosoph isst;
V (Gabel [i]);
V (Gabel [(i + 1) % n]);
};
}
−
diskutieren Sie die Lösung
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
/* exklusiver Zugriff
/* rechte Gabel
/* linke Gabel
*/
*/
*/
Betriebssysteme
•
Folie 3 - 82
Lösung 3 des Philosophen-Problems
−
Problem kann nicht mit Semaphor-Variablen allein gelöst werden
−
ein Philosoph muß feststellen, ob er die zweite Gabel bekommt,
bevor er die erste Gabel aufnimmt
⇒ Zustandsvariablen sind erforderlich, die gelesen werden
können
−
Philosoph i befindet sich zu jedem Zeitpunkt in genau einem der
folgenden Zustände
♦
c [i] = 0 Philosoph i denkt,
♦
c [i] = 1 Philosoph i ist hungrig,
♦
c [i] = 2 Philosoph i ist sehr hungrig,
♦
c [i] = 3 Philosoph i ißt.
(Der Zustand c [i] == 2 wird erst in der vierten Lösungsvariante benötigt und stellt
sicher, daß Philosoph i nicht verhungert, indem er gegenüber seinen Nachbarn unter
gewissen Bedingungen eine höhere Priorität erhält.)
−
Semaphor-Variablen werden jetzt den Philosophen und nicht
mehr den Gabeln zugeordnet
−
ein hungriger Philosoph muß u.U. von seinen Nachbarn zum
Essen geschickt werden
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 83
Programm
#define n
5
int
c [n];
semaphore Phil [n],
ex;
int main (void)
{
int
i;
for (i = 0; i < n; ++i)
{
Phil [i] = init_sem (0);
/* Philosoph darf nicht essen */
c [i]
= 0;
/* Philosoph denkt
*/
};
ex = init_sem (1);
COBEGIN
Philosoph (0); ...; Philosoph (n-1);
COEND;
}
/* ein Philosoph darf essen, wenn er hungrig ist und
* keiner seiner beiden Nachbarn isst
*/
void Test (int i)
{
if ((c [(i - 1 + n) % n] != 3) &&
(c [i] == 1)
&&
(c [(i + 1) % n] != 3))
{
c [i] = 3;
V (Phil [i]);
/* Essen erlauben
};
}
void Philosoph (int i)
{
for (;;)
{
Philosoph denkt;
P (ex);
c [i] = 1;
Test (i);
V (ex);
P (Phil [i]);
Philosoph isst;
P (ex);
c [i] = 0;
Test ((i - 1 + n) % n);
Test ((i + 1) % n);
V (ex);
};
}
*/
/* Philosoph i ist hungrig
/* darf er essen ?
*/
*/
/* ggf. warten
*/
/* Philosoph i denkt
*/
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 84
Diskussion der Lösung
•
exklusiver Zugriff auf die globalen Zustandsvariablen c [i]
•
wichtigster Teil des Programms ist die Prozedur Test
−
wird von jedem Philosophen zunächst in eigener Sache aufgerufen, wenn er hungrig ist (Test, ob einer seiner Nachbarn ißt)
−
falls kein Nachbar ißt, wird er durch die V (Phil [i])-Anweisung
der Prozedur Test zum Essen zugelassen
−
andernfalls muß er an seiner P (Phil [i])-Anweisung darauf
warten, daß er von seinen Nachbarn zum Essen geschickt wird,
sobald sie ihre Mahlzeit beendet haben
−
ein Ausdruck in der if-Anweisung der Prozedur Test ist immer
redundant
♦
c [i] == 1 ist redundant, wenn ein Philosoph die Funktion in
eigener Sache aufruft
(der Ausdruck wird erst benötigt, wenn ein Philosoph nach dem Essen für seine
Nachbarn die Prozedur Test aufruft, um sie ggf. zum Essen zu schicken)
♦
c [i + 1] != 3 oder c [i - 1] != 3 ist redundant, wenn ein
Philosoph die Funktion für einen Nachbarn aufruft
(er ruft die Prozedur erst auf, wenn er seine Mahlzeit beendet hat, so daß für ihn
c [i] == 0 gilt)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 85
•
der Algorithmus ist verklemmungsfrei (Beweis: siehe Literatur)
•
der Algorithmus garantiert nicht, daß jeder Prozeß innerhalb einer
gewissen Zeit ablaufen kann
(durch eine bösartige Kooperation von Philosophen kann ein Philosoph verhungern)
−
wenn sich die beiden Nachbarn eines Philosophen mit dem
Essen abwechseln, verhungert er zwischen ihnen
(diese Kooperation kann allerdings gestört werden, sobald einer der beiden übrigen
Philosophen hungrig wird)
−
vier Philosophen können so geschickt kooperieren, daß der fünfte Philosoph verhungert
(hier Philosoph 2; siehe z.B. F. Pieper: Einführung in die Programmierung paralleler
Prozesse, Oldenbourg, 1977)
−
lfd. Nr.
c[0]
c[1]
c[2]
c[3]
c[4]
1
2
3
4
5
6
7
8
9
10
11
12
13
1
1
1
3
3
3
0
0
0
0
1
1
1
3
3
0
0
0
1
1
3
3
3
3
3
3
1
1
1
1
1
1
1
1
1
1
1
1
1
1
3
3
3
3
3
3
3
0
0
0
1
1
0
0
0
0
1
1
1
1
1
3
3
3
0
Zeile 13 entspricht Zeile 1 ⇒ Zyklus kann unendlich oft
wiederholt werden
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 86
Lösung 4 des Philosophen-Problems (F. Hofmann, Erlangen, 1974)
#define n
5
int
c [n];
semaphore Phil [n],
ex;
int main (void)
{
...
}
void Test (int i)
{
if (((c [(i - 1 + n) % n] < 2) &&
(c [i] == 1)
&&
(c [(i + 1) % n] < 2))
||
(c [i] == 2))
{
c [i] = 3;
V (Phil [i]);
};
}
/* entspricht Loesung 3
*/
/* Essen erlauben
*/
void Erbarmen (int i)
{
if ((c [i] == 1)
&&
((c [(i - 1 + n) % n] == 3) ||
(c [(i + 1) % n] == 3)))
{
c [i] = 2;
};
}
void Philosoph (int i)
{
for (;;)
{
Philosoph denkt;
P (ex);
c [i] = 1;
/* Philosoph i ist hungrig
Test (i);
/* darf er essen ?
V (ex);
P (Phil [i]);
/* ggf. warten
Philosoph isst;
P (ex);
c [i] = 0;
/* Philosoph i denkt
/* Nachbarn ggf. zum Essen schicken
Test ((i - 1 + n) % n);
Test ((i + 1) % n);
/* prüfen, ob Philosoph (i-1) bzw. (i+1) zwischen zwei essenden
* Philosophen sass, als er hungrig wurde
* ⇒ der Philosoph ist inzwischen sehr hungrig
*/
Erbarmen ((i - 1 + n) % n);
Erbarmen ((i + 1) % n);
V (ex);
};
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
Betriebssysteme
Folie 3 - 87
Diskussion der Lösung
•
Philosophen können sich nicht gegen einen Kollegen verschwören
⇒ kein Philosoph muß verhungern
(wird durch den Zustand sehr hungrig sichergestellt, der dem Philosophen Priorität gibt.
Beweis: siehe z.B. F. Pieper: Einführung in die Programmierung paralleler Prozesse,
Oldenbourg, 1977)
•
ein Philosoph wird genau dann sehr hungrig, wenn er hungrig ist
und seine beiden Tischnachbarn gleichzeitig essen
(der Zustand wird eingestellt, wenn einer der Nachbarn seine Mahlzeit beendet)
•
kein Nachbar eines sehr hungrigen Philosophen wird zum Essen
zugelassen
•
Test überprüft, ob ein Nachbar des hungrigen Philosophen i ißt oder
sehr hungrig ist
(Philosoph i wird zum Essen zugelassen, wenn dies nicht der Fall ist; c [i] == 2 kommt nur
vor, wenn beide Nachbarn weder essen noch selbst sehr hungrig sind; der eine Nachbar hat
den Zustand nach seiner Mahlzeit gesetzt (Prozedur Erbarmen) und der andere Nachbar hat
Philosoph i zum Essen geschickt (Prozedur Test))
•
in Erbarmen ist die Abfrage c [i + 1] == 3 oder c [i - 1] == 3
redundant, da der Zustand des aufrufenden Prozesses 0 ist
Aufgabe 3-10:
Die Philosophen 1, 3 und 2 werden in dieser Reihenfolge hungrig. Danach werden die Philosophen 1 und 3 satt. Beschreiben Sie den Ablauf.
Geben Sie die Werte der Felder Phil und c während des Ablaufs an.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 88
Beurteilung des Lösungsansatzes "Semaphore"
1) nebenläufige Programme können unabhängig voneinander
entwickelt werden
2) beliebig viele Prozesse können relativ einfach synchronisiert werden
3) aktives Warten wird vermieden
4) nicht geeignet für verteilte Systeme (Rechnernetze), da ein Prozeß
auf Rechner A keine Semaphor-Variable von Rechner B kennt und
auch nicht in eine Warteschlange einer Semaphor-Variablen auf
Rechner B eingekettet werden kann
5) wenn eine P-Operation erfolglos war, wird der Prozeß gestoppt und
kann keine anderen Aktionen vorziehen
(es gibt entsprechende Erweiterungen des Konzepts, die das verhindern)
6) ein Prozeß kann nicht auf ein beliebiges Semaphor aus einer Menge
warten, wenn er mehrere exklusive Betriebsmittel benötigt
(es gibt entsprechende Erweiterungen des Konzepts, die das erlauben)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 89
7) fehleranfällig, da der Programmierer sehr leicht Fehler begehen kann
a) falls eine P-Operation aufgrund eines (bewußten oder unbewußten) Programmierfehlers fehlt, ist der wechselseitige Ausschluß
nicht gewährleistet
b) wenn eine V-Operation vergessen wird, können einzelne Prozesse auf Dauer blockiert werden
c) wenn die Reihenfolge der P- bzw. V-Operationen falsch programmiert wird, kann es zu Verklemmungen kommen
(siehe Aufgabe beim Erzeuger / Verbraucher Problem)
8) es gibt kein Rezept zur Lösung der Kooperationsprobleme, das zuverlässig zu einer richtigen Lösung führt
9) hat man eine Lösung gefunden, ist es i.a. noch schwieriger, die
Allgemeingültigkeit und Richtigkeit der Lösung nachzuweisen
(siehe Philosophenproblem)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 90
3.4.3 Monitore
•
Semaphore sind leistungsfähige, aber unstrukturierte Mechanismen
zur Prozeßkoordinierung
−
Semaphoroperationen können in der gesamten Anwendung
verstreut sein
−
globale Auswirkungen der Operationen sind deshalb schwer zu
übersehen
⇒ es werden Sprachmittel für strukturierte Koordinierung benötigt
⇒ Sprachmittel sollten in Programmiersprache eingebettet sein
(höhere Sicherheit, da die Benutzung der Sprachmittel vom Compiler überprüft wird
⇒ besser als die bisherigen Programmierkonventionen)
⇒ Semaphore können zur Realisierung der Sprachmittel benutzt
werden
•
ein Monitor ist ein solches strukturiertes Sprachmittel
(vorgeschlagen von Dijkstra (1971), Brinch Hansen (1972); formal definiert von Hoare
(1974); erweitert von Lampson und Redell (1980) für die Programmiersprache Mesa, ...)
−
besteht aus Prozeduren / Funktionen und Datenstrukturen
−
vergleichbar mit einer Klasse einer objektorientierten Programmiersprache
−
der Monitor stellt sicher, daß nur ein Prozeß zu einem Zeitpunkt
seine Prozeduren / Funktionen benutzt
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 91
ein Monitor besteht aus den drei folgenden Teilen:
1) Deklarationsteil für private Daten
2) lokale und globale Prozeduren zur Manipulation dieser Daten
3) Initialisierungsprogramm für die Daten (Hauptprogramm des Monitors)
•
prinzipielle Syntax
monitor Name_des_Monitors
Deklaration der Variablen
private procedure i1 (...)
{
...
}
...
private procedure im (...)
{
...
}
public procedure p1 (...)
{
...
}
...
public procedure pn (...)
{
...
}
{
Initialisierung der Daten
}
•
Monitorkonzept wird z.B. in den Programmiersprachen Concurrent
Pascal, Modula-2, Modula-3, Mesa und Java unterstützt
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 92
Monitor benötigt Sprachmittel zur Synchronisation von Prozessen
innerhalb des Monitors
•
−
Prozeß muß auf die Erfüllung einer Bedingung warten können
−
Prozeß muß aktiviert werden, sobald die Bedingung erfüllt ist
Synchronisation innerhalb eines Monitors wird unterstützt durch
−
Bedingungsvariablen:
♦
Typ: condition
♦
repräsentieren nur eine Prozeß-Warteschlange
(sie entsprechen damit einer Semaphor-Variablen ohne Zählervariable)
−
Operation wait
wait (x) bzw. x.wait bewirkt, daß aufrufender Prozeß blockiert
und in Warteschlange der Bedingungsvariablen x eingekettet
wird
(entspricht der P-Operation, wobei der Prozeß aber in jedem Fall blockiert wird)
−
Operation signal oder notify
♦
signal (x), x.signal, notify (x) bzw. x.notify bewirkt, daß ein
auf die Bedingung x wartender Prozeß aktiviert wird
(entspricht der V-Operation, wobei eine signal-Operation ohne Wirkung bleibt,
wenn die Warteschlange der Bedingungsvariablen leer ist, d.h. ein Prozeß, der
später eine wait-Operation ausführt, muß warten, bis erneut eine signal-Operation ausgeführt wird)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 93
Aufruf der wait-Operation gibt Monitor für andere Prozesse frei
(notwendig, da der Prozeß den Monitor sonst blockieren würde, weil sich nur ein Prozeß im
Monitor befinden darf)
•
beim Aufruf der signal-Operation gibt es ein Problem
1. der Prozeß, der die signal-Operation ausführt, befindet sich im
Monitor
2. der Prozeß, der u.U. durch die signal-Operation geweckt wird,
befindet sich ebenfalls im Monitor
⇒ zwei Prozesse wären im Monitor, obwohl zu jedem Zeitpunkt
nur ein Prozeß im Monitor aktiv sein darf !!!
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 94
Lösung: einer der beiden Prozesse muß warten
1) der Prozeß, der die signal-Operation ausführt, muß warten
(Vorschlag von Hoare)
−
der signalisierende Prozeß wird in einer Prioritätswarteschlange blockiert
−
sobald der aufgeweckte Prozeß den Monitor verläßt oder
sich selbst durch eine neue wait-Operation deaktiviert, wird
ein Prozeß aus der Prioritätswarteschlange gestartet
2) signal-Operation darf nur als letzte Anweisung einer Monitorprozedur ausgeführt werden
(d.h., daß der aktive Prozeß den Monitor sofort verläßt und damit nur der aufgeweckte
Prozeß im Monitor aktiv ist; diese Variante wird z.B. in Concurrent Pascal benutzt)
queue of
entering
processes
Monitor
entrance
waiting area
signal (c1 )
wait (c1)
local data
condition c1
...
signal (cn )
wait (cn)
condition cn
procedure 1
...
procedure k
wait, exit
signal
initialization code
priority queue
exit
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 95
3) der aufgeweckte Prozeß muß warten
(Vorschlag von Lampson und Redell für die Sprache Mesa)
−
ein aufgeweckter Prozeß wird aktiviert, sobald der signalisierende Prozeß den Monitor verläßt oder sich selbst durch
eine wait-Operation deaktiviert
−
die Funktion signal wird hier i.a. notify genannt
−
aufgeweckte Prozesse warten in einer Warteschlange innerhalb des Monitors oder gemeinsam mit neuen Prozessen in
der Eingangswarteschlange des Monitors
(abhängig von der Implementierung; in der Sprache Java warten aufgeweckte
Prozesse gemeinsam mit neuen Prozessen in der Eingangswarteschlange und
haben damit keine Priorität gegenüber neuen Prozessen)
queue of
entering or
notified
processes
*
Monitor
entrance
waiting area
wait, exit
*
local data
queue of notified processes
notify (c1 )
wait (c1 )
*
procedure 1
...
condition c1
...
procedure k
notify (cn )
wait (cn )
condition cn
*
initialization code
exit
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 96
"Problem" bei Lösung 3
−
es ist nicht sichergestellt, daß ein Prozeß ablaufen kann, dem
eine Bedingung als wahr gemeldet worden ist
−
Beispiel
P1:
...
if (Bedingung_1 falsch)
{
wait (B1);
};
...
P2:
...
if (Bedingung_2 falsch)
{
wait (B2);
};
...
♦
beide Prozesse warten
♦
Prozeß im Monitor informiert beide Prozesse mit notify (B1)
und notify (B2), daß ihre Bedingungen wahr geworden sind
♦
sobald der Prozeß den Monitor verläßt, kann einer der beiden Prozesse (z.B. P1) den Monitor wieder betreten
♦
da P1 alle Variablen im Monitor ändern darf, kann es
vorkommen, daß Bedingung B2 wieder falsch wird
♦
wenn P2 den Monitor wieder betritt, kann er nicht davon
ausgehen, daß die Bedingung B2 noch wahr ist
⇒ jeder Prozeß muß die Bedingung noch einmal überprüfen,
d.h. die Anweisung if (...) muß durch while (...) ersetzt
werden
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 97
Lösung 3 erlaubt zeitlich begrenztes Warten
−
problemlose Erweiterung, da jeder Prozeß seine Bedingung noch
einmal überprüft
−
−
•
Erweiterung der Operationen
wait (bedingung)
⇒
wait (bedingung, max_zeit)
notify (bedingung)
⇒
keine Änderung
robuster gegenüber Programmierfehlern (keine dauerhafte Blockierung)
Erweiterung des Konzepts durch Prioritätswarteschlangen
−
Prozesse sollen mit unterschiedlicher Priorität auf Wiedereintritt
in Monitor warten
−
•
Erweiterung der Operationen
wait (bedingung)
⇒
wait (bedingung, priorität)
signal (bedingung)
⇒
−
keine Änderung im Aufruf
−
aktiviert den Prozeß mit der
höchsten Priorität
beim Start eines "Monitorprogramms" werden die Initialisierungsprogramme der Monitore vor dem eigentlichen Programm ausgeführt
•
Monitorprozeduren sind bis zum Aufruf durch einen Prozeß passiv
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 98
Beispiel
−
Lösung des Erzeuger / Verbraucherproblems mit Hilfe eines
Monitors
−
Progamm besteht aus dem Monitor Puffer, den beiden Prozessen
Erzeuger und Verbraucher sowie dem Hauptprogramm
#define PufferGroesse
4
monitor Puffer
<Puffertyp> puf [PufferGroesse];
int
sIdx, lIdx,
AnzSatz;
condition
nichtleer, nichtvoll;
/* Schreib- / Leseindex
/* Anzahl Datensaetze
*/
*/
public procedure ablegen (Datensatz)
{
if (AnzSatz == PufferGroesse)
{
wait (nichtvoll);
};
puf [sIdx] = Datensatz;
sIdx = (sIdx + 1) % PufferGroesse;
AnzSatz++;
signal (nichtleer);
}
public procedure entnehmen (Datensatz)
{
if (AnzSatz == 0)
{
wait (nichtleer);
};
Datensatz = puf [lIdx];
lIdx = (lIdx + 1) % PufferGroesse;
AnzSatz--;
signal (nichtvoll);
}
{
/* Monitor-Hauptprogramm
sIdx
= 0;
lIdx
= 0;
AnzSatz = 0;
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
Betriebssysteme
Folie 3 - 99
int main (void)
{
COBEGIN
Erzeuger (1); ...; Erzeuger (n);
Verbraucher (1); ...; Verbraucher (m);
COEND;
}
void Erzeuger (int i)
{
for (;;)
{
empfange Datensatz;
Puffer.ablegen (Datensatz);
};
}
void Verbraucher (int i)
{
for (;;)
{
Puffer.entnehmen (Datensatz);
bearbeite Datensatz;
};
}
•
Implementierung des Monitors Puffer in Java
−
die Schlüsselwörter monitor und condition gibt es nicht
−
die Funktion signal heißt notify bzw. notifyAll
(Falls Threads auf verschiedene Bedingungen warten, muß notifyAll benutzt werden, da sonst u.U. ein
"falscher" Thread geweckt wird. Beispiel: Erzeuger / Verbraucherproblem mit zwei Erzeugern, einem
Verbraucher sowie einem Puffer für einen Datensatz. Zuerst will der Verbraucher lesen und wird
blockiert; dann will Erzeuger P1 einen Datensatz im Puffer ablegen; während dieser Aktion erfolgt ein
Kontextwechsel und Erzeuger P2 will ebenfalls einen Datensatz im Puffer ablegen; P2 wird blockiert;
jetzt führt P1 notify aus und verläßt den Monitor; falls notify zufällig P2 aufweckt, muß P2 sofort wait
aufrufen, da der Puffer belegt ist; dasselbe gilt, wenn P1 später einen weiteren Datensatz ablegen will ⇒
Blockade aller Threads (wird durch notifyAll vermieden))
−
wait, notify und notifyAll sind parameterlos
(Java unterstützt auch zeitlich begrenztes Warten ⇒ wait hat Parameter)
−
Monitor wird als Klasse realisiert
−
Monitor-Hauptprogramm ist der Konstruktor der Klasse
−
exklusive Benutzung der Monitorprozeduren wird über das
Schlüsselwort synchronized erreicht
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 100
Java-Programm für einen Puffer für ganze Zahlen
class intBuffer
{
private final static int BUF_SIZE = 5;
/* buffer size
private int
buffer [] = new int [BUF_SIZE];
private int
rIdx, wIdx;
/* read / write index
private boolean CellAvail,
/* free cells available
DataAvail;
/* data is available
*/
*/
*/
*/
public intBuffer ()
{
rIdx
= 0;
wIdx
= 0;
CellAvail = true;
DataAvail = false;
};
public synchronized void putData (int data)
{
while (!CellAvail)
{
try
{
wait ();
/* wait for free cells
*/
} catch (InterruptedException e)
{ System.err.println (e.toString ());
};
};
buffer [wIdx] = data;
wIdx
= (wIdx + 1) % BUF_SIZE;
DataAvail
= true;
if (wIdx == rIdx)
{
CellAvail = false;
};
notifyAll ();
/* signal data available */
};
public synchronized int getData ()
{
int data;
while (!DataAvail)
{
try
{
wait ();
} catch (InterruptedException e)
{ System.err.println (e.toString ());
};
};
data
= buffer [rIdx];
rIdx
= (rIdx + 1) % BUF_SIZE;
CellAvail = true;
if (rIdx == wIdx)
{
DataAvail = false;
};
notifyAll ();
return data;
};
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
/* wait for data
*/
/* signal free cells
*/
Betriebssysteme
•
Folie 3 - 101
getData ( ) liefert den Wert als Rückgabewert und nicht über die
Parameterliste
−
Java unterstützt nur call-by-value und nicht call-by-reference
−
falls Rückgabe des Wertes über die Parameterliste erfolgen soll,
muß eine Referenz auf ein Objekt als Parameter benutzt werden
(z.B. eine Klasse myInt)
•
getData ( ) mit Wertrückgabe über Parameterliste
class intBuffer
{
...
public synchronized void getData (myInt data)
{
while (!DataAvail)
{
try
{
wait ();
/* wait for data
} catch (InterruptedException e)
{
System.err.println (e.toString ());
};
};
data.value = buffer [rIdx];
rIdx
= (rIdx + 1) % BUF_SIZE;
CellAvail = true;
if (rIdx == wIdx)
{
DataAvail = false;
};
notifyAll ();
/* signal free cells
};
}
class myInt
{
public int value;
public myInt ()
{
this.value = 0;
};
public myInt (int value)
{
this.value = value;
};
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
Betriebssysteme
•
Folie 3 - 102
prinzipielle Implementierung von Monitoren über Semaphore
(für Lösung 1 des signal-Problems)
−
folgendes muß sichergestellt werden:
1) höchstens ein Prozeß darf eine Monitorprozedur ausführen
⇒ wechselseitiger Ausschluß für Monitorprozeduren
2) eine wait-Operation muß den Prozeß bei der entsprechenden
Bedingung blockieren
3) signal-Operation darf an beliebiger Stelle ausgeführt werden
⇒ aktueller Prozeß wird ggf. sofort blockiert und erhält
höchste Priorität zum Wiederbetreten des Monitors
4) falls ein Prozeß den Monitor freigibt, wird der nächste Prozeß in folgender Reihenfolge bestimmt
♦
zuerst wird überprüft, ob Prozesse bei einer signalOperation blockiert wurden
♦
danach wird überprüft, ob ein neuer Prozeß den Monitor
betreten will
⇒ Prozesse, die auf Bedingung warten, bleiben blockiert
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 103
für jeden Monitor werden die folgenden Variablen benötigt
(u.U. in der Form: <Monitorname>_<Variablenname>)
ex
binäres Semaphor; Anfangsinitialisierung: 1;
überwacht die exklusive Benutzung der Monitorprozeduren
prio
binäres Semaphor; Anfangsinitialisierung: 0;
für Prozesse, die eine signal-Operation ausführen
und blockiert werden müssen
prio_cnt
Zähler; Anfangsinitialisierung: 0;
gibt die Anzahl der in prio blockierten Prozesse an
cond [i]
Feld von binären Semaphoren; Anfangsinitialisierung: 0; für Prozesse, die auf Bedingungen warten
(Feld muß so groß gewählt werden, daß für jede Bedingungsvariable ein Element vorhanden ist)
cond_cnt [i] Feld von Zählern; Anfangsinitialisierung: 0;
geben die Anzahl der in cond [i] blockierten Prozesse an
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 104
1) jede Monitorprozedur wird in folgenden Code eingeschlossen
P (ex);
/* max. 1 Prozess im Monitor
*/
/* Wiedereintritt nach signal
*/
Monitorprozedur;
if (prio_cnt > 0)
{
V (prio);
}
else
{
V (ex);
};
/* neuem Proz. Zutritt erlauben */
2) für jeden Monitorbefehl wait (xyz) wird folgender Code erzeugt
cond_cnt [xyz]++;
if (prio_cnt > 0)
{
V (prio);
}
else
{
V (ex);
};
P (cond [xyz]);
cond_cnt [xyz]--;
/* Wiedereintritt nach signal
*/
/* neuem Proz. Zutritt erlauben */
/* Prozess blockieren
*/
3) für jeden Monitorbefehl signal (xyz) wird dieser Code erzeugt
if (cond_cnt [xyz] > 0)
{
prio_cnt++;
V (cond [xyz]);
/* wartenden Prozess aktivieren */
P (prio);
/* aktuellen Prozess blockieren */
prio_cnt--;
};
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 105
Implementierung ist einfacher, wenn signal-Operation nur als
letzte Anweisung einer Monitorprozedur vorkommen darf
(Lösung 2 des signal-Problems)
Monitorprozedur
⇒
P (ex);
Monitorprozedur;
wait (xyz)
⇒
cond_cnt [xyz]++;
V (ex);
P (cond [xyz]);
cond_cnt [xyz]--;
signal (xyz)
⇒
if (cond_cnt [xyz] > 0)
{
V (cond [xyz]);
}
else
{
V (ex);
};
warum steht die V(ex)-Anweisung nicht nach der Monitorprozedur sondern im else-Teil von signal ( )?
•
Monitore sind genauso mächtig wie Semaphore, d.h. Semaphore
können durch Monitore implementiert werden
Aufgabe 3-11:
Simulieren Sie ein binäres Semaphor und die zugehörigen Operationen
durch einen Monitor (mit der allgemeinen Monitorsyntax und nicht in Java!).
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 106
Simulation von Semaphoren durch Monitore in Java
class Semaphore
{
private int
value;
public Semaphore (int value)
{
if (value < 0)
{
this.value = 0;
System.err.print
("Semaphore: Init-value must be "
System.err.println ("positive");
}
else
{
this.value = value;
};
};
synchronized public void P ()
{
while (value == 0)
{
try
{
wait ();
} catch (InterruptedException e)
{
System.err.println (e.toString ());
};
};
value--;
}
synchronized public void V ()
{
value++;
notify ();
}
}
−
keine exakte Simulation der Semaphoroperationen
−
Befreiung eines wartenden Prozesses erfolgt durch MonitorMechanismus
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 3 - 107
genauere Simulation der Semaphoroperationen in Java
class Semaphore
{
private int
value;
private int
wait_cnt;
/* value of semaphore
/* # of threads in queue
public Semaphore (int value)
{
if (value < 0)
{
this.value = 0;
System.err.print
("Semaphore: Init-value must be "
System.err.println ("positive");
}
else
{
this.value = value;
};
this.wait_cnt = 0;
};
public synchronized void P ()
{
if (value > 0)
{
value--;
}
else
{
while (value == 0)
{
wait_cnt++;
try
{
wait ();
} catch (InterruptedException e)
{
System.err.println (e.toString ());
};
wait_cnt--;
};
};
}
public synchronized void V ()
{
if (wait_cnt > 0)
{
notify ();
}
else
{
value++;
};
}
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
Betriebssysteme
−
Folie 3 - 108
Verklemmung, wenn ein Prozeß im kritischen Bereich ist und
mindestens ein Prozeß in der P-Operation wartet
(Der Prozeß im kritischen Bereich sendet notify und reiht sich dann bei seiner nächsten P-Operation in die Warteschlange der anderen Prozesse ein. Der geweckte Prozeß
muß die Bedingung erneut evaluieren. Da der Wert der Semaphor-Variablen immer
noch Null ist, reiht er sich ebenfalls in die Liste der wartenden Prozesse ein ⇒ in Java
kann eine genauere Simulation der Semaphoroperation nicht so einfach realisiert
werden.)
−
Demonstration des Fehlers
...
Worker 5: try to enter critical section in cycle 0
P: semaphore value: 1 -> thread can pass
Worker 5: doing critical work in cycle 0
Worker 1: try to enter critical section in cycle 0
P: thread will be queued; waiting threads: 1
V: semaphore value: 0
waiting threads: 1
V: notify () performed
Worker 5: doing normal work in cycle 1
P: thread has dequeued; waiting threads: 0
P: thread will be queued; waiting threads: 1
(wegen erneuter Evaluation!)
Worker 3: try to enter critical section in cycle 0
P: thread will be queued; waiting threads: 2
Worker 2: try to enter critical section in cycle 0
P: thread will be queued; waiting threads: 3
Worker 0: try to enter critical section in cycle 0
P: thread will be queued; waiting threads: 4
Worker 4: try to enter critical section in cycle 0
P: thread will be queued; waiting threads: 5
Worker 5: try to enter critical section in cycle 1
P: thread will be queued; waiting threads: 6
< V e r k l e m m u n g >
Aufgabe 3-12:
Implementieren Sie eine Simulation durch Semaphore für die beiden
Java-Monitorlösungen zum Erzeuger/Verbraucherproblem.
(Rückgabe des Wertes in getData ( ) als Rückgabewert und über die Parameterliste).
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 109
Beurteilung des Lösungsansatzes "Monitor"
1) Monitore sind genauso mächtig wie Semaphore
2) Monitore erzwingen eine klare Programmstruktur
3) der Zugriff auf gemeinsame Betriebsmittel ist nur in der vorgesehenen Weise über Monitorprozeduren möglich
4) die Koordinierung der Prozesse ist Aufgabe des Programmierers
−
er entscheidet, welche Prozesse blockiert werden
−
er realisiert die Deblockierung
5) Lösungen mit Monitoren sind wesentlich weniger fehleranfällig als
solche mit Semaphoren
6) Monitore können einfach und effizient implementiert werden
7) geschachtelte Monitoraufrufe können zu einer nicht beabsichtigten
Sequentialisierung der Programme führen (⇒ u.U. ineffizient)
8) nicht in Rechnernetzen verwendbar, da das Monitorkonzept auf
lokalen Daten und lokalen Prozeduren beruht
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 110
Aufgabe 3-13:
a) Warum gibt es bei der Lösung des wechselseitigen Ausschlusses
durch globale Variablen Probleme?
b) Drei Prozesse benutzen gemeinsame Daten zum Datenaustausch.
Zeigen Sie, daß die Realisierung des wechselseitigen Ausschlusses
u.U. nicht fair ist, wenn nur ein Semaphor verwendet wird. Eine
Realisierung heißt fair, wenn garantiert ist, daß ein Prozeß nach
endlicher Zeit fortfahren kann.
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 111
3.5 Prozeßkommunikation
•
Austausch von Informationen zwischen kooperierenden Prozessen
(Interprozeßkommunikation, interprocess communications, IPC)
•
Kommunikationsmöglichkeiten
−
gemeinsame Speicherbereiche (shared memory)
♦
effizienter Datenaustausch zwischen parallelen Prozessen
(i.a. schnellste Möglichkeit der Datenübertragung zwischen zwei Prozessen)
♦
Prozesse müssen Datenkonsistenz selbst sicherstellen
(Synchronisation der Zugriffe z.B. über Semaphore)
♦
Realisierung direkt im Hauptspeicher (z.B. UNIX System V IPC)
oder über memory-mapped files (z.B. UNIX, Microsoft Windows)
♦
Funktionen
System V IPC UNIX mmap
Windows Win32-API
Speicherbereich
anlegen
shmget ()
open ()
lseek ()
write ()
CreateFileMapping ()
OpenFileMapping ()
Speicherbereich
anbinden
shmat ()
mmap ()
MapViewOfFile ()
Speicherbereich
freigeben
shmdt ()
munmap ()
UnmapViewOfFile ()
Speicherbereich
löschen
shmctl ()
close ()
unlink ()
CloseHandle ()
DeleteFile ()
(bei UNIX mmap wird die Größe einer neuen Datei dadurch festgelegt, daß mit lseek ( ) eine
Position in der Datei festgelegt wird, an der dann mit write ( ) ein Zeichen geschrieben wird)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 112
memory-mapped files
virtual memory
of process 1
file view 1
file view 2
physical memory
file on disk
virtual memory
of process 2
file mapping
object
file view 2
⋅
über mehrere mmap- / MapViewOfFile-Aufrufe können
verschiedene Teile der Datei an verschiedene Stellen im
virtuellen Speicher des Prozesses eingebunden werden
⋅
Pseudocode für direkten Dateizugriff
fd = open (...);
lseek (fd, offset, SEEK_SET);
read (fd, buf, len);
bearbeite Daten in "buf"
⋅
Pseudocode für Dateizugriff über mmap ( )
struct xyz {...} *buf;
fd = open (...);
buf = (struct xyz *) mmap (..., len, ..., fd, offset);
bearbeite Daten in Struktur "buf"
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 113
gemeinsame Dateien
♦
relativ langsam
♦
Prozesse müssen Datenkonsistenz selbst sicherstellen
(Synchronisation der Zugriffe z.B. über Semaphore oder Dateisperren)
−
Kommunikationskanäle, Pipes
♦
unidirektionaler Datenfluß zwischen zwei Prozessen
♦
keine Synchronisationsprobleme
♦
unnamed Pipes zur Interprozeßkommunikation zwischen
verwandten Prozessen (Vater - Sohn, Sohn - Sohn, ...)
(realisiert als Puffer im Speicherbereich des Betriebssystemkerns)
♦
named Pipes zur Interprozeßkommunikation zwischen
beliebigen Prozessen
(realisiert als FIFO-Datei im Dateisystem)
♦
unnamed und named Pipes für einige Anwendungen zu
langsam
♦
Zugriff auf jede Pipe erfolgt über Datei-Deskriptoren
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 114
Realisierung einer "bidirektionalen Pipe" durch zwei
"unidirektionale Pipes"
parent process
child process
fd1[1]
fd2[1]
fd2[0]
fd1[0]
pipe 1
data flow
pipe 2
data flow
operating system kernel
♦
Realisierung von "cat xyz | sort | more"
process cat
process sort
fd[1]
process more
fd[1]
fd[0]
pipe
fd[0]
pipe
data flow
data flow
operating system kernel
♦
unnamed und named Pipes gibt es in UNIX und Microsoft
Windows (Win32-API)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 115
Nachrichtenübermittlung (allgemeine Bemerkungen)
♦
Anzahl der kommunizierenden Prozesse
receiving
processes
sending
process
receiving
process
S
R
sending
process
R1
...
S
unicast
broadcast
receiving
processes
R
n
P
sending
process
R
1
...
S
multicast
Rn
Q
⋅
⋅
direkte Nachrichtenübermittlung
∗
1 : 1 Beziehung zwischen Sender und Empfänger
∗
1 : n Beziehung zwischen Sender und Empfängern
indirekte Nachrichtenübermittlung, z.B. über Puffer
(m : n Beziehung möglich)
♦
Art der Adressierung
⋅
direkte Adressierung
(der Sender gibt eine Zieladresse an; der Empfänger gibt eine Quelladresse
an oder empfängt von allen Prozessen Nachrichten)
⋅
indirekte Adressierung
(die Nachricht wird nicht an einen speziellen Prozeß sondern an eine Nachrichtenwarteschlange geschickt; Empfänger lesen aus der Nachrichtenwarteschlange)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 116
Synchronisation der Nachrichtenübertragung
⋅
sendender Prozeß wird blockiert bis Nachricht empfangen wurde oder wird nicht blockiert
⋅
empfangender Prozeß kann sofort weiterlaufen, wenn
Nachricht im Puffer vorhanden
⋅
falls keine Nachricht im Puffer ist, wird der empfangende Prozeß blockiert oder nicht blockiert
⋅
synchroner Nachrichtenaustausch
(blockierendes Senden / Empfangen; ein synchroner Nachrichtenaustausch
mit "leerer Nachricht" kann zur Synchronisation von Prozessen in Rechnernetzen benutzt werden; wird manchmal auch Rendezvous genannt)
⋅
asynchroner Nachrichtenaustausch
(nicht blockierendes Senden / Empfangen; maximale Parallelarbeit)
⋅
Mischform: nicht blockierendes Senden und blockierendes Empfangen
(wird häufig benutzt; Sender arbeitet mit maximaler Geschwindigkeit; Sender kann Nachrichten an mehrere Empfänger schicken; Empfänger kann i.a.
nur dann sinnvoll weiterarbeiten, wenn er die Nachricht empfangen hat)
⋅
blockierendes Senden / Empfangen kann einen Prozeß
beliebig lange blockieren
(z.B. bei Synchronisationsfehlern (beide Prozesse wollen senden oder
empfangen), bei Verlust von Nachrichten oder wenn ein Prozeß fehlerhaft
endet und keine weiteren Nachrichten sendet / empfängt)
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 117
Format der Nachricht
⋅
i.a. davon abhängig, ob das Nachrichtensystem nur auf
einem Rechner (z.B. UNIX System V message queues) oder in
einem Rechnernetz (z.B. Message-Passing Interface) zur Verfügung steht
⋅
feste Nachrichtengröße: häufig Nachrichtenkopf plus
Verweis auf Nachricht
⋅
variable Nachrichtengröße: i.a. Nachrichtenkopf inkl.
Nachricht
message type
destination ID
source ID
message header
message length
control information
message contents
or pointer to
message contents
message body
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 118
Datenformate (ganze Zahlen, Gleitkommazahlen, Zeichencodierung, ...)
higher memory
address
⋅
lower memory
address
byte 0
byte 1
byte 2
byte 3
big-endian
byte 3
byte 2
byte 1
byte 0
little-endian
big-endian: Motorala 680x0, Sun Sparc, SGI Mips, ...
little-endian: Intel 80x86, ...
⋅
Gleitkommazahlen: Anzahl Bits für Mantisse und Exponent i.a. unterschiedlich bei heterogenen Systemen
⋅
Zeichencodierung: ASCII, Unicode, ...
⋅
u.U. unterschiedliche Anordnung der Daten im Speicher
(alignment; z.B. müssen manche Datentypen auf geraden oder durch 4, 8
oder 16 teilbaren Adressen beginnen, damit ein (schneller) Datenzugriff
möglich ist; abhängig von der CPU; manchmal auch abhängig vom Compiler)
⋅
in heterogenen Rechnernetzen müssen alle Datenformate
zwischen plattformabhängiger und plattformunabhängiger Darstellung transformiert werden
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 119
Übertragung einer Nachricht
⋅
send (Adresse, Nachricht)
⋅
receive (Adresse, Nachricht)
sending
process
receiving
process
messagepassing
module
messagepassing
module
process ID
♦
message
Beispiel für eine Client / Server-Anwendung
client 1
PID = 87
type = 1
type = 87
client 2
PID = 65
type = 1
type = 65
client 3
PID = 93
type = 1
type = 93
message queue
type = 1
type = 65, 87, or 93
server
⋅
Nachrichtenauswahl über Nachrichtentyp
⋅
fester Typ für Server, unterschiedliche Typen für Clients
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 3 - 120
Mailbox-Systeme / Nachrichtenwarteschlangen
♦
indirekte Adressierung
♦
asynchrone Nachrichtenübertragung
♦
Betriebssystemfunktionen zum Anlegen / Entfernen einer
Mailbox / Nachrichtenwarteschlange
♦
Betriebssystemfunktionen zum Senden / Empfangen einer
Nachricht
♦
Mailbox / Nachrichtenwarteschlange mit m : n Beziehung
sending
processes
receiving
processes
S1
...
S
♦
R1
...
mailbox
R
m
n
Mailbox / Nachrichtenwarteschlange mit m : 1 Beziehung
(in diesem Fall wir die Mailbox oft Port genannt)
sending
processes
receiving
process
S1
...
S
port
R
1
m
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 121
Funktionen in Microsoft Windows (Win32-API)
Mailbox anlegen
CreateMailslot ()
Mailbox öffnen
CreateFile ()
Nachricht senden
WriteFile ()
Nachricht empfangen
ReadFile ()
Mailbox schließen
CloseHandle ()
⋅
der Server (Empfänger) legt eine Mailbox an
⋅
die Clients öffnen die Mailbox zum Schreiben
⋅
die Mailbox wird automatisch zerstört, wenn der letzte
Prozeß seine Mailbox schließt
⋅
ein Prozeß kann sowohl Server als auch Client sein, so
daß eine bidirektionale Kommunikation möglich ist
⋅
mehrere Prozesse können aus einer Mailbox lesen, wenn
sie den entsprechenden Deskriptor kennen
(z.B. Sohnprozesse eines Mailbox-Servers)
♦
Funktionen für Nachrichtenwarteschlangen in UNIX
POSIX message queues
System V message queues
mq_open ()
mq_close ()
mq_unlink ()
mq_send ()
mq_receive ()
msgget ()
msgctl ()
msgsnd ()
msgrcv ()
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 122
Sockets
⋅
Endpunkte für Kommunikationsverbindungen
⋅
jedem Socket ist genau ein Prozeß zugeordnet
⋅
für (heterogene) Rechnernetze geeignet
⋅
weitverbreitet (z.B. unter UNIX, Microsoft Windows, Java verfügbar)
⋅
Socket-Systemaufrufe in C bei verbindungsorientiertem
Protokoll (TCP/IP)
create a socket
Server
Client
socket ()
socket ()
bind a well-known port
number to the socket
bind ()
establish a queue for
connections
listen ()
accept a connection
accept ()
read from connection
recv ()
recvfrom ()
recvmsg ()
...
establish a
connection
data
transfer
connect ()
send ()
sendto ()
sendmsg ()
...
write to connection
send ()
sendto ()
sendmsg ()
recv ()
recvfrom ()
recvmsg ()
close a connection
close ()
close ()
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
⋅
Folie 3 - 123
Sockets in Java mit verbindungsorientiertem Protokoll
(TCP/IP)
Server
Client
ServerSocket ()
accept ()
InputStream ()
...
establish a
connection
data
transfer
OutputStream ()
close ()
Socket ()
OutputStream ()
...
InputStream ()
close ()
∗
vereinfachte Programmierung gegenüber C
∗
ServerSocket ( ) führt automatisch die Funktionen
socket ( ), bind ( ) und listen ( ) aus
∗
Socket ( ) führt automatisch die Funktionen socket ( )
und connect ( ) aus
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
♦
Folie 3 - 124
entfernter Prozeduraufruf (Remote Procedure Call, RPC)
⋅
bidirektionaler Nachrichtenaustausch
⋅
für (heterogene) Rechnernetze geeignet
⋅
Prozeß erhält Dienstleistung eines entfernten Prozesses
über "gewöhnlichen" Prozeduraufruf
⇒ wesentlich weniger fehleranfällig, da alle Sende-
und Empfangsoperationen und alle Datenkonvertierungen automatisch erzeugt werden
⇒ Netzwerk und Kommunikationsmechanismen sind
transparent für das Anwendungsprogramm
remote
server process
client
process
call
local
procedures
return
call
call
client stub
procedures
server stub
procedures
RPC
mechanism
RPC
mechanism
messagepassing
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
return
call
local
procedures
Betriebssysteme
⋅
Folie 3 - 125
Ablauf für synchronen, entfernten Prozeduraufruf
client
network
stub
procedure
call
server
stub
creates
message
waiting
RPC
analyzes
message
procedure
call
waiting
return
RPC return
return
analyzes
message
executes
local
procedure
creates
return
message
waiting
⋅
RPC-Schnittstelle wird in spezieller Sprache beschrieben
(Interface Definition Language, IDL, Microsoft Interface Definition
Language, MIDL, RPC Language, RPCL)
⋅
spezieller Compiler erzeugt aus der IDL-Bechreibung
u.a. die Platzhalter-Prozeduren (stubs), die lokale Prozeduraufrufe in RPC-Prozeduraufrufe überführen
(Unix: rpcgen, Microsoft Windows: MIDL Compiler)
⋅
IDL-Beschreibungen verschiedener Systeme sind i.a.
nicht kompatibel
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
⋅
Folie 3 - 126
Entwicklung eines RPC-Programms
1. RPC-Schnittstelle erstellen
2. Server implementieren, der Dienstleistungen erbringt
3. Client entwickeln, der die Dienstleistungen nutzt
develop the interface of remote procedures
RPCtest.idl
rpcgen
RPCtest_clnt.c
⋅
RPCtest.h
RPCtest_svc.c
client stub
procedure
server stub
procedure
RPCtest_client.c
RPCtest_server.c
develop the client
develop the server
RPCtest.idl erstellen
program RPCtest
{
version RPCtestVers2
{
string RetString (void)
= 1;
int
RetSquare (int)
= 2;
void
ShutDownServer (void) = 3;
} = 2;
version RPCtestVers1
{
string RetString (void) = 1;
} = 1;
} = 0x20000000;
const LatestRPCtestVers = 2;
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
⋅
Folie 3 - 127
der Server kann verschiedene Programmversionen unterstützen
⋅
Prozeduren werden fortlaufend numeriert
⋅
Programme können mit eindeutiger Programmnummer
offiziell registriert werden oder eine beliebige Nummer
aus dem Bereich 0x20 000 000 - 0x3f fff fff verwenden
⋅
rpcgen erzeugt aus der IDL-Datei die beiden Platzhalterdateien und eine Header-Datei
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
⋅
Folie 3 - 128
RPCtest.h (von rpcgen erzeugt)
#ifndef _RPCTST_H_RPCGEN
#define _RPCTST_H_RPCGEN
#include <rpc/rpc.h>
#define LatestRPCtestVers 2
#define RPCtest
0x20000000
#define RPCtestVers2
2
#if defined(__STDC__) || defined(__cplusplus)
#define RetString 1
extern char ** retstring_2(void *, CLIENT *);
extern char ** retstring_2_svc(void *,
struct svc_req *);
#define RetSquare 2
extern long * retsquare_2(long *, CLIENT *);
extern long * retsquare_2_svc(long *,
struct svc_req *);
#define ShutDownServer 3
extern void * shutdownserver_2(void *, CLIENT *);
extern void * shutdownserver_2_svc(void *,
struct svc_req *);
extern int rpctest_2_freeresult (SVCXPRT *, xdrproc_t,
caddr_t);
#else /* K&R C */
...
#endif /* K&R C */
#define RPCtestVers1 1
#if defined(__STDC__) || defined(__cplusplus)
extern char ** retstring_1(void *, CLIENT *);
extern char ** retstring_1_svc(void *,
struct svc_req *);
extern int rpctest_1_freeresult (SVCXPRT *, xdrproc_t,
caddr_t);
#else /* K&R C */
...
#endif /* K&R C */
#endif /* !_RPCTST_H_RPCGEN */
⇒ Prozeduren mit gleichem Namen müssen in ver-
schiedenen Programmversionen dieselbe Nummer
haben
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
⋅
Folie 3 - 129
RPCtest_clnt.c (von rpcgen erzeugt)
...
char ** retstring_2(void *argp, CLIENT *clnt)
{
static char *clnt_res;
memset((char *)&clnt_res, 0, sizeof(clnt_res));
if (clnt_call (clnt, RetString, (xdrproc_t) xdr_void,
(caddr_t) argp, (xdrproc_t) xdr_wrapstring,
(caddr_t) &clnt_res, TIMEOUT) != RPC_SUCCESS)
{
return (NULL);
}
return (&clnt_res);
}
...
⋅
RPCtest_client.c (vom Programmierer erzeugt)
...
#define PROT_TYP "tcp"
...
void rpctest_2 (char *host)
{
CLIENT *clnt;
char
**msg;
long
*square, value;
clnt = clnt_create (host, RPCtest, RPCtestVers2,
PROT_TYP);
if (clnt == (CLIENT *) NULL)
{
clnt_pcreateerror (...);
exit (-1);
};
printf ("\nRPC client: call remote function
msg = retstring_2 ((void **) NULL, clnt);
if (msg == (char **) NULL)
{
clnt_perror (...);
exit (-1);
};
printf ("Received string: %s\n", *msg);
...
clnt_destroy (clnt);
...\n");
}
⇒ über die Programm-, Versions- und Prozedurnum-
mer werden RPC-Prozeduren eindeutig identifiziert
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
⋅
Folie 3 - 130
RPCtest_svc.c (von rpcgen erzeugt)
...
static void rpctest_2(struct svc_req *rqstp,
register SVCXPRT *transp)
{
...
char
*result;
xdrproc_t _xdr_argument, _xdr_result;
char
*(*local)(char *, struct svc_req *);
switch (rqstp->rq_proc
{
...
case RetString:
_xdr_argument = (xdrproc_t) xdr_void;
_xdr_result = (xdrproc_t) xdr_wrapstring;
local = (char *(*)(char *, struct svc_req *))
retstring_2_svc;
break;
...
}
memset ((char *)&argument, 0, sizeof (argument));
if (!svc_getargs (transp, _xdr_argument,
(caddr_t) &argument))
{
svcerr_decode (transp);
return;
}
result = (*local)((char *)&argument, rqstp);
if (result != NULL &&
!svc_sendreply(transp, _xdr_result, result))
{
svcerr_systemerr (transp);
}
if (!svc_freeargs (transp, _xdr_argument,
(caddr_t) &argument))
{
fprintf (stderr, "%s", "unable to free arguments");
exit (1);
}
return;
}
...
int main (int argc, char **argv)
{
register SVCXPRT *transp;
...
transp = svctcp_create(RPC_ANYSOCK, 0, 0);
if (transp == NULL)
{
fprintf (stderr, "%s", "cannot create tcp service.");
exit(1);
}
if (!svc_register(transp, RPCtest, RPCtestVers2,
rpctest_2, IPPROTO_TCP))
{
fprintf (stderr, "%s", "unable to register"
" (RPCtest, RPCtestVers2, tcp).");
exit(1);
}
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 3 - 131
...
svc_run ();
fprintf (stderr, "%s", "svc_run returned");
exit (1);
}
⋅
RPCtest_server.c (vom Programmierer erzeugt)
...
char **retstring_2_svc (void *argp, struct svc_req *rqstp)
{
static char buffer [BUF_SIZE];
static char *bufPtr;
struct utsname sys_info;
/* system information */
printf ("RPC server version 2: RetString() called\n");
uname (&sys_info);
strcpy (buffer, "Greetings from ");
strncpy (buffer + strlen (buffer), sys_info.nodename,
BUF_SIZE - strlen (buffer));
strncpy (buffer + strlen (buffer), " (Version 2)",
BUF_SIZE - strlen (buffer));
bufPtr = buffer;
return &bufPtr;
}
...
Fachhochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß