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