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 oder ; 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

"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

"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 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: _)

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



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 #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