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