2 Prozesse und Threads

Werbung
Betriebssysteme
Folie 2 - 1
2 Prozesse und Threads
•
•
Programm
−
statische Folge von Anweisungen und Daten
−
befindet sich im Allgemeinen als Datei auf einer Festplatte
−
der Dateiinhalt entspricht den Regeln des Betriebssystems für
ausführbare Programme (exe-Format, elf-Format, ...)
Prozess
−
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 Prozess
−
besitzt zugeordnete Speicherbereiche für den Programmcode,
seine statischen und dynamischen Daten sowie seinen Stack
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 2
Systemfunktionen zum Erzeugen/Beenden eines Prozesses
−
Windows 95/98/NT/2000/XP/... (Win32-API)
♦ CreateProcess (...)
♦ OpenProcess (...)
♦ ExitProcess (...)
♦ TerminateProcess (...)
−
UNIX
♦ 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 ()
...
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 3
Thread
−
sequentieller Ausführungsfaden innerhalb eines Prozesses
−
minimaler eigener Kontext
♦ CPU-Registersatz
♦ Stack
♦ Identifikationsnummer, Priorität, ...
•
−
alle Threads eines Prozesses benutzen denselben Adressraum
sowie weitere Betriebsmittel des Prozesses gemeinsam
−
jeder Prozess besitzt mindestens einen (initialen) Thread
Systemfunktionen zum Erzeugen/Beenden eines Threads
−
Windows 95/98/NT/2000/XP/... (Win32-API)
♦ CreateThread (...)
♦ ExitThread (...)
♦ TerminateThread (...)
−
POSIX Threads (UNIX, ...)
♦ pthread_create (...)
♦ pthread_exit (...)
♦ pthread_kill (...)
♦ pthread_cancel (...)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 4
Java Threads (plattform-übergreifend)
♦ Erweiterung der Klasse Thread
♦ Implementierung der Schnittstelle Runnable
♦ Aufruf der Methode start ( ) der Klasse Thread
♦ Aufruf der Methode interrupt ( ) der Klasse Thread
•
Struktur eines Prozesses mit mehreren Threads (multi-threaded
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
...
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Stack
Betriebssysteme
•
Folie 2 - 5
Prozesserzeugung unter UNIX
1) Betriebssystemkern muss eine Umgebung bereitstellen, in der
das Programm ausgeführt werden kann
2) der Prozess 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
(nach der Initialisierung besteht zwischen dem Prozess und dem Programm, das er
ausführt, keine weitere Verbindung)
4) das Betriebssystem legt im system data segment alle erforderlichen Datenstrukturen zur Verwaltung des Prozesses an bzw.
aktualisiert bereits vorhandene Datenstrukturen
(es wird z. B. ein Prozesskontrollblock für den Prozess angelegt und initialisiert, ein
Verweis auf den Prozesskontrollblock in die Prozesstabelle eingefügt und der Prozess
dann in die „bereit“-Warteschlange eingefügt)
5) der Prozess 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)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 6
Prozessverwaltung
−
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 (concurrent) ausgeführt werden
−
das Betriebssystem unterscheidet Anwendungs- bzw. 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 privilegierte Befehle des
Prozessors erlaubt sind
−
das Betriebssystem benötigt geeignete Datenstrukturen zur
Verwaltung der Prozesse
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 7
Prozessobjekte unter Windows NT/2000/XP/...
−
Prozessobjekte werden durch den Objektmanager erzeugt und
abgebaut
−
jedes Prozessobjekt enthält einen Kopf, der unter der
Verwaltung des Objektmanagers steht
−
Aufbau des Prozessobjekts
Objekttyp
Prozess
Attribute
Prozess-ID
Access Token
Basispriorität
Prozessorzugehörigkeit
Kontingentbeschränkung
Ausführungsdauer
I/O-Zähler
VM-Arbeitszähler
Exception/Debugging Ports
Beendigungsstatus
Dienste
Prozess erzeugen
Prozess öffnen
Prozessinformationen einholen
Prozessinformationen einstellen
Aktueller Prozess
Prozess beenden
VM reservieren/freigeben
VM lesen/schreiben
Virtuellen Speicher schützen
VM sperren und entsperren
VM-Informationen einholen
Virtuellen Speicher aktualisieren
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 8
2.1 Prozess/Thread-Zustände
•
Prozesszustände und die zugehörigen Zustandsübergänge
new
create
I/O completion,
event occurs
ready
blocked
dispatch
timeout,
yield
I/O wait,
event wait
running
terminate
suspend
suspend
exit
resume
suspended
ready
•
resume
suspend
I/O completion,
event occurs
suspended
blocked
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
•
Folie 2 - 9
Prozess/Thread-Verwaltung basiert auf Zustandsmodell
−
Grundzustände, erweiterte Zustände, Pseudozustände
−
Übergänge zwischen den Zuständen
−
Prozess/Thread kann einige Zustände im Laufe seines Lebens
mehrfach durchlaufen
typische Grundzustände
−
bereit (Synonyme: rechenwillig, rechenbereit, ausführbar,
ready, runnable)
♦ Prozess/Thread wartet nur auf Zuteilung eines Prozessors
♦ im Allgemeinen gibt es verschiedene Prioritätswarteschlangen
♦ neue Prozesse werden im Allgemeinen am Ende ihrer
Prioritätswarteschlange eingefügt
−
aktiv (Synonyme: rechnend, running)
♦ Prozess/Thread hat einen Prozessor zugeteilt bekommen
♦ auf einem Einprozessorsystem kann sich nur ein
Prozess/Thread in diesem Zustand befinden
−
blockiert (Synonyme: wartend, blocked, waiting)
♦ Prozess/Thread wartet auf ein Ereignis, z. B. Ein-/Ausgabe
♦ im Allgemeinen gibt es für verschiedene Wartebedingungen
verschiedene Warteschlangen
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 10
erweiterte Zustände
−
gibt es z. B. bei vielen UNIX-Systemen
−
suspendiert (Synonyme: ausgelagert, suspended)
♦ Prozess/Thread wurde angehalten und im Allgemeinen auf
externes Speichermedium ausgelagert
♦ häufig über zwei Zustände realisiert: suspendiert blockiert
und suspendiert bereit
♦ warum wird ein Prozess/Thread suspendiert ?
⋅
schlechte Systemleistung wegen kurzfristiger Überlast
⋅
Betriebssystem unterstützt keinen virtuellen Speicher, so
dass ganze Prozesse ein-/ausgelagert werden müssen
⋅
Benutzer will einen Prozess 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))
♦ wer kann einen Prozess/Thread suspendieren ?
⋅
ein Prozess/Thread (selbst) über entsprechende
Systemfunktionen
⋅
der Benutzer/das „Betriebssystem“
♦ Suspendierung durch Prozess/Thread kann schwerwiegende
Folgen haben
⋅
Blockierung mehrerer Prozesse/Threads, falls der suspendierte Prozess /Thread eine Betriebsmittelsperre hält
⋅
u. U. sogar Deadlock, falls die Funktion zur Fortsetzung
des Prozesses/Threads dieselbe Betriebsmittelsperre
benötigt
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 11
Zombie
♦ Zwischenzustand bevor ein Prozess das System verlässt
♦ Prozess hat seine Aufgabe erledigt und muss nur noch einen
Status an seinen Vaterprozess liefern
♦ Zombie-Prozesse belegen nur noch einen Eintrag in der
Prozesstabelle des Betriebssystems
•
Pseudozustände
−
Beginn (Synonyme: nicht existent, new, born)
♦ „notwendiger“ Zustand, um einen Prozess/Thread in das
System zu bringen
♦ wichtig ist nur der Zustandsübergang
−
Ende (Synonyme: nicht existent, exit, exited, dead)
♦ „notwendiger“ Zustand, um einen Prozess/Thread aus dem
System zu entfernen
♦ wichtig ist nur der Zustandsübergang
•
Zustandsübergänge
−
Beginn ⇒ bereit
♦ neuer Prozess/Thread wird erzeugt
♦ Speicherbereiche und Datenstrukturen für neuen Prozess
anlegen und initialisieren
(z. B. Codesegment, Datensegment, Prozesskontrollblock, ...)
♦ Prozess/Thread in Warteschlange „bereit“ einketten
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 12
bereit ⇒ aktiv
♦ Prozess/Thread wird einem freien Prozessor zugeteilt
♦ letzten Prozess/Thread-Kontext wiederherstellen
♦ Prozess/Thread-Zustand auf aktiv setzen
♦ Prozess/Thread starten
−
aktiv ⇒ bereit
♦ Prozessor wird dem Prozess/Thread entzogen
⋅
Zeitscheibe des Prozesses/Threads ist abgelaufen
⋅
Prozess/Thread höherer Priorität wurde rechenwillig
♦ Prozess/Thread-Kontext retten
♦ Prozess/Thread-Zustand auf „bereit“ setzen und Prozess/
Thread in Warteschlange „bereit“ einketten
−
aktiv ⇒ blockiert
♦ Prozess/Thread muss auf ein Ereignis warten
⋅
Beendigung einer Ein-/Ausgabeoperation
⋅
Beendigung eines erzeugten Prozesses/Threads
⋅
...
♦ Prozess/Thread-Kontext retten
♦ Prozess/Thread-Zustand auf „blockiert“ setzen und Prozess/
Thread in Warteschlange „blockiert“ einketten
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 13
aktiv ⇒ Ende
♦ Prozess/Thread verlässt das System
♦ Aufräumarbeiten durchführen (im Allgemeinen nur bei
Prozessen)
⋅
ggf. offene Dateien des Prozesses schließen
⋅
ggf. andere Betriebsmittel des Prozesses freigeben
♦ alle Datenstrukturen und Speicherbereiche freigeben (im
Allgemeinen nur bei Prozessen)
−
blockiert ⇒ bereit
♦ Ereignis ist eingetreten
♦ Prozess/Thread-Zustand auf „bereit“ setzen und
Prozess/Thread in Warteschlange „bereit“ einketten
−
blockiert ⇒ suspendiert blockiert, bereit ⇒ suspendiert bereit,
suspendiert blockiert ⇒ blockiert, suspendiert bereit ⇒ bereit
(im Allgemeinen nur bei Prozessen; suspendierte Threads werden im Allgemeinen
über Zustand „blockiert“ des Prozesses verwaltet)
♦ nur in Ausnahmefällen (siehe Zustand „suspendiert“)
♦ Prozess/Thread-Zustand ändern und Prozess/Thread in
entsprechende Warteschlange einketten
♦ aufwendig, da Datentransport zwischen Haupt- und Hintergrundspeicher erforderlich
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 14
suspendiert blockiert ⇒ suspendiert bereit
(im Allgemeinen nur bei Prozessen)
♦ Ereignis ist eingetreten
♦ Prozess kann ausgeführt werden, sobald er in Hauptspeicher
geladen worden ist
♦ Prozesszustand ändern und Prozess in Warteschlange
„suspendiert bereit“ einketten
−
aktiv ⇒ suspendiert bereit (nur bei Prozessen)
♦ kommt eigentlich nicht (mehr) vor
♦ könnte benutzt werden, wenn ein Prozess der Warteschlange
„suspendiert blockiert“ mit hoher Priorität rechenwillig wird,
der sehr viel Hauptspeicher benötigt und der aktuelle Prozess
viel Hauptspeicher belegt
♦ Prozesskontext retten
♦ Prozesszustand ändern und Prozess in Warteschlange
„suspendiert bereit“ einketten
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 15
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?
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
process
process
Betriebssysteme
•
Folie 2 - 16
Warteschlangenmodelle für Prozesszustände
−
Modell mit mehreren „blockiert“-Warteschlangen
partially executed
swapped-out processes
(in disk swap area)
swap in
new
process
ready
queue
dispatch
swap out
CPU
...
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
dispatch
CPU
...
CPU
batch
processes
process with higher
priority is ready,
time slice expired,
I/O finished, ...
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
terminated
process
Betriebssysteme
Folie 2 - 17
2.2 Prozesskontrollblock
•
das Betriebssystem verwaltet für jeden Prozess einen Prozesskontrollblock, der alle relevanten Informationen über den Prozess
enthält
(Synonyme: Prozesskennblock, Prozesskontext, process control block, task control block
Abkürzungen: PCB, TCB)
•
typische Komponenten eines Prozesskontrollblocks
−
Identifikatoren
♦ Prozessnummer/-name
♦ Nummer/Name des Benutzers, der den Prozess gestartet hat
♦ Gruppennummer
−
Stellung in der Prozesshierarchie
♦ Prozessnummer des Vaterprozesses
♦ Prozessnummern der Sohnprozesse
−
Prozessor-Zustandsinformation
♦ Register der Programmierumgebung
(Datenregister, Adressregister, Segmentregister, Indexregister, ...)
♦ Kontroll-, Steuer-, Statusregister
(System-/Benutzer-Stack-Register, Programmzähler, Prozessorstatuswort, ...)
−
Informationen zur Prozess-Steuerung
♦ Prozesszustand
♦ Priorität
♦ Informationen zur Interprozesskommunikation
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 18
♦ Zeiger auf Vorgänger und/oder Nachfolger
♦ Zeiger auf Threads des Prozesses
−
Rechte
♦ Zugriffsrechte auf Speicherbereiche, Dateien, ...
♦ Art der erlaubten Prozessorbefehle (privilegiert/nicht privilegiert)
−
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, ...)
Prozesskontext wird manchmal aufgeteilt in
−
Hardware-Kontext (i. w. Abbild der Prozessorregister)
−
Benutzer-Kontext (i. w. Speicherabbild des Prozesses)
−
System-Kontext (betriebssysteminterne Verwaltungsdaten)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 19
Prozesstabelle enthält Verweise auf Prozesskontrollblöcke
process control block
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
...
process
table
...
...
...
NULL
•
Beispiele
−
Linux
♦ Prozesstabelle bis Version 2.2.x
(linux/kernel/sched.c)
struct task_struct *task [NR_TASKS] = {&init_task, };
♦
„Prozesstabelle“ ab Version 2.4.x
struct task_struct *init_tasks [NR_CPUS] = {&init_task, };
⇒ Prozesstabelle wurde in Prozessliste je CPU überführt
−
Solaris
♦ struct proc
(/usr/include/sys/proc.h)
♦ struct _klwp
(/usr/include/sys/klwp.h)
♦ struct _kthread
(/usr/include/sys/thread.h)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 20
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 Prozess wird zusätzlich in der Prozesstabelle geführt
(bis Linux Version 2.2.x)
♦ jeder Prozess befindet sich zusätzlich in der Prozessliste
(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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
p_sibling
oldest
child
Betriebssysteme
Folie 2 - 21
Aufgabe 2-1:
a) Definieren Sie die Begriffe Programm, Prozess und Thread.
b) Beschreiben Sie stichwortartig die Prozesserzeugung unter UNIX.
Aufgabe 2-2:
Nennen Sie einige Komponenten des Prozesskontrollblocks und beschreiben Sie stichwortartig, warum die Komponenten vorhanden sind.
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 22
2.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 Prozessverhaltens ändern
−
wird z. B. aktiviert, wenn
♦ Prozesse erzeugt/beendet werden
♦ Prozesseigenschaften (z. B. Priorität) geändert werden
♦ eine Zeit abgelaufen ist oder bestimmte Systemfunktionen
aufgerufen werden, die indirekt in die Prozessverwaltung
eingreifen (z. B. zur Koordinierung/Synchronisation von Prozessen)
•
Dispatcher (Prozessumschalter, low-level scheduler) ist für den
Prozesswechsel (task switch, context switch) zuständig
−
−
muss sehr effizient implementiert werden, da Prozesswechsel im
10 bis 100 Millisekundenbereich vorkommen
wird z. B. aktiviert, wenn der aktive Prozess
♦ unterbrochen wird (z. B. Ablauf der Zeitscheibe)
♦ auf ein Ereignis warten muss (z. B. Ende einer Ein-/Ausgabeoperation)
♦ freiwillig auf den Prozessor verzichtet (spezielle Systemfunktion)
♦ endet
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 23
Aufgaben des Dispatchers
−
sichert den (gesamten) Hardware-Kontext des bisher aktiven
Prozesses in dessen Prozesskontrollblock
Die Intel Pentium Familie führt diese Aktion automatisch bei einem Prozesswechsel
durch. Ein Prozesswechsel wird hier z. B. durchgeführt, wenn ein „CALL“-Befehl auf
einen TSS-Deskriptor (task state segment) in der globalen Deskriptortabelle ausgeführt wird.)
−
ändert den Zustand des Prozesses („bereit“, „blockiert“, ...)
−
kettet den Prozess in die entsprechende Warteschlange ein
−
„sucht“ den nächsten zu aktivierenden Prozess
−
ändert den Zustand des Prozesses von „bereit“ auf „aktiv“
−
restauriert den (gesicherten) Hardware-Kontext des Prozesses
(Die Intel Pentium Familie führt diese Aktion automatisch bei einem Prozesswechsel
durch.)
−
„startet“ den Prozess
(Erfolgt im Allgemeinen automatisch durch die Rückkehr aus der Dispatcher-Routine,
da die Prozessorregister nach dem Kontextwechsel den früher unterbrochenen Zustand
des restaurierten Prozesses enthalten.)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 24
Aufgaben des Schedulers
−
Minimierung der Gesamtkosten des Systems
♦ möglichst hoher Durchsatz (Stapelbetrieb)
⇒ Verwaltungsaufwand reduzieren (wenig Prozesswechsel)
♦ möglichst kurze Antwortzeiten (Dialogbetrieb)
⇒ häufige Prozesswechsel
♦ 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 Prozessklassen oder Warte- und Rechenzeiten)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 25
„bereit“-Warteschlangen unter Solaris
scheduling
order
class-specific
priorities
first
scheduler
classes
interrupts
RT max
0
real-time
class (RT)
system
class (SYS)
last
+ TS max
0
- TS max
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 Prozess behält die CPU bis
♦ seine Zeitscheibe abgelaufen ist
♦ er sich selbst blockiert oder endet
♦ ein Prozess höherer Priorität rechenwillig wird
−
Prozesse derselben Priorität benutzen die Round-Robin-Strategie
−
für alle Prozesse gilt im Allgemeinen eine Standard-Zeitscheibe
−
Echtzeitprozesse können prozess-spezifische Zeitscheiben haben
−
Prioritäten von Echtzeitprozessen werden durch das System
nicht geändert (können nur vom Benutzer geändert werden)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 26
−
Prioritäten von Systemprozessen werden nur vom Betriebssystem verwaltet (können vom Benutzer nicht beeinflusst werden)
−
Prioritäten von Timesharing-Prozessen
♦ haben benutzer- und system-orientierte Komponente
♦ system-orientierte Komponente wird dynamisch geändert
⋅
Priorität wird erhöht, wenn der Prozess 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 Prozess höherer Priorität unterbrochen werden)
♦ benutzer-orientierte Komponente
⋅
kann vom Benutzer zwischen „-TS max“ und „TS max“
geändert werden (im Allgemeinen -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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 27
„bereit“-Warteschlangen unter Windows NT/2000/XP/...
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 jedes Mal 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, bei anderen Ein-/Ausgaben
♦ 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 Prozess/Thread aufgrund der Prozessorzugehörigkeit
nicht auf einem freien Prozessor ausgeführt werden kann, wird der Prozess/
Thread mit der nächst geringeren Priorität ausgewählt
geringste Priorität hat der „idle“-Prozess („system“)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 28
„bereit“-Warteschlangen für Java-Threads
high priority
Priority 10
Priority 9
thr1
Priority 8
Priority 7
Priority 6
normal priority
Priority 5
thr2
thr6
thr3
thr5
...
thrn
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)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 29
Einteilung der Scheduling-Strategien
−
nicht unterbrechende Verfahren
(Prozess bleibt solange aktiv, bis er sich selbst in den Zustand blockiert versetzt)
−
unterbrechende Verfahren
(dem Prozess kann der Prozessor entzogen werden; notwendig bei Echtzeit-Betriebssystemen)
♦ Prozess kann nur dann unterbrochen werden, wenn der
Prozessor im Benutzermodus arbeitet
(Standard bei älteren UNIX-Systemen)
♦ Prozess 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 30
Bewertung von Scheduling-Verfahren
−
CPU-Auslastung (utilization) bzw. Effizienz maximieren
(CPU sollte nach Möglichkeit immer Prozesse ausführen)
−
Fairness
(jeder Prozess erhält einen fairen Anteil der CPU-Zeit; kein Prozess wird beliebig
lange an seiner Arbeit gehindert)
−
Durchsatz (throughput) maximieren
♦ Anzahl der pro Zeiteinheit fertig gestellten 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 bzw. Verweilzeit (turnaround time) minimieren
♦ 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) minimieren
♦ Wartezeit in der „bereit“-Warteschlange
♦ nur diese Zeit kann vom Scheduler direkt beeinflusst werden
−
Antwortzeit (response time) minimieren
♦ wichtige Zeitspanne für interaktive Prozesse
♦ Zeit zwischen Eingabe und Reaktion auf Eingabe
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 31
CPU-Auslastung kann bei vielen Systemen grafisch dargestellt
werden
Windows XP
SunOS
Linux: X-osview
Linux: GNOME-Systemmonitor
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 32
mögliche Scheduling-Verfahren
−
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
♦ im Allgemeinen gute Auslastung der CPU
♦ kann zu langen Warteschlangen mit E/A-intensiven Prozessen führen, wenn ein rechenintensiver Prozess 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.
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 33
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.)
−
Shortest-Job-First (SJF), Shortest-Processing-Time (SPT)
(der Prozess 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 im Allgemeinen 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)
−
Shortest-Remaining-Time (SRT), Shortest-RemainingProcessing-Time (SRPT)
(im Prinzip SPT mit Unterbrechung, d. h. falls ein neuer Prozess eintrifft, dessen Rechenzeit geringer ist als die verbleibende Rechenzeit des gerade aktiven Prozesses,
dann wird ein Prozesswechsel durchgeführt)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 34
Round-Robin (RR)
(Der Prozess 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 Prozesswechsel
⇒ 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
−
Shortest-Elapsed-Time (SET), Multilevel Feedback Queue
Scheduling
(im Prinzip RR mit Prioritätswarteschlangen; neuer Prozess 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)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 35
Aufgabe 2-3:
Die folgenden Prozesse kommen in der Reihenfolge (P1, P2, P3, P4, P5)
zum Zeitpunkt 0 an. Zeichnen Sie die Gantt-Diagramme für die zeitliche
Zuordnung der Prozesse auf einen Prozessor für die Scheduling-Verfahren FCFS, SJF, RR (eine Zeitscheibe entspricht einer Zeiteinheit) und
ein nicht unterbrechendes Verfahren mit Prioritätswarteschlangen (kleine Prioritätsnummer entspricht hoher Priorität). Geben Sie für die verschiedenen Verfahren die Durchlaufzeit, die mittlere Durchlaufzeit, die
Wartezeit und die mittlere Wartezeit der Prozesse an.
Prozess
Rechenzeit
Priorität
(in Zeiteinheiten)
P1
P2
P3
P4
P5
10
1
2
1
5
3
1
3
4
2
Aufgabe 2-4:
Die Prozesse P1, P2, P3, P4 und P5 treffen in dieser Reihenfolge ein. Die
genauen Ankunfts- und Ausführungszeiten können der nachfolgenden
Tabelle entnommen werden. Zeichnen Sie die Gantt-Diagramme für die
zeitliche Zuordnung der Prozesse auf einen Prozessor für die Scheduling-Verfahren FIFO, SPT, SRT und RR (eine Zeitscheibe entspricht
zwei Zeiteinheiten). Geben Sie für die verschiedenen Verfahren die
mittlere Verweilzeit der Prozesse im System an.
Prozess
P1
P2
P3
P4
P5
Ankunftszeit Ausführungszeit
0,0
11
0,2
2
1,1
6
3,1
1
4,1
4
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 36
2.4 Prozess-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
Prozess/Thread 1
Prozess/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
Prozess/Thread 1
Prozess/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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
•
Folie 2 - 37
Problem des obigen Beispiels
−
Fehler kann auftreten, muss aber nicht auftreten
−
Fehler ist schwer reproduzierbar, da abhängig von Nebenbedingungen (z. B. Systemlast)
−
typischer Synchronisationsfehler, wenn ein Prozess 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 muss 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 Ausschluss (mutual exclusion)
♦ Menge von konkurrierenden Prozessen/Threads um ein
Betriebsmittel
♦ einer wird ausgewählt und alle anderen werden gesperrt
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 38
wechselseitiger Ausschluss wird im Allgemeinen 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 Prozess innerhalb des kritischen Abschnitts befinden
2) Lebendigkeit: wenn ein Prozess in einen kritischen Abschnitt
eintreten will, wird ihm dies nach endlicher Zeit gestattet
⇒ jeder Prozess verbringt nur eine endliche Zeit im kritischen
Abschnitt (keine Programmierfehler durch Endlosschleifen, ...)
⇒ das Protokoll garantiert Fairness
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 39
Realisierungsmöglichkeiten für wechselseitigen Ausschluss
−
kurzfristige Deaktivierung des Unterbrechungssystems
(verhindert Unterbrechungen und damit Prozesswechsel; 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. muss der Wert im
Vorprotokoll zyklisch abgefragt werden, bis der kritische Abschnitt frei ist (aktives
Warten, busy waiting), so dass 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 dass 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 dass der Einsatz vom Compiler überwacht werden kann; stehen nur in wenigen Sprachen zur Verfügung)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 40
2.4.1 Globale Variablen
•
Synchronisation der Prozesse/Threads ohne Betriebssystem-/Compiler-Unterstützung
(in Zukunft wird nur noch der Begriff Prozess 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 2.4)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 41
möglicher Ablauf
−
das Betriebsmittel sei frei und Programm A sei aktiv
−
nachdem Programm A die while-Schleife erfolgreich durchlaufen hat, wird ein Prozesswechsel 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ß, dass ihm zwischenzeitlich der Prozessor entzogen worden ist, belegt und benutzt es das Betriebsmittel
ebenfalls
⇒ die Sperrsynchronisation durch eine globale Variable hat versagt
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 42
Lösung 1 für den wechselseitigen Ausschluss
volatile int turn;
/* Nr. des Prozesses, der kritischen */
/*
Abschnitt betreten darf
*/
int main (void)
{
turn = 1;
COBEGIN
worker1;
worker2;
COEND;
}
/* ========
Hauptprogramm
/* beide Prozesse starten
void worker1 (void)
/*
{
for (;;)
{
while (turn == 2)
/*
{
/*
;
/*
}
/*
kritischer Abschnitt;
turn = 2;
/*
sonstige Arbeit;
/*
}
}
==========
void worker2 (void)
/*
{
for (;;)
{
while (turn == 1)
/*
{
/*
;
/*
}
/*
kritischer Abschnitt;
turn = 1;
/*
sonstige Arbeit;
/*
}
}
==========
•
======== */
Prozess 1
*/
========== */
| Vorprotokoll
|
|
(p2 hat Erlaubnis -> warten)
|
*/
*/
*/
*/
| Nachprot. (p2 Erlaub. erteilen) */
hier kann Proz. u.U. beendet werd.*/
Prozess 2
========== */
| Vorprotokoll
|
|
(p1 hat Erlaubnis -> warten)
|
*/
*/
*/
*/
| Nachprot. (p1 Erlaub. erteilen) */
hier kann Proz. u.U. beendet werd.*/
Code zwischen COBEGIN und COEND wird parallel ausgeführt
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 43
Diskussion der Lösung 1
•
Lösung garantiert wechselseitigen Ausschluss, da Prozess i nur dann
in seinen kritischen Abschnitt eintritt, wenn turn == i ist
•
Lösung ist verklemmungsfrei, da der Prozess, dessen Nummer in
turn gespeichert ist, immer weiterarbeiten kann
•
Lösung stellt sicher, dass kein Prozess 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 Prozess kann einen schnellen
behindern
2) falls Prozess worker1 den kritischen Abschnitt durchlaufen und
für Prozess worker2 freigegeben hat, kann er ihn nie mehr betreten, wenn Prozess worker2 inzwischen z. B. aufgrund eines Fehlers im unkritischen Bereich beendet wurde
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 44
Implementierung des obigen Programms
/* Solution 1 for the synchronization of two concurrent workers
* with global variables.
*
* For Windows 95/98/NT/ME/2000/XP/... (Win 32 API) 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 95/98/NT/ME/2000/XP/...:
*
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: 21.08.2007
*
*/
#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 (long nr);
void worker2 (long nr);
/* turn: number of the worker which is allowed to enter the critical
*
section next
* "volatile" is necessary to prevent that modern compilers optimize
* a statement away when its assignment isn't used or that they change
* the sequence of statements which contain this variable.
*/
volatile 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;
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
Betriebssysteme
Folie 2 - 45
/* 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 (long nr)
{
int i;
/* loop variable
*/
for (i = 0; i < NUM_CYCLES; ++i)
{
printf ("Worker %ld: doing normal work in cycle %d.\n", nr, i);
/* simulate some normal work
*/
SLEEP ((unsigned int) rand () % MAX_NTIME);
printf ("Worker %ld: try to enter critical section in cycle %d.\n",
nr, i);
while (turn == 2)
{
printf ("Worker %ld: waiting in cycle %d.\n", nr, i);
SLEEP (1);
}
printf ("Worker %ld: 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 (long nr)
{
int i;
/* loop variable
*/
for (i = 0; i < NUM_CYCLES; ++i)
{
printf ("Worker %ld: doing normal work in cycle %d.\n", nr, i);
/* simulate some normal work
*/
SLEEP ((unsigned int) rand () % MAX_NTIME);
printf ("Worker %ld: try to enter critical section in cycle %d.\n",
nr, i);
while (turn == 1)
{
printf ("Worker %ld: waiting in cycle %d.\n", nr, i);
SLEEP (1);
}
printf ("Worker %ld: doing critical work in cycle %d.\n", nr, i);
/* simulate some critical work
*/
SLEEP ((unsigned int) rand () % MAX_CTIME);
turn = 1;
/* release critical region
*/
}
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 46
„worker1“ und „worker2“ sind im Wesentlichen identisch, so dass
folgende Vereinfachung vorgenommen werden kann
int main (void)
{
...
/* start concurrent workers
thr_id [0] = parallel ((PtrFunc_t) worker, 0);
thr_id [1] = parallel ((PtrFunc_t) worker, 1);
...
}
void worker (long nr)
{
int i;
/* loop variable
*/
*/
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);
while (turn != nr)
{
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);
turn = (nr + 1) % NUM_THR;
/* release critical region
*/
}
}
void prt_msg (char *name, long id, char *text, int cycle)
{
P (screen);
printf ("%s %ld: %s %d.\n", name, id, text, cycle);
V (screen);
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 47
Lösung 2 für den wechselseitigen Ausschluss
•
anstelle der gemeinsamen Variablen turn wird jedem der beiden
Prozesse worker_i eine globale Variable ci zugeordnet
volatile int c1, c2; /* ci = 0: P. ist im krit. Abschn. o. */
/*
will ihn betreten
*/
/* ci = 1: P. ist im "normalen" Progr.*/
int main (void)
{
c1 = 1;
c2 = 1;
COBEGIN
worker1;
worker2;
COEND
}
/* ========
Hauptprogramm
/* beide Prozesse starten
========= */
*/
void worker1 (void) /* ========== Prozess 1 =========== */
{
for (;;)
{
c1 = 0;
/* | Vorprotokoll (Abschn. reserv.)
*/
while (c2 == 0) /* |
*/
{
/* |
*/
;
/* |
ggf. warten bis Abschn. frei
*/
}
/* |
*/
kritischer Abschnitt;
c1 = 1;
/* | Nachprotokoll (Abschn. freigeben)*/
sonstige Arbeit;
}
}
void worker2 (void) /* ========== Prozess 2 =========== */
{
for (;;)
{
c2 = 0;
/* | Vorprotokoll (Abschn. reserv.)
*/
while (c1 == 0) /* |
*/
{
/* |
*/
;
/* |
ggf. warten bis Abschn. frei
*/
}
/* |
*/
kritischer Abschnitt;
c2 = 1;
/* | Nachprotokoll (Abschn. freigeben)*/
sonstige Arbeit;
}
}
•
diskutieren Sie die Lösung
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 48
Lösung 3 für den wechselseitigen Ausschluss
•
Problem von Lösung 2 soll dadurch behoben werden, dass der
Prozess kurzfristig darauf verzichtet, den kritischen Abschnitt zu
betreten, wenn die globale Variable des anderen Prozesses anzeigt,
dass er im kritischen Abschnitt ist bzw. ihn betreten will
volatile 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;
worker2;
COEND;
}
/* =========
void worker1 (void)
{
for (;;)
{
normale Arbeit;
c1 = 0;
while (c2 == 0)
{
c1 = 1;
...
c1 = 0;
}
kritischer Abschnitt;
c1 = 1;
}
}
/* ===========
void worker2 (void)
{
...
}
•
Hauptprogramm
========== */
/* beide Prozesse starten
/*
/*
/*
/*
/*
/*
/*
Prozess 1
*/
============ */
| Vorprotokoll
|
|
|
Verzicht auf krit. Abschnitt
|
einen Augenblick warten
|
neuer Versuch
|
/* | Nachprotokoll
/* ===========
Prozess 2
/* analog worker1
diskutieren Sie die Lösung
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
*/
*/
*/
============ */
*/
Betriebssysteme
Folie 2 - 49
Lösung 4: Algorithmus von Dekker
•
Kombination der ersten und dritten Lösung zum wechselseitigen
Ausschluss
volatile int turn,
c1, c2;
int main (void)
{
turn = 1;
c1
= 1;
c2
= 1;
COBEGIN
worker1;
worker2;
COEND;
}
void worker1 (void)
{
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 (void)
{
...
}
/* 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
=========== */
*/
Betriebssysteme
Folie 2 - 50
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 Prozess im Konfliktfall die höhere
Priorität hat und welcher höflich sein muss
die Probleme der ersten drei Lösungen beruhen darauf, dass ein
Prozess nach jeder Operation unterbrochen werden kann
⇒ Überprüfung der Betriebsmittel-Variablen und ggf. Belegen des
Betriebsmittels muss in einer unteilbaren Operation erfolgen
•
alle Lösungen haben den Nachteil, dass die Prozesse aktiv auf ein
Ereignis warten und damit sinnlos Prozessorleistung verschwenden
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 51
Aufgabe 2-5:
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
Aufgabe 2-6:
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 52
Beurteilung des Lösungsansatzes „Globale Variable“
1) Programme hängen sehr stark voneinander ab
•
die Lösung muss 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
Prozess auf Rechner A keine Variable auf Rechner B
modifizieren kann
5) fehleranfällig, da der wechselseitige Ausschluss durch den Anwendungsprogrammierer gewährleistet werden muss
⇒ es sind mächtigere Sprachmittel für die Programmierung nebenläufiger Programme erforderlich
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 53
2.4.2 Semaphore
•
Sprachmittel zur Koordinierung paralleler Programme (Dijkstra 1965)
−
stammt aus dem Griechischen: Zeichenträger, Signalmast, optischer Telegraph (z. B. in der Schifffahrt), ...
−
Semaphore sind in verschiedenen Betriebssystemen implementiert (UNIX, Windows NT/2000/XP/..., ...)
−
aktives Warten wird vermieden
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
•
•
Folie 2 - 54
Definition: Ein Semaphor s ist eine Variable, die nur nicht-negative
ganzzahlige Werte annehmen kann
−
manchmal wird die Semaphor-Variable auch so definiert, dass
sie negative Werte annehmen darf
−
intern besteht ein Semaphor im Allgemeinen aus einer
Zählervariablen und einer Warteschlange
erlaubte Manipulationen
−
einmalige Initialisierung der Variablen
−
unteilbare Elementaroperationen P(s) und V(s)
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 im Allgemeinen FIFO (diese Strategie ist aber
nicht zwingend vorgeschrieben)
4) Semaphore und ihre Operationen sind normalerweise Bestandteil
des Betriebssystemkerns, so dass die Prozesszustände einfach
durch die Prozessverwaltung 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.
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 55
Systemfunktionen für Semaphore
−
Windows 95/98/NT/2000/XP/... (Win32-API)
Operation
Objekt erzeugen
−
Critical Section
Mutex
(prozess-intern)
(prozess-übergreifend)
InitializeCriticalSection (...)
Semaphore
CreateMutex (...)
CreateSemaphore (...)
OpenMutex (...)
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 (...)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
usw.
Betriebssysteme
•
Folie 2 - 56
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, ...
*/
}
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
Betriebssysteme
•
Folie 2 - 57
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
=========== */
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 58
Implementierung des Programms in C (semaphor.c)
#include
#include
#include
#include
#define
#define
#define
#define
#define
<stdio.h>
<stdlib.h>
<time.h>
"parallel.h"
NUM_THR
NUM_CYCLES
MAX_NTIME
MAX_CTIME
SEM_VAL
6
30
4
2
1
/*
/*
/*
/*
/*
# of concurrent threads
# of normal/critical cycles
max. time for normal work
max. time for critical work
initial semaphore value
*/
*/
*/
*/
*/
void worker (long nr);
void prt_msg (char *name, long id, char *text, int cycle);
semaphore ex,
screen;
/* exclusive access
*/
/* synchronize output on screen*/
int main (void)
{
ThrID_t thr_id [NUM_THR];
int
i;
/* ID's of concurrent threads
/* loop variable
srand ((unsigned int) time ((time_t) NULL));
screen = init_sem (SEM_VAL);
/* initialize semaphores
ex
= init_sem (SEM_VAL);
/* start concurrent threads
for (i = 0; i < NUM_THR; ++i)
{
thr_id [i] = parallel ((PtrFunc_t) worker, i);
}
/* wait until all concurrent threads have terminated
join_all (thr_id, NUM_THR);
rel_allsem ();
/* release semaphore
return 0;
*/
*/
*/
*/
*/
*/
}
void worker (long nr)
{
int i;
/* loop variable
*/
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);
}
}
void prt_msg (char *name, long id, char *text, int cycle)
{
P (screen);
printf ("%s %ld: %s %d.\n", name, id, text, cycle);
V (screen);
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 59
Implementierung des Programms in Java (ab Java 5)
Datei: SemaphorOneMain.java
import java.util.concurrent.Semaphore;
public class SemaphorOneMain
{
public static void main (String args[])
{
final int
NUM_THR = 6;
/* # of threads
final int
SEM_VAL = 1;
/* initial sem. value
final Semaphore ex
= new Semaphore (SEM_VAL, true);
*/
*/
WorkerOne thr[] = new WorkerOne[NUM_THR];
/* create all threads
for (int i = 0; i < NUM_THR; ++i)
{
thr[i] = new WorkerOne (i, ex);
thr[i].start ();
}
/* Join all terminating threads
for (int i = 0; i < NUM_THR; ++i)
{
try
{
String thrName = thr[i].getName ();
*/
*/
thr[i].join ();
System.out.println ("SemaphorOneMain: '" + thrName +
"' terminated.");
} catch (InterruptedException e)
{
System.err.println ("SemaphorOneMain: received unexpected " +
"InterruptedException while joining "+
"'" + thr[i].getName () + "'.");
}
}
}
}
Datei: WorkerOne.java
import java.util.concurrent.Semaphore;
class WorkerOne extends Thread
{
private int
thrID;
private final Semaphore ex;
private boolean
isNotInterrupted;
/* ID of this thread
/* interrupt received? */
public WorkerOne (int thrID, Semaphore ex)
{
this.thrID = thrID;
this.ex
= ex;
setName ("WorkerOne-" + Integer.toString (thrID));
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
Betriebssysteme
public void
{
final int
final int
final int
Folie 2 - 60
run ()
MAX_NTIME = 3000;
MAX_CTIME = 1000;
NUM_CYCLES = 30;
/* max. 3 s normal work
*/
/* max. 1 s crit. work
*/
/* # of normal/critical cycles */
String name = getName () + ": ";
isNotInterrupted = true;
for (int i = 0; (i < NUM_CYCLES) && isNotInterrupted; ++i)
{
System.out.println (name + "doing normal work in cycle " + i);
try
/* simulate it
{
Thread.sleep ((int) (Math.random () * MAX_NTIME));
} catch (InterruptedException e)
{
System.err.println (getName () + ": Received " +
"unexpected InterruptedException.");
isNotInterrupted = false;
/* terminate loop
}
if (isNotInterrupted)
{
System.out.println (name + "try to enter critical section " +
"in cycle " + i);
/* check if thread has been interrupted in acquire-operation
try
{
ex.acquire ();
} catch (InterruptedException e)
{
System.err.println ((Thread.currentThread ()).getName () +
": Interrupted while waiting for " +
"resources in acquire-operation.");
isNotInterrupted = false;
/* terminate loop
}
}
if (isNotInterrupted)
{
try
/* simulate critical work
{
System.out.println (name + "doing critical work in " +
"cycle " + i);
Thread.sleep ((int) (Math.random () * MAX_CTIME));
} catch (InterruptedException e)
{
System.err.println (getName () + ": Received " +
"unexpected InterruptedException.");
isNotInterrupted = false;
/* terminate loop
}
ex.release ();
}
}
}
public String toString ()
{
return getName ();
}
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
*/
Betriebssysteme
•
Folie 2 - 61
Programmänderungen bei Endlosschleife
Datei: SemaphorTwoMain.java
...
public class SemaphorTwoMain
{
...
/* wait some time before terminating all threads
try
{
Thread.sleep (WAIT_TIME);
} catch (InterruptedException e)
{
System.err.println ("SemaphorTwoMain: received unexpected " +
"InterruptedException.");
}
/* Terminate all threads.
for (int i = 0; i < NUM_THR; ++i)
{
System.out.println ("SemaphorTwoMain: I stop thread '" +
thr[i].getName () + "'.");
((WorkerTwo) thr[i]).stopThread ();
}
/* Join all terminating threads
...
}
Datei: WorkerTwo.java
...
class WorkerTwo extends Thread
{
...
public void run ()
{
...
isNotInterrupted = true;
for (int i = 0; isNotInterrupted; ++i)
{
...
}
}
public void stopThread ()
{
try
{
isNotInterrupted = false;
this.interrupt ();
} catch (SecurityException e)
{
System.err.println (getName () + ": Caught unexpected " +
"security exception.");
}
}
...
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
Betriebssysteme
Folie 2 - 62
•
neben dem wechselseitigen Ausschluss ist die Synchronisation von
Prozessen eine wichtige Aufgabe in der parallelen Programmierung
•
Beispiele
−
Erzeuger/Verbraucher-Problem (producer/consumer problem)
♦ ein Prozess 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 muss 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 63
Leser/Schreiber-Problem (readers/writers problem)
♦ beliebig viele Prozesse dürfen gleichzeitig Daten lesen
♦ zu jedem Zeitpunkt darf nur ein Prozess 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 muss immer
aktuell sein, ...)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 64
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 Prozess-Synchronisation (starvation) wird untersucht und veranschaulicht
−
hat darüber hinaus keine praktische Bedeutung
Hochschule 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 2 - 65
Betriebssysteme
•
Folie 2 - 66
Lösung 3 des Erzeuger /Verbraucher-Problems
(Lösung 2 fehlt nicht, sondern wird im Rahmen der Diskussion der Lösung 1 erstellt)
−
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;
}
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
*/
*/
*/
Betriebssysteme
•
Folie 2 - 67
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 muss nur deshalb warten, weil ein Schreiber darauf
wartet, dass der letzte Leser fertig wird
−
Hinweise zum Programm
1) es wird ein Semaphor s für den wechselseitigen Ausschluss
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
Hochschule 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 2 - 68
Betriebssysteme
•
Folie 2 - 69
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 Abschnitt betreten
⇒ er muss eine eventuell vorhandene Leserwarteschlange nicht
berücksichtigen
(eine Leserwarteschlange kann entstehen, wenn mehrere Schreiber auf den
Zugriff zum kritischen Abschnitt warten)
−
ein Schreiber muss 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;
}
Hochschule 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 2 - 70
Betriebssysteme
Folie 2 - 71
Aufgabe 2-7:
Gegeben sei der folgende Pseudocode für das Erzeuger-/Verbraucherproblem:
int
n;
semaphore s, delay;
int main (void)
{
ThrID_t thr_id [2];
n
= 0;
s
= init_sem(1);
delay = init_sem(0);
thr_id [0] = parallel ((PtrFunc_t) erzeuger, 0);
thr_id [1] = parallel ((PtrFunc_t) verbraucher, 1);
...
}
void erzeuger (int nr)
{
for (;;)
{
erzeuge Datensatz;
P(s);
lege Datensatz im Puffer ab;
n = n + 1;
if (n == 1)
{
V(delay);
}
V(s);
}
}
void verbraucher (int nr)
{
int m;
P(delay);
for (;;)
{
P(s);
entnehme Datensatz aus Puffer;
n = n - 1;
m = n;
V(s);
verbrauche Datensatz;
if (m == 0)
{
P(delay);
}
}
}
Warum würde das Programm fehlerhaft arbeiten, wenn die Anweisung
„if (m == 0) ...“ in der Funktion verbraucher durch die Anweisung „if (n == 0) ...“ ersetzt wird? Welcher Fehler tritt auf?
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 72
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, dass kein Philosoph verhungern muss. Selbstverständlich ist vorausgesetzt, dass ein
essender Philosoph gelegentlich satt wird, seine Gabeln säubert und
sie zurücklegt.
0
4
0
4
1
3
3
1
2
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
2
Betriebssysteme
•
Folie 2 - 73
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 muss 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 dass 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]);
}
}
Hochschule 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.
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 2 - 74
Betriebssysteme
•
Folie 2 - 75
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 Prozess-Systeme im Allgemeinen nicht
reversibel sind
(z. B. automatische Blockade bei P-Operation)
♦ alle Philosophen könnten ihre rechten Gabeln im gleichen
Takt aufnehmen und zurücklegen
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 76
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
/* exklusiver Zugriff
/* rechte Gabel
/* linke Gabel
*/
*/
*/
Betriebssysteme
•
Folie 2 - 77
Lösung 3 des Philosophen-Problems
−
Problem kann nicht mit Semaphor-Variablen allein gelöst werden
−
ein Philosoph muss 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 isst.
(Der Zustand c [i] == 2 wird erst in der vierten Lösungsvariante benötigt und stellt
sicher, dass 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 muss u. U. von seinen Nachbarn zum
Essen geschickt werden
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 78
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);
}
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
/* Philosoph i ist hungrig
/* darf er essen ?
*/
*/
/* ggf. warten
*/
/* Philosoph i denkt
*/
Betriebssysteme
Folie 2 - 79
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 isst)
−
falls kein Nachbar isst, wird er durch die V (Phil [i])-Anweisung
der Prozedur Test zum Essen zugelassen
−
andernfalls muss er an seiner P (Phil [i])-Anweisung darauf
warten, dass 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 dass für ihn
c [i] == 0 gilt)
•
der Algorithmus ist verklemmungsfrei (Beweis: siehe Literatur)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 80
der Algorithmus garantiert nicht, dass jeder Prozess 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, dass 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 81
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);
}
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
*/
*/
*/
Betriebssysteme
Folie 2 - 82
Diskussion der Lösung
•
Philosophen können sich nicht gegen einen Kollegen verschwören
⇒ kein Philosoph muss 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 isst 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 2-8:
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.
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 83
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 Prozess
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 Prozess gestoppt und
kann keine anderen Aktionen vorziehen
(es gibt entsprechende Erweiterungen des Konzepts, die das verhindern)
6) ein Prozess 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)
7) fehleranfällig, da der Programmierer sehr leicht Fehler begehen kann
a) falls eine P-Operation aufgrund eines (bewussten oder
unbewussten) Programmierfehlers fehlt, ist der wechselseitige
Ausschluss 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)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 84
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 im Allgemeinen noch
schwieriger, die Allgemeingültigkeit und Richtigkeit der Lösung
nachzuweisen
(siehe Philosophenproblem)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 85
2.4.3 Monitore
•
Semaphore sind leistungsfähige, aber unstrukturierte Mechanismen
zur Prozesskoordinierung
−
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, dass nur ein Prozess zu einem
Zeitpunkt seine Prozeduren/Funktionen benutzt
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 86
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
•
Folie 2 - 87
Monitor benötigt Sprachmittel zur Synchronisation von Prozessen
innerhalb des Monitors
−
Prozess muss auf die Erfüllung einer Bedingung warten können
−
Prozess muss aktiviert werden, sobald die Bedingung erfüllt ist
Synchronisation innerhalb eines Monitors wird unterstützt durch
−
Bedingungsvariablen
♦ Typ: condition
♦ repräsentieren nur eine Prozess-Warteschlange
(sie entsprechen damit einer Semaphor-Variablen ohne Zählervariable)
−
Operation wait
wait (x) bzw. x.wait bewirkt, dass aufrufender Prozess blockiert
und in Warteschlange der Bedingungsvariablen x eingekettet
wird
(entspricht der P-Operation, wobei der Prozess aber in jedem Fall blockiert wird)
−
Operation signal oder notify
♦ signal (x), x.signal, notify (x) bzw. x.notify bewirkt, dass ein
auf die Bedingung x wartender Prozess aktiviert wird
(entspricht der V-Operation, wobei eine signal-Operation ohne Wirkung bleibt,
wenn die Warteschlange der Bedingungsvariablen leer ist, d. h., dass ein Prozess,
der später eine wait-Operation ausführt, warten muss, bis erneut eine signalOperation ausgeführt wird)
♦ bei signal wird der aufgeweckte Prozess sofort aktiviert und
der signalisierende Prozess wird blockiert, während er bei
notify erst dann aktiviert wird, wenn der signalisierende Prozess den Monitor verlassen hat oder wait aufgerufen hat
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 88
Aufruf der wait-Operation gibt Monitor für andere Prozesse frei
(notwendig, da der Prozess den Monitor sonst blockieren würde, weil sich nur ein Prozess
im Monitor befinden darf)
•
beim Aufruf der signal-Operation gibt es ein Problem
1. der Prozess, der die signal-Operation ausführt, befindet sich im
Monitor
2. der Prozess, 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 Prozess im Monitor aktiv sein darf !!!
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 89
Lösung: einer der beiden Prozesse muss warten
1) der Prozess, der die signal-Operation ausführt, muss warten
(Vorschlag von Hoare)
−
der signalisierende Prozess wird in einer Prioritätswarteschlange blockiert
−
sobald der aufgeweckte Prozess den Monitor verlässt oder
sich selbst durch eine neue wait-Operation deaktiviert, wird
ein Prozess aus der Prioritätswarteschlange gestartet
2) signal-Operation darf nur als letzte Anweisung einer Monitorprozedur ausgeführt werden
(d. h., dass der aktive Prozess den Monitor sofort verlässt und damit nur der aufgeweckte Prozess 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)
condition c1
...
signal (cn)
wait (cn)
condition cn
local data
procedure 1
...
procedure k
wait, exit
signal
priority queue
initialization code
exit
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 90
3) der aufgeweckte Prozess muss warten
(Vorschlag von Lampson und Redell für die Sprache Mesa)
−
ein aufgeweckter Prozess wird aktiviert, sobald der
signalisierende Prozess den Monitor verlässt oder sich selbst
durch eine wait-Operation deaktiviert
−
die Funktion signal wird hier im Allgemeinen 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)
condition c1
...
*
procedure 1
...
procedure k
notify (cn)
wait (cn)
condition cn
*
initialization code
exit
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 91
„Problem“ bei Lösung 3
−
es ist nicht sichergestellt, dass ein Prozess 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
♦ Prozess im Monitor informiert beide Prozesse mit notify (B1)
und notify (B2), dass ihre Bedingungen wahr geworden sind
♦ sobald der Prozess den Monitor verlässt, kann einer der beiden Prozesse (z. B. P1) den Monitor wieder betreten
♦ da P1 alle Variablen im Monitor ändern darf, kann es
vorkommen, dass Bedingung B2 wieder falsch wird
♦ wenn P2 den Monitor wieder betritt, kann er nicht davon
ausgehen, dass die Bedingung B2 noch wahr ist
⇒ jeder Prozess muss die Bedingung noch einmal überprüfen,
d. h. die Anweisung if (...) muss durch while (...) ersetzt
werden
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Lösung 3 erlaubt zeitlich begrenztes Warten
−
problemlose Erweiterung, da jeder Prozess seine Bedingung
noch einmal überprüft
−
Erweiterung der Operationen
−
•
Folie 2 - 92
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 Prozess 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 Prozess passiv
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 93
Beispiel
−
Lösung des Erzeuger/Verbraucherproblems mit Hilfe eines
Monitors
−
Programm 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;
}
int main (void)
{
COBEGIN
Erzeuger (1); ...; Erzeuger (n);
Verbraucher (1); ...; Verbraucher (m);
COEND;
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
Betriebssysteme
Folie 2 - 94
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, muss 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 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, da er den Monitor „Puffer“ nicht betreten darf; jetzt will der
Verbraucher lesen und wird ebenfalls blockiert, da er den Puffer nicht betreten darf; anschließend wird
P1 fortgesetzt, führt notify aus und verlässt den Monitor; falls notify zufällig P2 aufweckt (bei einer
FIFO-Implementierung würde P2 auf jeden Fall ausgewählt werden), muss P2 sofort wait aufrufen, da
der Puffer keine freien Speicherzellen enthält; 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 95
Java-Programm für einen Puffer für ganze Zahlen
class IntBuffer
{
final
int
BUF_SIZE = 5;
/* buffer size
private int
buffer[] = new int[BUF_SIZE];
private int
readIndex, writeIndex; /* read / write index
private boolean cellAvail,
/* free cells are available
dataAvail;
/* data is available
*/
*/
*/
*/
public IntBuffer ()
{
readIndex = 0;
writeIndex = 0;
cellAvail = true;
dataAvail = false;
}
public synchronized void putVal (int val) throws InterruptedException
{
while (!cellAvail)
{
try
{
wait ();
/* wait for free cells
*/
} catch (InterruptedException e)
{ ...
}
}
buffer[writeIndex] = val;
writeIndex
= (writeIndex + 1) % BUF_SIZE;
dataAvail
= true;
if (writeIndex == readIndex)
{
cellAvail = false;
}
notifyAll ();
/* signal data available
*/
}
public synchronized int getVal () throws InterruptedException
{
int val;
while (!dataAvail)
{
try
{
wait ();
/* wait for data
} catch (InterruptedException e)
{ ...
}
}
val
= buffer[readIndex];
readIndex = (readIndex + 1) % BUF_SIZE;
cellAvail = true;
if (readIndex == writeIndex)
{
dataAvail = false;
}
notifyAll ();
/* signal free cells
return val;
}
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
Betriebssysteme
•
Folie 2 - 96
prinzipielle Implementierung von Monitoren über Semaphore
(für Lösung 1 des signal-Problems)
−
folgendes muss sichergestellt werden:
♦ höchstens ein Prozess darf eine Monitorprozedur ausführen
⇒ wechselseitiger Ausschluss für Monitorprozeduren
♦ eine wait-Operation muss den Prozess bei der entsprechenden Bedingung blockieren
♦ signal-Operation darf an beliebiger Stelle ausgeführt werden
⇒ aktueller Prozess wird ggf. sofort blockiert und erhält
höchste Priorität zum Wiederbetreten des Monitors
♦ falls ein Prozess den Monitor freigibt, wird der nächste
Prozess in folgender Reihenfolge bestimmt
♦ zuerst wird überprüft, ob Prozesse bei einer signalOperation blockiert wurden
♦ danach wird überprüft, ob ein neuer Prozess den Monitor
betreten will
⇒ Prozesse, die auf Bedingung warten, bleiben blockiert
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 97
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 muss so groß gewählt werden, dass 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 98
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--;
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
*/
*/
Betriebssysteme
•
Folie 2 - 99
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 100
Simulation von Semaphoren durch Monitore in Java
class Semaphore
{
private int value;
public Semaphore (int value)
{
if (value < 0)
{
this.value = 0;
System.err.println ("Semaphore value must be positive. " +
"Value changed to 0.");
}
else
{
this.value = value;
}
}
synchronized public void P () throws InterruptedException
{
while (value == 0)
{
try
{
wait ();
} catch (InterruptedException e)
{
String msg = (Thread.currentThread ()).getName () +
": Interrupted while waiting for resources in P-operation.";
throw new InterruptedException (msg);
}
}
value--;
}
synchronized public void V ()
{
value++;
notify ();
}
}
−
keine exakte Simulation der Semaphoroperationen
−
Befreiung eines wartenden Prozesses erfolgt durch MonitorMechanismus
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 101
genauere Simulation der Semaphoroperationen in Java
class ErrSemaphore
{
private int value;
private int waitCnt;
/* value of semaphore
/* number of threads in queue
*/
*/
public ErrSemaphore (int value)
{
if (value < 0)
{
this.value = 0;
System.err.println ("Semaphore value must be positive. " +
"Value changed to 0.");
}
else
{
this.value = value;
}
this.waitCnt = 0;
}
public synchronized void P () throws InterruptedException
{
if (value > 0)
{
value--;
}
else
{
while (value == 0)
{
waitCnt++;
try
{
wait ();
} catch (InterruptedException e)
{
String msg = (Thread.currentThread ()).getName () +
": Interrupted while waiting for resources in P-operation.";
throw new InterruptedException (msg);
}
waitCnt--;
}
}
}
public synchronized void V ()
{
if (waitCnt > 0)
{
notify ();
}
else
{
value++;
}
}
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
−
Folie 2 - 102
Verklemmung, wenn ein Prozess im kritischen Bereich ist und
mindestens ein Prozess in der P-Operation wartet
(Der Prozess im kritischen Bereich sendet notify und reiht sich dann bei seiner nächsten P-Operation in die Warteschlange der anderen Prozesse ein. Der geweckte
Prozess muss 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
...
Worker2-2: try to enter critical section in cycle 0
P: semaphore value: 1 -> thread can pass
Worker2-2: doing critical work in cycle 0
Worker2-3: try to enter critical section in cycle 0
P: Worker2-3 will be queued; waiting threads: 1
Worker2-0: try to enter critical section in cycle 0
P: Worker2-0 will be queued; waiting threads: 2
V: semaphore value: 0
waiting threads: 2
V: notify () performed
P: Worker2-3 thread has dequeued; waiting threads: 1
P: Worker2-3 will be queued; waiting threads: 2 (wegen erneuter Evaluation)
Worker2-2: doing normal work in cycle 1
Worker2-5: try to enter critical section in cycle 0
P: Worker2-5 will be queued; waiting threads: 3
Worker2-4: try to enter critical section in cycle 0
P: Worker2-4 will be queued; waiting threads: 4
Worker2-1: try to enter critical section in cycle 0
P: Worker2-1 will be queued; waiting threads: 5
Worker2-2: try to enter critical section in cycle 1
P: Worker2-2 will be queued; waiting threads: 6
<Verklemmung>
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 103
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 104
Aufgabe 2-9:
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, dass die Realisierung des wechselseitigen Ausschlusses
u. U. nicht fair ist, wenn nur ein Semaphor verwendet wird. Eine
Realisierung heißt fair, wenn garantiert ist, dass ein Prozess nach
endlicher Zeit fortfahren kann.
Aufgabe 2-10:
Gegeben sei die folgende Simulation für ein binäres Semaphor.
monitor BinarySemaphore
int
sem_val;
condition ws;
public procedure P (void)
{
if (sem_val > 0)
{
sem_val--;
}
else
{
wait (ws);
}
}
public procedure V (void)
{
if (sem_val == 0)
{
sem_val++;
}
else
{
signal (ws);
}
}
{
sem_val = 1;
}
a) Zeigen Sie, dass das obige Programm keine korrekte Simulation für
ein binäres Semaphor ist.
b) Führen Sie alle notwendigen Änderungen im obigen Programm
durch, so dass es ein binäres Semaphor korrekt simuliert.
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 105
2.5 Prozesskommunikation
•
Austausch von Informationen zwischen kooperierenden Prozessen
(Interprozesskommunikation, interprocess communications, IPC)
•
Kommunikationsmöglichkeiten
−
Shared Memory (gemeinsame Speicherbereiche)
−
Pipes (Kommunikationskanäle, FIFO-Dateien)
−
Nachrichtenübertragung
−
Mailbox-System (Nachrichtenwarteschlange)
−
Sockets
−
Remote Procedure Call (RPC, entfernter Prozeduraufruf)
−
usw.
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 106
2.5.1 Shared Memory
•
effizienter Datenaustausch zwischen parallelen Prozessen
(im Allgemeinen 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, dass mit lseek ( ) eine Position in der
Datei festgelegt wird, an der dann mit write ( ) ein Zeichen geschrieben wird)
•
Konzept von 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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 107
−
ü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“
2.5.2 Pipes
•
unidirektionaler Datenfluss zwischen zwei Prozessen
•
keine Synchronisationsprobleme
•
unnamed Pipes zur Interprozesskommunikation zwischen verwandten Prozessen (Vater - Sohn, Sohn - Sohn, ...)
(realisiert als Puffer im Speicherbereich des Betriebssystemkerns)
•
named Pipes zur Interprozesskommunikation zwischen beliebigen
Prozessen
(realisiert als FIFO-Dateien im Dateisystem)
•
unnamed und named Pipes für einige Anwendungen zu langsam
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 108
•
Zugriff auf jede Pipe erfolgt über Datei-Deskriptoren
•
Realisierung einer „bidirektionalen Pipe“ durch zwei „unidirektionale Pipes“
parent process
child process
fd1[1]
fd2[0]
fd2[1]
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]
fd[1]
fd[0]
pipe
process more
fd[0]
pipe
data flow
data flow
operating system kernel
•
unnamed und named Pipes gibt es in UNIX und Microsoft Windows
(Win32-API)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 109
2.5.3 Nachrichtenübertragung
•
viele Dinge müssen festgelegt werden, bevor die Nachricht
übertragen werden kann
•
Anzahl der kommunizierenden Prozesse
sending
process
S
receiving
process
sending
process
R
receiving
processes
S
R1
...
unicast
broadcast
receiving
processes
Rn
P
sending
process
S
multicast
R1
...
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 Prozess sondern an eine Nachrichtenwarteschlange geschickt; Empfänger lesen aus der Nachrichtenwarteschlange)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 110
Synchronisation der Nachrichtenübertragung
−
sendender Prozess wird blockiert bis Nachricht empfangen
wurde oder wird nicht blockiert
−
empfangender Prozess kann sofort weiterlaufen, wenn Nachricht
im Puffer vorhanden
−
falls keine Nachricht im Puffer ist, wird der empfangende
Prozess 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 im Allgemeinen nur
dann sinnvoll weiterarbeiten, wenn er die Nachricht empfangen hat)
−
blockierendes Senden/Empfangen kann einen Prozess beliebig
lange blockieren
(z. B. bei Synchronisationsfehlern (beide Prozesse wollen senden oder empfangen),
bei Verlust von Nachrichten oder wenn ein Prozess fehlerhaft endet und keine
weiteren Nachrichten sendet/empfängt)
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 111
Format der Nachricht
−
hängt im Allgemeinen davon ab, 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: im Allgemeinen Nachrichtenkopf
inkl. Nachricht
message type
destination ID
source ID
message header
message length
control information
message contents
or pointer to
message contents
message body
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 112
Datenformate (ganze Zahlen, Gleitkommazahlen, Zeichencodierung, ...)
higher memory
lower memory
address
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, ...
(Programm Endian.c im Programmarchiv zum Betriebssystempraktikum)
−
little-endian: Intel 80x86, ...
−
Gleitkommazahlen: Anzahl Bits für Mantisse und Exponent im
Allgemeinen unterschiedlich bei heterogenen Systemen
(Programm Floatingpoint.c im Programmarchiv zum Betriebssystempraktikum)
−
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)
(Programm StructTwo.c im Programmarchiv zum Betriebssystempraktikum)
−
in heterogenen Rechnernetzen müssen alle Datenformate
zwischen plattformabhängiger und plattformunabhängiger
Darstellung transformiert werden
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 113
Ü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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 114
2.5.4 Mailbox-System
•
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
...
R1
mailbox
Sm
•
...
Rn
Mailbox/Nachrichtenwarteschlange mit m : 1 Beziehung
(in diesem Fall wird die Mailbox oft Port genannt)
sending
processes
receiving
process
S1
...
port
R1
Sm
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 115
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 Prozess
seine Mailbox schließt
−
ein Prozess kann sowohl Server als auch Client sein, so dass 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 ()
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 116
2.5.5 Sockets
•
Endpunkte für Kommunikationsverbindungen
•
jedem Socket ist genau ein Prozess zugeordnet
•
für (heterogene) Rechnernetze geeignet
•
weit verbreitet (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
read from connection
accept ()
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 ()
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 117
Sockets in Java mit verbindungsorientiertem Protokoll (TCP/IP)
Server
Client
ServerSocket ()
accept ()
InputStream ()
...
establish a
connection
data
transfer
Socket ()
OutputStream ()
...
OutputStream ()
InputStream ()
close ()
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
Folie 2 - 118
2.5.6 Remote Procedure Call
•
bidirektionaler Nachrichtenaustausch
•
für (heterogene) Rechnernetze geeignet
•
Prozess 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
client
process
call
remote
server process
call
return
local
procedures
return
return
call
call
client stub
procedures
server stub
procedures
RPC
mechanism
RPC
mechanism
messagepassing
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
return
local
procedures
Betriebssysteme
•
Folie 2 - 119
Ablauf für synchronen, entfernten Prozeduraufruf
client
network
stub
procedure
call
server
stub
creates
message
RPC
waiting
analyses
message
procedure
call
waiting
return
RPC return
return
•
analyses
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-Beschreibung u. a. die
Platzhalter-Prozeduren (stubs), die lokale Prozeduraufrufe in RPCProzeduraufrufe überführen
(Unix: rpcgen, Microsoft Windows: MIDL Compiler)
•
IDL-Beschreibungen verschiedener Systeme sind im Allgemeinen
nicht kompatibel
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 120
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 121
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;
•
der Server kann verschiedene Programmversionen unterstützen
•
Prozeduren werden fortlaufend nummeriert
•
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
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 122
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 verschiedenen
Programmversionen dieselbe Nummer haben
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
Folie 2 - 123
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 Prozedurnummer werden
RPC-Prozeduren eindeutig identifiziert
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Betriebssysteme
•
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);
}
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 2 - 124
Betriebssysteme
...
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;
}
...
Hochschule Fulda, Fachbereich AI, Prof. Dr. S. Groß
Folie 2 - 125
Zugehörige Unterlagen
Herunterladen