Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Alois Schütte 23. März 2016 1 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Inhaltsverzeichnis Ein Prozess kann als die Abstraktion eines Programms, das von einem Prozessor ausgeführt wird, angesehen werden. Hier wird behandelt, was Prozesse sind und wie sie in Betriebssystemen implementiert werden, inklusive Prozesskommunikation, -synchronisation und Scheduling. 1 Prozessmodell 2 Interprozesskommunikation 3 IPC Probleme 4 Scheduling 5 Literatur 2 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Prozessmodell 1 Prozessmodell Prozesszustände Implementierung von Prozessen Abstraktion des Prozessmodells in Java 2 Interprozesskommunikation 3 IPC Probleme 4 Scheduling 5 Literatur 3 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Prozessmodell Um zu verstehen, wie unterschiedliche Aktivitäten scheinbar gleichzeitig ablaufen, braucht man ein Modell eines sich in Ausführung befindenden Programms. Ein (sequentieller) Prozess ist ein sich in Ausführung befindendes (sequentielles) Programm zusammen mit: • dem aktuellen Wert des Programmzähler, • den Registerinhalten, • den Werten der Variablen und • dem Zustand der geöffneten Dateien und Netzverbindungen. Konzeptionell besitzt jeder Prozess seinen eigenen Prozessor - in der Praxis wird aber immer der reale Prozessor zwischen den Prozessen hinund hergeschaltet. 4 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Sichtweisen Beim Mehrprogrammbetrieb mit 4 Programmen (A-D) ergeben sich damit folgende Sichtweisen: 5 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Prozess-Hierarchie Ein Betriebssystem mit einen solchen Prozessmodell muss in der Lage sein, dass von einem (Initial-) Programm andere Prozesse erzeugt werden können. • In Unix dient dazu der fork-Systemaufruf. • Beim Hochfahren“ des Betriebssystems werden” dann alle erforderlichen Prozesse erzeugt für Systemdienste, wie • • • • Scheduler, Dateidienst, Terminaldienst, ... 6 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Prozesskategorien und Ausführungsmodi Damit das Betriebssystem allein die Ressourcen verwalten kann, werden die Prozesse in Kategorien unterteilt: • Kernel-Prozesse laufen von anderen Prozessen abgeschottet; sie sind privilegiert, direkt auf Ressourcen zuzugreifen. • User Prozesse verwenden stets Kernel-Funktionalität, um indirekt auf Ressourcen zuzugreifen. Heutige Prozessoren unterscheiden prinzipiell zwei Ausführungsmodi für Instruktionen: • privilegierten Modus (Kernel-Prozesse): z.B. E/A-Befehle, Registerzugriff und Interruptmaskierung • Normalmodus (User-Prozesse) Ein Userprozess ruft über Systemfunktionen (system calls) einen Dienst im eigenen Adressraum auf. Kein Prozess hat dabei direkten Zugriff auf den Speicher des Kerns. 7 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Trap-Mechanismus Eine Kernel-Funktion wird stets über den Trap-Mechanismus (Kernel-Aufruf) aufgerufen: 1 2 Speichern des Funktionskodes und der Parameter in speziellen Registern Auslösen eines Software Interrupts, d.h • Inkrementieren des Programmzählers • Umschalten in privilegierten Modus User 1 2 UserModus 3 Modus 3 Ausführen der Kernelfunktion 4 Speichern des Ergebnisses in Registern 5 Umschalten in Normalmodus 6 Zurückladen der Ergebnisse aus dem Register 7 Ausführen der nächsten Instruktion der Anwendung 4 KernelModus 5 6 UserModus 7 8 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Prozesszustände Prozesszustände Eine Aufgabe des Betriebssystems ist das Multiplexen des physischen Prozessors. Diese Aufgabe übernimmt der Scheduler zusammen mit dem Dispatcher. Prozesse können sich in verschiedenen Zustände befinden. 1 rechnend (running) der Prozessor ist dem Prozess zugeteilt 2 bereit (ready) der Prozess ist ausführbar, aber ein anderer Prozess ist gerade rechnend 3 blockiert (waiting) der Prozess kann nicht ausgeführt werden, da er auf ein externes Ereignis wartet (z.B. Benutzer hat Taste auf Tastatur gedrückt) Diese Zuständen bilden eine Entscheidungsgrundlage für die Auswahl eines geeigneten Kandidaten bei einem Prozesswechsel. 9 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Prozesszustände Zustandsübergänge 1 2 Prozess wartet auf externes Ereignis Scheduler wählt anderen Prozess aus, da die Zeitscheibe des Prozesses abgelaufen ist 3 Scheduler wählt diesen Prozess aus 4 externes Ereignis ist eingetreten 5 ein neuer Prozess wird erzeugt 6 der Prozess terminiert 6 5 rechnend 2 1 3 bereit blockiert 4 Die Zustandsübergänge werden vom Dispatcher durchgeführt, die Auswahl eines rechenbereiten Prozesses übernimmt der Scheduler. 10 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Prozesszustände Modell für Prozesssystem Damit ergibt sich ein Modell für ein Prozesssystem, bei dem • die unterste Schicht die Unterbrechungen behandelt und das Scheduling der Prozesse inklusive Erzeugung und Abbrechen von Prozessen erledigt; • alle anderen Prozesse befinden sich auf gleicher Ebene darüber und haben einen sequentiellen Kontrollfluss. • Somit gibt es stets einen Wechsel von Aktivitäten eines (User-)Prozesses und des Kerns. Prozesse 1 Kernel-Modus 2 3 ... n User-Modus Dispatcher / Scheduler 11 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Implementierung von Prozessen Das o.g. Prozessmodell kann in einem Betriebssystem durch eine Prozesstabelle, die für jeden Prozess einen Eintrag enthält, realisiert werden. Ein Eintrag muss alle Informationen enthalten, um einen Prozess wieder anlaufen zu lassen, wenn er suspendiert wurde: • Zustand, • Programmzähler, • Stack-Zeiger, • belegten Speicher, • Zustand der geöffneten Dateien und • Verwaltungsinformationen (bisher belegte CPU Zeit, Schedulinginformationen, etc.) Welche Information muss auf Ebene des HAL bei Wechsel des HAL-Programms/Prozesses gespeichert werden? Also was muss passieren, wenn ein HAL-Prozessor einen Prozesswechsel durchführt? 12 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Prozesstabelle Je nach Betriebssystem variieren diese Felder der Prozesstabelle. $ ps -o " % p PID PGID 3427 3427 3496 3496 $ %r %c %a COMMAND bash ps %x %t" COMMAND TIME - bash 00:00:00 ps -o % p % r % c % 00:00:00 Ein Prozesswechsel kann durch ein externes Ereignis ausgelöst werden. Dazu wird jeder Klasse von E/A-Geräten (Festplatten, Terminals, etc.) ein Unterbrechungsvektor zugeordnet, der die Adresse einer Unterbrechungsbehandlungsprozedur (Interrupt Routine) enthält. Wenn z.B. ein Terminal eine Unterbrechung auslöst, dann wird hardwareseitig (Microcode) folgendes getan: 1 Programmzähler des aktuell laufender Prozess und einiger Register auf Stack legen 2 Aufruf der entsprechenden Unterbrechungsbehandlungsprozedur Alle anderen Aktivitäten (Auswahl des nächsten Prozesses, der nach der Unterbrechungsroutine laufen soll) muss durch die Betriebssystemsoftware erfolgen. 13 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Dämonen In Unix werden Verwaltungsaufgaben von speziellen Programmen, die als Hintergrundprozesse ablaufen, übernommen. Beispiele sind: cron Systemcronometer at Ausführen eines Kommandos zu einer vorgegebenen Zeit inetd Internet Service Dämon sshd Secure Shell Dämon Solche Prozesse werden oft beim Hochfahren des Betriebssystems gestartet und erst beim Shutdown des Systems beendet. Auf Shellebene kann man sich Dämonen ansehen durch das ps-Kommando. Sie sind daran zu erkennen, dass die Spalte TTY“ ein ” Fragezeichen ?“ enthält. ” $ ps - el | grep \? 1277 ? 00:02:22 crond 1291 ? 00:00:00 atd 1578 ? 00:00:03 sshd 30045 ? 00:03:41 sendmail 14 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Diese Prozesse sind als Dämonen realisiert: • Jeder Prozess (außer init) in Unix hat einen Vater und gehört zu einer Prozessgruppe. • Jede Prozessgruppe hat einen Prozess als Prozessgruppenleiter und besitzt maximal ein Kontrollterminal. Das Kontrollterminal liefert für alle Mitglieder der Prozessgruppe die Standardeinstellungen von stdin, stdout und stderr. Das Kontrollterminal lässt sich immer unter dem Namen /dev/tty“ ansprechen. ” • Ein Signal des Kontrollterminals wird an alle Mitglieder der Prozessgruppe gesendet (die Shell schützt sich und alle von ihr initiierten Prozesse allerdings davor). • Prozesse, die zwar Mitglieder einer Prozessgruppe sind, aber kein Kontrollterminal haben, nennt man Dämonen. • Da sie kein Kontrollterminal besitzen, sind sie nicht über Signale abzubrechen. • Diese Dämonen treiben ihr Unwesen im Bauch der Maschine“. ” 15 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Zur Implementierung von Dämonen sollen folgende Regeln eingehalten werden: • Zunächst sollte ein fork ausgeführt werden, der Elternprozess sollte danach ein exit ausführen. Dadurch wird folgendes erreicht: 1 2 Wird der Prozess von der Shell gestartet, so denkt die Shell, der Prozess wäre terminiert, da der Vater beendet ist. Das Kind erbt vom Vater die Prozessgruppe, bekommt aber (durch fork) eine neue PID. Dadurch ist ausgeschlossen, dass der Prozess ein Prozessgruppenführer ist. • Der nächste Schritt ist, den Systemaufruf setsid()“ auszuführen. ” Dadurch wird eine neue Session erzeugt und der Aufrufer wird Prozessgruppenführer. • Nun wird in das aktuelle Verzeichnis gesetzt. Es sollte das Root-Verzeichnis sein, damit nicht ein eventuelle gemountetes Dateisystem (wenn das das Home-Verzeichnis des Aufrufers war) dazu führt, dass das Betriebssystem nicht heruntergefahren werden kann. • Die Maske zum Dateierzeugen (umask) wird auf 0 gesetzt, damit nicht geerbte Maske vom Aufrufer dazu führt, dass der Dämon Dateien nicht anlegen kann. • Letztlich sollten alle nicht benötigten Filedeskriptoren geschlossen 16 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Beispiel Dämon daemon.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # include < sys / types .h > # include < sys / stat .h > # include < fcntl .h > int daemon_init ( void ) { pid_t pid ; if (( pid = fork ()) < 0) return ( -1); else if ( pid != 0) exit (0); /* parent goes bye - bye */ /* child */ setsid (); /* become session leader */ chdir ( " / " ); /* change working directory */ umask (0); /* clear our file mode creatio mask */ return (0); } int main () { /* ... * some initial tasks */ daemon_init (); /* make a daemon out of the program */ sleep (100); /* daemon code , we see the daemon with ps -x */ exit (0); } 17 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Dämonen $ ./ daemon $ ps - xl F UID PID PPID PRI 0 500 22873 22871 20 1 500 23030 1 20 0 500 23031 22873 20 $ WCHAN STAT TTY wait Ss pts /0 hrtime Ss ? R+ pts /0 TIME 0:00 0:00 0:00 COMMAND - bash ./ daemon ps - xl ← Nach de Start des Dämon kann man mit ps –xl“ sehen, dass der Dämon ” aktiv ist und als Vater und den Prozess 1 besitzt. Ein Problem bei Dämonen ist, dass es nicht möglich ist, stderr und stdout zu verwenden. Also muss man eine Methode entwickeln, wie ein Dämon die Aktivitäten protokollieren kann. In Unix existiert das syslog Werkzeug, um Nachrichten geordnet zu verarbeiten. 18 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Zombies Ein Prozess terminiert und hört auf zu existieren, wenn zwei Ereignisse eingetreten sind: 1 Der Prozess hat sich selbst beendet oder wurde durch ein Signal beendet und 2 der Vaterprozess hat wait()“ aufgerufen. ” Ein Prozess, der beendet wird, bevor der Vater einen wait-Aufruf für ihn ausgeführt hat, erhält einen besonderen Zustand, er wird ein Zombie: • Der Prozess wird vom Scheduler nicht mehr berücksichtigt, er wird aber nicht aus der Prozesstabelle entfernt. • Wenn der Vater dann einen wait-Aufruf veranlasst, wird der Prozess aus der Prozesstabelle entfernt und der belegte (Haupt-) Speicher wird frei gegeben. ps -l: 0 laufend S schlafend (sleeping) R auf den Prozessor wartend (runable) ... Z Zombie 19 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Beispiel Zombie zombie.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /* Create zombie to illustrate " ps - el */ # include < stdio .h > int main () { int pid ; if (( pid = fork ()) == -1) fprintf ( stderr , " fork failed !\ n " ); if ( pid == 0) { /* Child */ printf ( " child : % d \ n " , getpid ()); exit (1); /* because father will not wait , a zombie is born */ } else { /* fahter */ printf ( " father : % d \ n " , getpid ()); sleep (30); /* now you can use ps - el to see during 30 secs that child is a zombie */ exit (0); } } 20 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Beispiel Zombie $ ./ zombie & [1] 23036 $ father : 23036 child : 23037 ps -l F S PID PPID 0 S 22873 22871 0 S 23036 22873 1 Z 23037 23036 0 R 23038 22873 $ WCHAN wait hrtime exit - TTY pts /0 pts /0 pts /0 pts /0 TIME 00:00:00 00:00:00 00:00:00 00:00:00 CMD bash zombie zombie < defunct > ps ← ← 21 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Zombies entfernen per SIGCHLD Wenn ein Vater nicht auf die Beendigung seiner Kinder warten kann (z.B. bei der Shell mit Hintergrundprozessen), kann man die Zombies eliminieren, indem • der Vater auf das Signal SIGCHLD reagiert • die Signalbehandlungsroutine dann mittels waitpid den Zobmiestatus des Kindes aufhebt. zombie cleanup.c /* Simplest dead child cleanup in a SIGCHLD handler . * Prevent zombie processes but don 't actually do anything * with the information that a child died . */ # include < sys / types .h > ... /* SIGCHLD handler . */ static void sigchld_hdl ( int sig ) { /* Wait for all dead processes . * We use a non - blocking call to be sure this signal handler * will not block if a child was cleaned up in another * part of the program . */ while ( waitpid ( -1 , NULL , WNOHANG ) > 0) ; ← } 22 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Implementierung von Prozessen Zombies entfernen per SIGCHLD int main ( int argc , char * argv []) { int i ; if ( signal ( SIGCHLD , sigchld_hdl )) { perror ( " signal " ); return 1; } ← /* Make some children . */ for ( i = 0; i < 5; i ++) { switch ( fork ()) { case -1: perror ( " fork " ); return 1; case 0: // do something exit (0); } } while (1) { // father performs something } return 0; } 23 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java Abstraktion des Prozessmodells in Java Um Eigenschaften und Probleme mit Prozessen zeigen zu können, werden Algorithmen in der Vorlesung in Java (oder C) beschrieben. Betrachtet werden Java-Threads als Abstraktion von Prozessen eines Rechners: Java Prozess Threads Rechner Prozesse gemeinsamer Speicher Speicher Deshalb wird hier zunächst gezeigt, wie in Java Threads erzeugt werden können. 24 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java Erzeugen und Starten vonJava-Threads Beim Start eines Java Programms wird ein Prozess erzeugt, der einen Thread enthält, der die Methode main der angegebenen Klasse ausführt. Der Code weiterer Threads muss in einer Methode mit Namen run realisiert werden. public void run () { // Code wird in eigenem Thread ausgeführt } Ein Programm, das Threads erzeugt, erbt von der Klasse Thread und überschreibt die Methode run() (auf Interfaces wird später eingegangen): 25 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java Hello World MyThread.java 1 2 3 4 public class MyThread extends Thread { public void run () { System . out . println ( " Hello World " ); } public static void main ( String [] args ) { MyThread t = new MyThread (); t . start (); } 6 7 8 9 10 ← ← ← } Die Methode start() ist in der Klasse thread definiert und startet den Thread, genauer die Methode run(). Somit wird zunächst ein Thread erzeugt (new ...), dann durch start die Methode run aufgerufen und somit Hello World“ ausgegeben. ” 26 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java mehrere Threads Werden mehrere Thread erzeugt, so ist die Ausführungsreihenfolge nicht vorhersehbar! Loop1.java 1 2 3 4 5 public class Loop1 extends Thread { private String myName ; public Loop1 ( String name ) { myName = name ; } public void run () { for ( int i = 1; i <= 10000; i ++) { System . out . println ( myName + " ( " + i + " ) " ); } } 7 8 9 10 11 public static void main ( String [] args ) { Loop1 t1 = new Loop1 ( " Thread 1 " ); Loop1 t2 = new Loop1 ( " Thread 2 " ); Loop1 t3 = new Loop1 ( " Thread 3 " ); t1 . start (); t2 . start (); t3 . start (); } 13 14 15 16 17 18 19 20 21 } 27 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java Threads in komplexen Klassenhierarchien Wenn sich die Methode run() in einer Klasse befinden soll, die selbst bereits aus einer anderen Klasse abgeleitet ist, so kann diese Klasse nicht zusätzlich von Thread abgeleitet werden (Java unterstützt keine Mehrfachvererbung). In diesem Fall kann das Interface Runnable des Package java.lang verwendet werden. MyRunnableThread.java 1 2 3 4 5 6 7 8 9 10 public class MyRunnabl e T h r e a d implements Runnable { public void run () { System . out . println ( " Hello World " ); } public static void main ( String [] args ) { M y RunnableThread runner = new M y R u n n a b l eT h r e a d (); Thread t = new Thread ( runner ); t . start (); } } ← ← ← 28 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java Threadtermination Ein Thread terminiert, wenn seine run()-Methode (bzw. die Methode main() im Fall des Ursprungs-Thread) beendet ist. Sind alle von einem Prozess initiierten Threads beendet, so terminiert der Prozess (falls es kein Dämon ist). Die Klasse Thread stellt eine Methode isAlive bereit, mit der abfragbar ist, ob ein Thread noch lebt (schon gestartet und noch nicht terminiert ist). Damit könnte aktives Warten etwa wie folgt programmiert werden: 1 2 3 4 5 6 // MyThread sei aus Thread abgeleitet MyThread t = new myThread (); t . start (); while ( t . isAlive ()) ; // hier ist jetzt : t . isAlive == false , der Thread t ist terminiert Man sollte es so aber nie tun, da aktives Warten sehr rechenintensiv ist. wieso? 29 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java Warten bis Thread beendet ist Wenn in einer Anwendung auf das Ende eines Thread gewartet werden muss, etwa um die Rechenergebnisse des Thread weiterzuverarbeiten, kann die Methode join der Klasse Thread benutzt werden. Der Thread wird blockiert, bis der Thread, auf den man wartet, beendet ist. 1 2 3 4 5 // MyThread sei aus Thread abgeleitet MyThread t = new myThread (); t . start (); t . join (); // blockiert , bis t beendet ist . // auch hier jetzt : t . isAlive == false , der Thread t ist terminiert wieso ist das besser? 30 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java Verwendung von join zum Abfragen von Ergebnissen Das folgende Beispiel verwendet Threads, um ein grosses Feld von Boolean zu analysieren, indem mehrere Threads parallel arbeiten, wobei je Thread ein Bereich des Feldes bearbeitet wird. 1 0 0 T1:1 0 1 0 1 1 0 T2:3 0 0 T3:0 0 1 1 1 1 T4:4 main: 8 31 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java 1 2 3 4 5 class Service implements Runnable { private boolean [] array ; private int start ; private int end ; private int result ; public Service ( boolean [] array , int start , int end ) { this . array = array ; this . start = start ; this . end = end ; } 7 8 9 10 11 public int getResult () { return result ; } 13 14 15 public void run () { for ( int i = start ; i <= end ; i ++) { if ( array [ i ]) result ++; } } 17 18 19 20 21 22 23 ← ← } 32 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java 1 2 3 5 6 7 9 10 11 12 13 14 15 16 18 19 20 22 23 24 public class AsynchRequest { private static final int ARRAY_SIZE = 100000; private static final int N U M B E R _ O F _ S E R V E R S = 100; ← ← public static void main ( String [] args ) { // start time long startTime = System . c u r r e n t T i m e M i l l i s (); // array creation , init with random boolean values boolean [] array = new boolean [ ARRAY_SIZE ]; for ( int i = 0; i < ARRAY_SIZE ; i ++) { if ( Math . random () < 0.1) array [ i ] = true ; else array [ i ] = false ; } ← // creation of array for service objects and threads Service [] service = new Service [ N U M B E R _ O F _ S E R V E R S ]; Thread [] serverThread = new Thread [ N U M B E R _ O F _ S E R V E R S ]; ← ← ← int start = 0; int end ; int howMany = ARRAY_SIZE / N U M B E R _ O F _ S E R V E R S ; 33 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java 1 2 3 4 5 6 7 8 10 11 12 13 14 16 17 18 19 20 // creation of services and threads for ( int i = 0; i < N U M B E R _ O F _ S E R V E R S ; i ++) { end = start + howMany - 1; service [ i ] = new Service ( array , start , end ); serverThread [ i ] = new Thread ( service [ i ]); serverThread [ i ]. start (); // start thread i start = end + 1; } ← // wait for termination of each service ( thread ) try { for ( int i = 0; i < N U M B E R _ O F _ S E R V E R S ; i ++) serverThread [ i ]. join (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} ← // accumulate service results int result = 0; for ( int i = 0; i < N U M B E R _ O F _ S E R V E R S ; i ++) { result += service [ i ]. getResult (); } ← ← ← ← ← ← 34 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java // end time long endTime = System . c u r r e n t T i m e M i l l i s (); float time = ( endTime - startTime ) / 1000.0 f ; System . out . println ( " computation time : " + time ); 1 2 3 4 // print result System . out . println ( " result : " + result ); } // main 6 7 8 9 } $ java AsynchRequest Rechenzeit : 0.11 Ergebnis : 9953 $ 35 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java zum Üben für zu Hause 1 Testen des Verhaltens, wenn NUMBER OF SERVERS mit 1, 10, 100, 1.000, 10.000 und 100.00 gesetzt wird und Erklärung des Ergebnisses. 2 Ändern der Metode run() und Test und Erklärung der Auswirkung gegenüber dem Verhalten von 1. public void run () { for ( int i = start ; i <= end ; i ++) { if ( array [ i ]) result ++; } try { Thread . sleep (100); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } 36 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Prozessmodell Abstraktion des Prozessmodells in Java Beenden von Threads per Programm Die Klasse Thread besitzt die Methode stop(), zum Abbruch von Threads. Die Verwendung ist aber problematisch, da ein inkonsistenter Zustand erreicht werden kann. • Dies ist der Fall, wenn der Thread gerade eine synchronized()-Methode (später) ausführt und den Zustand eines Objektes verändert und dies aber nicht bis zum Schluss durchführen kann (wegen Stop()). • Durch Stopp werden alle Sperren aufgehoben, der Thread konnte aber nicht fertig werden. Eine Abhandlung zum Stoppen von Threads kann nachgelesen werden in threadPrimitiveDeprecation oder HowToStopThreads. Wir werden später nochmals darauf zurückkommen. 37 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Interprozesskommunikation 1 Prozessmodell 2 Interprozesskommunikation Probleme des gemeinsamen Zugriffs Kritischer Bereich Lösungsansätze in Java Semaphore Monitore und andere Primitive CSP - Communication Sequential Processes Nachrichtenaustausch 3 IPC Probleme 4 Scheduling 5 Literatur 38 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Interprozesskommunikation Betriebssysteme stellen unterschiedliche Mechanismen zur Verfügung, mit denen zwei Prozesse kommunizieren können: • beide Prozesse können sich den Arbeitsspeicher teilen, oder • gemeinsamer Speicher ist nicht verfügbar, es werden externe Medien, wie Dateien verwendet, auf die gemeinsam zugegriffen werden kann. Die Probleme sind aber unabhängig davon, welcher der beiden Mechanismen verwendet wird. Im folgenden werden die Probleme und Lösungen für den Zugriff auf gemeinsam verwendete Ressourcen gezeigt. 39 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Probleme des gemeinsamen Zugriffs Probleme des gemeinsamen Zugriffs Wenn mehrere Threads in Java gemeinsam auf Daten zugreifen, müssen sich die einzelnen Threads verständigen“, wer wann was machen darf. ” Dazu werden die Möglichkeiten, die Java bietet, am Beispiel von Buchungen eines Bankkontos gezeigt. Eine Bank wird modelliert durch 4 Klassen: Die Klasse Konto repräsentiert ein Konto mit dem Attribut kontostand und den Methoden zum Setzen und Abfragen des aktuellen Kontostandes. BankOperation.java 1 2 3 4 5 6 7 8 9 class Konto { private float kontostand ; public void setzen ( float betrag ) { kontostand = betrag ; } public float abfragen () { return kontostand ; } } 40 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Probleme des gemeinsamen Zugriffs Bank Die Klasse Bank hat als Attribut ein Feld von Referenzen auf Konto-Objekte (Konten). Die Methode buchen implementiert einen Buchungsvorgang auf ein Konto. 1 2 class Bank { private Konto [] konten ; public Bank () { konten = new Konto [100]; for ( int i = 0; i < konten . length ; i ++) { konten [ i ] = new Konto (); } 4 5 6 7 8 public void buchen ( int kontonr , float betrag ) { float alterStand = konten [ kontonr ]. abfragen (); float neuerStand = alterStand + betrag ; konten [ kontonr ]. setzen ( neuerStand ); } 10 11 12 13 14 15 } 41 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Probleme des gemeinsamen Zugriffs Bankangestellte Die Klasse Bankangestellte ist aus der Klasse Thread abgeleitet. Der Name der Angestellten wird der Name des Thread, da mehrere Bankangestellte (Threads) für die Bank arbeiten können sollen. Buchungen werden in der run-Methode durch Zufallszahlen erzeugt. 1 2 3 4 5 6 class BankAngestellte extends Thread { private Bank bank ; public BankAngestell te ( String name , Bank bank ) { super ( name ); this . bank = bank ; start (); } public void run () { for ( int i = 0; i < 10000; i ++) { /* Kontonummer einlesen ; simuliert durch Wahl einer Zufallszahl zwischen 0 und 99 */ int kontonr = ( int )( Math . random ()*100); /* Ü be r we is u ng s b e t r a g einlesen ; simuliert durch Wahl einer Zufallszahl zwischen -500 und +499 */ float betrag = ( int )( Math . random ()*1000) - 500; bank . buchen ( kontonr , betrag ); } } 8 9 10 11 12 13 14 15 16 17 18 19 } 42 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Probleme des gemeinsamen Zugriffs Bankbetrieb In der Klasse Bankbetrieb wird eine Bank mit 2 Bankangestellten simuliert. Die Bankangestellten fangen an zu Arbeiten, wenn die Objekte erzeugt werden (durch new und dann die Methode start im Konstruktor von Bankangestellte). 1 2 3 4 5 6 7 8 9 public class Bankbetrieb { public static void main ( String [] args ) { Bank sparkasse = new Bank (); B ankAngestellte müller = new BankAngestel l te ( " Andrea Müller " , sparkasse ); B ankAngestellte schmitt = new BankAngestel l te ( " Petra Schmitt " , sparkasse ); } } ← ← sparkasse ist das gemeinsam verwendete Objekt ! 43 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Probleme des gemeinsamen Zugriffs verlorene Buchungen Bei dieser Bank können aber Buchungen verloren gehen! → am Rechner Jede der 2 Bankangestellten bebucht das • Konto 1 • jeweils 1000 mal mit • jeweils 1 EUR Also muss das Konto 1 am Ende des Tages einen Kontostand von 2000 EUR aufweisen ! Dieses Problem tritt immer auf, wenn mehrere Threads auf ein gemeinsam nutzbares Objekt (hier das Feld Konto der Klasse Bank) zugreifen und es keine Schutzmechanismen gibt. 44 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Probleme des gemeinsamen Zugriffs Verdeutlichung Gehen wir von folgender Situation aus: • Andrea Müller bucht -100 EUR auf Konto 47, es sollen also 100 EUR abgebucht werden. Der Kontostand sein 0. • Petra Schmitt führt auch Buchungen für Konto 47 aus, es sollen 1000 EUR gutgeschrieben werden. Thread Andrea Müller: 1 public void buchen ( int kontonr =47 , float betrag = -100) { Konten[47]=0 2 float alterStand = konten [ kontonr ]. abfragen (); -> Umschalten auf Thread Petra Schmitt float neuerStand = alterStand + betrag ; konten [ kontonr ]. setzen ( neuerStand ); } 3 4 5 6 alterStand=0 Thread Petra Schmitt: 1 2 3 4 5 6 public void buchen ( int kontonr =47 , float betrag =1000) { Konten[47]=0 float alterStand = konten [ kontonr ]. abfragen (); float neuerStand = alterStand + betrag ; konten [ kontonr ]. setzen ( neuerStand ); } -> Umschalten auf Thread Andrea Müller alterStand=0 neuerStand=1000 Konten[47]=1000 45 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Probleme des gemeinsamen Zugriffs Verdeutlichung Thread Andrea Müller: 1 2 3 public void buchen ( int kontonr =47 , float betrag = -100) { Konten[47]=0 float alterStand = konten [ kontonr ]. abfragen (); -> Weiter nach Unterbrechung alterStand=0 4 float neuerStand = alterStand + betrag ; neuerStand=-100 5 konten [ kontonr ]. setzen ( neuerStand ); Konten[47]=-100 6 } Nun ist die Gutschrift, die Petra Schmitt gebucht hat verloren! Der aktuelle Kontostand ist -100 anstatt +900. Die Ursache des Problems ist, dass eine Buchung aus mehreren Java Anweisungen besteht und zwischen diesen Anweisungen zwischen Threads umgeschaltet werden kann. 46 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Kritischer Bereich Kritischer Bereich Das gezeigte Problem der verlorenen Buchungen kann auch abstrakt formuliert werden. Der Teil eines Programms, in dem auf gemeinsame Ressourcen zugegriffen wird, wird kritischer Bereich genannt. Zeitkritische Abläufe (wie das Buchen des selben Kontos) können vermieden werden, wenn sich zu keinem Zeitpunkt zwei Prozesse in ihrem kritischen Bereich befinden. Im folgenden werden Lösungsversuche (nicht korrekte) diskutiert. 47 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Kritischer Bereich Lösungsversuch 1 (nur eine Anweisung) In der Klasse Bank wird das Buchen durch eine Java Anweisung realisiert: 1 2 3 4 class Konto { private float kontostand ; public void buchen ( float betrag ) { kontostand += betrag ; } 5 6 } 8 class Bank { private Konto [] konten ; 9 also kein Umschalten möglich public Bank () { konten = new Konto [100]; for ( int i = 0; i < konten . length ; i ++) { konten [ i ] = new Konto (); } 11 12 13 14 15 public void buchen ( int kontonr , float betrag ) { konten [ kontonr ]. buchen ( betrag ); ← } 17 18 19 20 ← nur eine Anweisung } Wie beurteilen Sie den Ansatz ? 48 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Kritischer Bereich Lösungsversuch 1 Dies ist keine Lösung, denn Java-Programme werden in Bytekode übersetzt. Dabei wird aus der einer Java-Anweisung 1 kontostand += betrag ; schematisch etwa folgender Code: 1 2 3 LOAD ( kontostand ); ADD ( betrag ); STORE ( kontostand ); Die JVM führt also 3 Anweisungen aus, wobei dann auch wieder dazwischen umgeschaltet werden kann. 49 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Kritischer Bereich Lösungsversuch 2 (Sperrvariable) Die Idee ist die Verwendung von Sperrvariablen: • Zu einem Zeitpunkt darf nur eine Angestellte eine Buchung ausführen. • Erst wenn die Buchung abgeschlossen ist, darf eine andere Angestellte buchen. Dies ist offensichtlich eine korrekte Lösung: der kritische Bereich wird zu einem Zeitpunkt nur einmal betreten. Ein Implementierungsversuch ist: Alle Klassen entsprechen den des Ausgangsbeispiels; nur die Klasse Bank wird wie folgt geändert. 50 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Kritischer Bereich 1 2 3 class Bank { private Konto [] konten ; private boolean gesperrt ; public Bank () { konten = new Konto [100]; for ( int i = 0; i < konten . length ; i ++) { konten [ i ] = new Konto (); } gesperrt = false ; } 5 6 7 8 9 10 11 ← public void buchen ( int kontonr , float betrag ) { while ( gesperrt ); ← gesperrt = true ; ← float alterStand = konten [ kontonr ]. abfragen (); float neuerStand = alterStand + betrag ; konten [ kontonr ]. setzen ( neuerStand ); gesperrt = false ; ← } 13 14 15 16 17 18 19 20 21 ← } Die 3 Anweisungen zum Buchen können nur ausgeführt werden, falls die Bank momentan nicht gesperrt ist. Ist die Bank gesperrt, wartet der Thread, bis sie durch den sperrenden Thread wieder entsperrt wird. Wie bewerten Sie diese Lösung ? 51 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Kritischer Bereich Diese Realisierung ist auf den ersten Blick korrekt; es gibt aber folgende Probleme: 1 Paralleles Arbeiten wird unmöglich: parallel könnten unterschiedlichen Konten bebucht werden – dies ist hier ausgeschlossen. 2 Durch das aktive Warten (while (gesperrt) ;) wird kostbare Rechenzeit aufgewendet, um nichts zu tun. 3 Die Realisierung ist nicht korrekt. Das aktive Warten ist keine unteilbare Aktion, denn der Bytekode sieht etwa wie folgt aus: while ( gesperrt ); 1: LOAD ( gesperrt ); 2: JUMPTRUE 1; gesperrt = true ; 3: LOADNUM ( TRUE ); 4: STORE ( geperrt ) Wenn hierbei zwischen Befehl 1 und 2 umgeschaltet wird und die Sperre frei ist (geperrt=false) so kann ein wartender Thread buchen. Um eine korrekte Lösung in Java zu programmieren, sind in Java Sprachelemente zur Synchronisation verfügbar. 52 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Lösungsansätze in Java Die erste korrekte (aber ineffiziente) Lösung für die Probleme 2 (aktives Warten) und 3 (verlorene Buchungen) nutzt die Möglichkeit von Java, Methoden als synchronized zu kennzeichnen. Dazu wird einfach die Klasse buchen mit dem Attribut synchronized versehen – ansonsten ändert sich nichts. 1 2 class Bank { private Konto [] konten ; public Bank () { konten = new Konto [100]; for ( int i = 0; i < konten . length ; i ++) { konten [ i ] = new Konto (); } 4 5 6 7 8 public synchronized void buchen ( int kontonr , float betrag ) { float alterStand = konten [ kontonr ]. abfragen (); float neuerStand = alterStand + betrag ; konten [ kontonr ]. setzen ( neuerStand ); } 10 11 12 13 14 15 } 53 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java synchronized Die Java-Superklasse Object beinhaltet als Eigenschaft eine Sperre. Da jede Klasse von Object abgeleitet ist, besitzen alle Klassen diese Eigenschaft. Das Sperren gehorcht dem acquire-release Protokoll“: ” Die Sperre wird gesetzt (acquire), beim Betreten einer synchronized-Methode (oder synchronized Blocks) und entfernt (release) bei Verlassen des Blocks (auch bei Verlassen durch eine Exception). 54 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java acquire release Protokoll • Wird eine Methode einer Klasse mit synchronized gekennzeichnet, so muss diese Sperre zuerst gesetzt werden, bevor die Methode ausgeführt wird, hier versucht von Thread B. • Hat ein anderer Thread A die Sperre bereits gesetzt (seine Methode ist in Ausführung), so wird der aufrufende Thread B blockiert. Thread B Tread A • Das Blockieren ist aber nicht durch aktives Warten realisiert, sondern der Thread B wird beim Thread-Umschalten nicht mehr berücksichtigt. Sperre Object • Wenn die Methode des Thread A beendet ist, wird die Sperre entfernt und der Thread B wird wieder beim Scheduling wieder berücksichtigt. 55 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Somit ist das Problem 2 und 3 gelöst. Diese Lösung ist aber ineffizient (Problem 1), da die ganze Bank gesperrt wird, obwohl doch nur Probleme auftauchen, wenn auf das selbe Konto gebucht wird. Dies wird im Beispielprogramm umgesetzt,indem die Java-Eigenschaft, Blöcke als synchronized zu kennzeichnen, verwendet wird. 1 2 class Bank { ... public void buchen ( int kontonr , float betrag ) { synchronized(konten[kontonr]) { float alterStand = konten [ kontonr ]. abfragen (); float neuerStand = alterStand + betrag ; konten [ kontonr ]. setzen ( neuerStand ); } } 4 5 6 7 8 9 10 11 } Ein Block (...) wird dabei mit dem Schlüsselwort synchronized versehen und das Objekt, auf das sich die Sperre bezieht wird danach angegeben. 56 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Verwendung von synchronized Wenn alle Threads nur lesend auf ein Objekt zugreifen, ist keine Synchronisation erforderlich – hier würde durch synchronized ein unnötiger Rechenaufwand erzeugt werden (Setzen und Lösen der Sperre). Generell gilt folgende Regel: Wenn von mehreren Threads auf ein Objekt zugegriffen wird, wobei mindestens ein Thread den Zustand (repräsentiert durch die Werte der Attribute) des Objekts ändert, dann müssen alle Methoden, die auf den Zustand lesend oder schreibend zugreifen, mit synchronized gekennzeichnet werden. 57 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Petrinetz für synchronized Betrachten wir folgendes Beispiel, ein Programm, bei dem alle Threads auf demselben Objekt arbeiten: 1 2 3 4 5 6 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class K { public synchronized void public synchronized void private void action1 () { private void action2 () { } m1 () { action1 (); } m2 () { action2 (); } ... } ... } class T extends Thread { private K einK ; public T ( K k ) { einK = k ; } public void run () { while (...) { switch (...) { case ...: einK . m1 (); break ; case ...: einK . m2 (); break ; } } } } → petrinet 58 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Petrinetz für synchronized • Die Stellen des Petri-Netzes entsprechen den Stellen zwischen den Anweisungen im Programmtext. • Die Transitionen sind die Anweisungen und die Marken sind die Threads bzw. die Sperre des gemeinsam benutzten K-Objektes, die durch synchronized belegt wird. • Zum Start eines Thread muss sowohl eine Marke auf start“ als auch auf lock“ liegen. ” ” • Schaltet eine Transition, so wird die Marke von lock“ entfernt und erst wieder auf sie ” gelegt, wenn die synchronized Methode syncEnd“ erreicht hat. ” • Damit kann zu einem Zeitpunkt höchstens ein Thread eine der synchronized Methoden auf ein Objekt anwenden. 59 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Test, ob eine Sperre gesetzt ist Der Test, ob eine Sperre gesetzt ist, ist durch holdsLock möglich: Main.java 1 2 3 public class Main { public static void main ( String [] argv ) throws Exception { Object o = new Object (); System . out . println ( Thread . holdsLock ( o )); synchronized ( o ) { System . out . println ( Thread . holdsLock ( o )); } 5 6 7 8 ← } 9 10 ← } 60 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Hörsaalübung Welche Ausgabe erzeugt das Programm? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Test.java class Even { private int n = 0; public int next () { // POST : next is always even ← ++ n ; try { Thread . sleep (( long )( Math . random ()*10)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) { } ++ n ; return n ; } } public class Test extends Thread { private Even e ; public Test ( Even e ) { this . e = e ;} public void run () { for ( int i = 1 ; i <= 1000; i ++) { System . out . println ( getName ()+ " : " + e . next ()); } } public static void main ( String [] args ) { Even e = new Even (); Test t1 = new Test ( e ); t1 . start (); } } 61 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java → Hörsaalübung Welche Ausgabe erzeugt das Programm? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Test1.java class Even { ... } public class Test1 extends Thread { private Even e ; public Test1 ( Even e ) { this . e = e ; } public void run () { for ( int i = 1 ; i <= 1000; i ++) { System . out . println ( getName ()+ " : " + e . next ()); } } public static void main ( String [] args ) { Even e = new Even (); Test1 t1 = new Test1 ( e ); Test1 t2 = new Test1 ( e ); t1 . start (); t2 . start (); } } Wie ist das Programm Test1.java abzuändern, so dass die Ausgabe der Intuition der Verwendung der Klasse Even entspricht? 62 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait und notify Werden Objekte von mehreren Threads benutzt und dabei deren Zustände verändert, dann gewährleisten synchronized-Methoden, dass ein Thread immer einen konsistenten Zustand eines Objektes vorfindet. In vielen Anwendungssituationen ist es erforderlich, dass eine Methode nur dann ausgeführt wird, • wenn zusätzlich zum konsistenten Zustand • weitere anwendungsspezifische Bedingungen erfüllt sind. 63 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Beispiel Parkhaus Ein Parkhaus wird modelliert, indem der Zustand durch die aktuell noch freie Parkplätze beschrieben wird und Autos durch Threads mit Methoden Einfahren (enter()) und Ausfahren (leave()) realisiert sind. • Beim Einfahren vermindert sich die Anzahl der freien Plätze um 1; beim Ausfahren eines Autos wird sie um 1 erhöht. • Die freien Plätze können nicht erhöht werden, wenn das Parkhaus voll ist (freie Plätze == 0). • Da ein Parkhaus von mehreren Autos (Threads) benutzt wird und der Zustand (=freie Plätze) durch ein Auto verändert wird, müssen die Methoden enter() und leave() synchronized sein. Zunächst werden zwei vergebliche Versuche, das Problem freie Plätze“ ” zu lösen gezeigt, dann eine korrekte Implementierung. 64 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java unkorrekter Versuch ParkingGarageOperation.java 1 2 3 4 5 6 7 8 9 10 11 12 13 class ParkingGarage { private int places ; public ParkingGarage ( int if ( places < 0) places } public synchronized void while ( places == 0) ; places - -; } public synchronized void places ++; } } places ) { = 0; this . places = places ; enter () { // enter parking garage ← aktives Warten leave () { // leave parking garage Diese Lösung hat zwei Probleme: 1 Aktives Warten führt zu schlechter Performance. 65 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java unkorrekter Versuch ParkingGarageOperation.java 1 2 3 4 5 6 7 8 9 10 11 12 13 class ParkingGarage { private int places ; public ParkingGarage ( int if ( places < 0) places } public synchronized void while ( places == 0) ; places - -; } public synchronized void places ++; } } places ) { = 0; this . places = places ; enter () { // enter parking garage ← aktives Warten leave () { // leave parking garage Diese Lösung hat zwei Probleme: 1 Aktives Warten führt zu schlechter Performance. 2 Die Lösung arbeitet nicht korrekt, wenn ein Parkhaus einmal voll geworden ist. Wenn ein Auto in ein volles Parkhaus einfahren will (Aufruf Methode enter), dann kann das Parkhaus nicht mehr benutzt werden, weil der Thread wegen synchronized und der while-Schleife die Sperre nie mehr freigibt, da kein anderes Auto ausfahren kann (wegen der Sperre). 65 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java unkorrekter Versuch ParkingGarageOperation.java 1 2 3 4 5 6 7 8 9 10 11 12 13 class ParkingGarage { private int places ; public ParkingGarage ( int if ( places < 0) places } public synchronized void while ( places == 0) ; places - -; } public synchronized void places ++; } } places ) { = 0; this . places = places ; enter () { // enter parking garage ← aktives Warten leave () { // leave parking garage Diese Lösung hat zwei Probleme: 1 Aktives Warten führt zu schlechter Performance. 2 Die Lösung arbeitet nicht korrekt, wenn ein Parkhaus einmal voll geworden ist. Wenn ein Auto in ein volles Parkhaus einfahren will (Aufruf Methode enter), dann kann das Parkhaus nicht mehr benutzt werden, weil der Thread wegen synchronized und der while-Schleife die Sperre nie mehr freigibt, da kein anderes Auto ausfahren kann (wegen der Sperre). 65 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Korrekte Lösung ohne aktives Warten Das aktive Warten auf einen freien Platz ist weder im gesperrten Zustand des Parkhaus-Objektes noch im nicht gesperrten Zustand korrekt. Durch die Methoden wait() und notify() der Klasse Objekt kann das Problem gelöst werden. public class Object { ... public final void wait () throws I n t e r r u p t e d E x c e p t i o n {...} public final void notify () { ...} } Diese beiden Methoden müssen auf ein Objekt angewendet werden, das durch synchronized gesperrt ist, ansonsten tritt die Ausnahme IllegalMonitorStateException“ auf. ” 66 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait, notify, notifyAll Ein wait bewirkt die folgenden Aktionen: 1 Der laufende Thread blockiert. 2 Wenn der laufende Thread unterbrochen wurde, wird die Ausnahme InterruptedException erzeugt. 3 Die JVM fügt den laufenden Thread in eine Menge (Waitingset) ein, die mit dem Objekt assoziiert ist. 4 Der synchronization Lock für das Objekt wird freigegeben (released), alle anderen Locks bleiben erhalten. 67 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait, notify, notifyAll Ein notify bewirkt die folgenden Aktionen: 1 Ein zufälliger Thread t wird aus dem Waitingset des Objektes ausgewählt. 2 t muss den Lock des Objektes wieder erhalten, d.h. Er blockiert solange, bis der Thread der notify aufgerufen hat, den Lock besitzt oder bis ein anderer Thread, der den Lock hält, ihn freigegeben hat. 3 t wird nach erhalten des Lock nach seinem wait weitermachen. Ein notifyAll arbeitet genauso, nur dass alle Threads im Waitingset ausgewählt werden (Achtung: nur einer kann aber weitermachen, da die anderen ja auf den Erhalt des Lock warten. sind. 68 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait, notify, notifyAll 69 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait, notify, notifyAll 70 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait, notify, notifyAll 71 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait, notify, notifyAll 72 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait, notify, notifyAll 73 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Parkhaus mit korrekter Lösung ParkingGarageOperation.java 1 2 class ParkingGarage { private int places ; public ParkingGarage ( int places ) { if ( places < 0) places = 0; this . places = places ; } 4 5 6 7 8 public synchronized void enter () { // enter parking garage while ( places == 0) { try { wait (); ← } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } places - -; } 10 11 12 13 14 15 16 17 public synchronized void leave () { // leave parking garage places ++; notify (); ← } 19 20 21 22 23 } 74 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Parkhaus mit korrekter Lösung 1 2 class Car extends Thread { private ParkingGarage parkingGarage ; public Car ( String name , ParkingGarage p ) { super ( name ); this . parkingGarage = p ; start (); } 4 5 6 7 8 public void run () { while ( true ) { try { sleep (( int )( Math . random () * 10000)); // drive before parking } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} parkingGarage . enter (); System . out . println ( getName ()+ " : entered " ); try { sleep (( int )( Math . random () * 20000)); // stay into the garage } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} parkingGarage . leave (); System . out . println ( getName ()+ " : left " ); } } 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 } 75 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Parkhaus mit korrekter Lösung 1 2 3 4 5 6 7 8 public class P a r k i n g G a r a g e O p e r a t i o n { public static void main ( String [] args ){ ParkingGarage parkingGarage = new ParkingGarage (10); for ( int i =1; i <= 40; i ++) { Car c = new Car ( " Car " +i , parkingGarage ); } } } 76 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Frage 1 2 class ParkingGarage { ... public synchronized void enter () { // enter parking garage while ( places == 0) { ← if anstelle von while try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } places - -; } 4 5 6 7 8 9 10 11 public synchronized void leave () { // leave parking garage places ++; notify (); } 13 14 15 16 } Was passiert, wenn man in der Methode enter(), die while-Schleife durch ein if ersetzt? Die Lösung müsste immer noch korrekt sein, da der Thread doch erst geweckt wird, wenn ein Platz frei wurde und somit kann er in das Parkhaus einfahren. Ist die Argumentation korrekt? 77 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Antwort Nein, denn obwohl er geweckt wird, wenn ein Platz frei geworden ist, heißt das ja nicht, dass er auch vom Scheduler ausgewählt wird. Es könnte ein anderer Thread, der gerade einfahren will ausgewählt werden, der dann den freien Platz belegt. Dies entspricht in der Realität dem Tatbestand, dass ein Auto vor dem Einlass steht und von einem anderen dahinter stehenden Wartenden überholt wird, der dann in die letzte Parklücke einfährt. 78 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Modellierung von wait- und notify durch Petri-Netze class K { public synchronized void m1 () { while (...) wait (); ← action1 (); } public synchronized void m2 () { action2 (); notify (); ← } private void action1 () { ... } private void action2 () { ... } } class T extends Thread { private K einK ; public T ( K k ) { einK = k ; } public void run () { while (...) switch (...) { case ...: einK . m1 (); break ; case ...: einK . m2 (); break ; } } } 79 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Die Stelle checkCond1“ entspricht der Überprüfung der Wartebedingung. Der ” Aufruf von wait() wird durch die Transition wait1“ modelliert. Die Anzahl der ” Marken in der Stelle waiting1“ entspricht der Anzahl der wartenden Threads. ” Das Schalten der Transition wait1“ bewirkt, dass eine Marke auf die Stelle ” lock“ gelegt wird, dies entspricht der Freigabe der Sperre beim Aufruf von ” wait() – dadurch können weitere Threads die Methode m1“ aufrufen. ” Irgendwann wird ein Thread die Methode m2“ aufrufen. Dann wird nach ” Schalten der Transition action2“ eine Marke auf der Stelle afterAction2“ ” ” liegen. Nun folgt notify(): die Transition notify2“ kann auf jeden Fall schalten. ” Falls sich mindestens eine Marke in der Stelle waiting1“ befindet, kann ” zusätzlich die Transition wakeup1“ schalten. Dadurch entsteht zwischen ” wakeup1“ und notify2“ ein Konflikt. Durch Zuweisen einer höheren Priorität ” ” an wakeup1“ erreicht man, dass nur die Transition wackup1“ schaltet. Die ” ” Transition notify2“ soll nämlich nur Schal- ten, wenn sich keine Marke auf ” waiting1“ befindet (dies ist die Semantik non notify(): notify() weckt genau ” einen Thread, wenn es einen solchen gibt; ansonsten ist notify() wirkungslos). 80 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java wait() und notifyAll() Probleme mit wait() und notify() entstehen, wenn mehrere Threads in der Warteschlange stehen und der falsche Thread geweckt wird. Dies wird am Erzeuger-Verbraucher Problem demonstriert. 81 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erzeuger-Verbraucher Problem • Ein Erzeuger (producer) will Werte an einen Verbraucher (consumer) senden. • Erzeuger und Verbraucher sind Threads. • Die Werte, die ausgetauscht werden, sind in einem Puffer (implementiert durch Integervariable) abgelegt, auf die beide Threads Zugriff haben. • Der Erzeuger verwendet die Methode put(), um einen Wert in den Puffer zu schreiben; • der Verbraucher kann einen Wert aus dem Puffer durch die Methode get() lesen. 82 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erzeuger-Verbraucher Problem • Bei der Realisierung darf es nicht vorkommen, dass ein Wert verloren geht, etwa weil der Erzeuger einen neuen Wert in den Puffer schreibt, bevor der Verbraucher den alten Wert gelesen hat. • Weiterhin darf ein Wert nicht mehrmals gelesen werden, etwa wenn der Verbraucher erneut liest, bevor der Erzeuger einen neuen Wert geschrieben hat. • Diese Problematik soll durch Warten realisiert werden: • der Erzeuger wartet mit dem Schreiben, bis der Verbraucher den Wert gelesen hat; • der Verbraucher wartet, bis ein neuer Wert in den Puffer geschrieben ist. 83 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Unkorrekte Lösung Der Puffer wird durch die Klasse Buffer mit • den Attributen • data (Werte) und • available (Flag zeigt an, ob Daten bereit stehen) und den • Methoden • put() und • get() realisiert. 84 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java ProduceConsume.java 1 2 3 class Buffer { private boolean available = false ; private int data ; public synchronized void put ( int x ) { while ( available ) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } data = x ; available = true ; notify (); } 5 6 7 8 9 10 11 12 13 14 public synchronized int get () { while (! available ) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } available = false ; notify (); return data ; } 16 17 18 19 20 21 22 23 24 25 26 ← ← ← ← } 85 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erzeuger und Verbraucher werden als Threads modelliert, wobei im Konstruktor eine Referenz auf das gemeinsam benutzte Objekt (buffer) übergeben wird. Der Erzeuger produziert 100 Werte, der Verbraucher konsumiert 100 Werte. 1 2 3 class Producer extends Thread { private Buffer buffer ; private int start ; public Producer ( Buffer b , int s ) { buffer = b ; start = s ; } 5 6 7 8 public void run () { for ( int i = start ; i < start +100; i ++) { buffer . put ( i ); } } 10 11 12 13 14 15 } 86 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java 1 2 class Consumer extends Thread { private Buffer buffer ; public Consumer ( Buffer b ) { buffer = b ; } 4 5 6 public void run () { for ( int i =0; i <100; i ++) { int x = buffer . get (); System . out . println ( " got " + x ); } } 8 9 10 11 12 13 14 } 87 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java In der Klasse ProduceConsume werden ein Puffer, ein Erzeuger und ein Verbraucher erzeugt und die beiden Threads gestartet. 1 2 3 4 5 6 7 8 9 public class ProduceConsume { public static void main ( String [] args ) { Buffer b = new Buffer (); Consumer c = new Consumer ( b ); Producer p = new Producer (b , 1); c . start (); p . start (); } } Ein Aufruf des Programms bewirkt also: $ java ProduceConsume got 1 got 2 got 3 got 4 got 5 ... got 100 $ 88 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Normalerweise hat man die Situation, dass mehrere Erzeuger und mehrere Verbraucher parallel arbeiten: ProduceConsume2.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ProduceConsu m e2 { public static void main ( String [] args ) { Buffer b = new Buffer (); Consumer c1 = new Consumer ( b ); Consumer c2 = new Consumer ( b ); Consumer c3 = new Consumer ( b ); Producer p1 = new Producer (b , 1); Producer p2 = new Producer (b , 101); Producer p3 = new Producer (b , 201); c1 . start (); c2 . start (); c3 . start (); p1 . start (); p2 . start (); p3 . start (); } } 89 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Ein Lauf des Programms kann folgende Ausgabe bewirken (das hängt von Betriebssystem und Java-Version ab): $ java ProduceConsume2 got 1 got 101 got 2 got 102 got 103 got 201 got 3 got 104 got 202 ... got 230 got 231 got 33 got 8 got 232 D.h. das Programm bleibt stehen, es passiert nichts mehr; es wird keine neue Ausgabe mehr erzeugt, das Programm ist aber noch nicht beendet. Dieses Verhalten wurde verursacht, da durch notify() der falsche“ ” Thread geweckt wurde. 90 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Ein Lauf des Programms kann folgende Ausgabe bewirken (das hängt von Betriebssystem und Java-Version ab): $ java ProduceConsume2 got 1 got 101 got 2 got 102 got 103 got 201 got 3 got 104 got 202 ... got 230 got 231 got 33 got 8 got 232 D.h. das Programm bleibt stehen, es passiert nichts mehr; es wird keine neue Ausgabe mehr erzeugt, das Programm ist aber noch nicht beendet. Dieses Verhalten wurde verursacht, da durch notify() der falsche“ ” Thread geweckt wurde. 90 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 1 Alle Verbraucher c1, c2 und c3 können laufen. Da der Puffer anfänglich leer ist, werden alle 3 Threads durch wait() innerhalb get() blockiert und in die Warteschlange des Objektes b aufgenommen. 2 Nun startet der Thread p1, der eine Wert in den Puffer b ablegt und einen Verbraucher weckt, nehmen wir an c1. Nun hat die Warteschlange und der Puffer folgendes Aussehen: 91 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 1 Alle Verbraucher c1, c2 und c3 können laufen. Da der Puffer anfänglich leer ist, werden alle 3 Threads durch wait() innerhalb get() blockiert und in die Warteschlange des Objektes b aufgenommen. 2 Nun startet der Thread p1, der eine Wert in den Puffer b ablegt und einen Verbraucher weckt, nehmen wir an c1. Nun hat die Warteschlange und der Puffer folgendes Aussehen: 91 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 3 p1 läuft weiter und will einen Wert in den Puffer schreiben. Da der Puffer voll ist, blockiert er und wird in die Warteschlange eingereiht: 4 Nehmen wir an, es wird auf den Erzeuger p2 umgeschaltet. Da der Puffer immer noch voll ist, wird auch p2 blockiert. Dies wiederholt sich für p3: 92 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 3 p1 läuft weiter und will einen Wert in den Puffer schreiben. Da der Puffer voll ist, blockiert er und wird in die Warteschlange eingereiht: 4 Nehmen wir an, es wird auf den Erzeuger p2 umgeschaltet. Da der Puffer immer noch voll ist, wird auch p2 blockiert. Dies wiederholt sich für p3: 92 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 5 Der einzige Thread, auf den nun noch umgeschaltet werden kann, ist der vorher geweckte Thread c1. Der nimmt nun einen Wert aus dem Puffer. Dann wird einer der Threads in der Warteschlange geweckt, gehen wir von c2 aus. 6 c1 arbeitet weiter und will einen Wert aus dem Puffer nehmen; der Puffer ist leer, also wird c1 blockiert und es steht noch immer kein Wert im Puffer: 93 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 5 Der einzige Thread, auf den nun noch umgeschaltet werden kann, ist der vorher geweckte Thread c1. Der nimmt nun einen Wert aus dem Puffer. Dann wird einer der Threads in der Warteschlange geweckt, gehen wir von c2 aus. 6 c1 arbeitet weiter und will einen Wert aus dem Puffer nehmen; der Puffer ist leer, also wird c1 blockiert und es steht noch immer kein Wert im Puffer: 93 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 7 Der Thread, auf den als einziges umgeschaltet werden kann, ist der Verbraucher c2. Er versucht einen Wert zu lesen, der Puffer ist leer, also blockiert er: Da nun alle Thread in der Warteschlange sind, kann keiner weiter arbeiten, das Programm steht. Schritt 5 hat außerdem gezeigt : der Verbraucher c1 hat einen anderen Verbraucher (c2) geweckt. Dies war der falsche“ Thread. ” 94 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 7 Der Thread, auf den als einziges umgeschaltet werden kann, ist der Verbraucher c2. Er versucht einen Wert zu lesen, der Puffer ist leer, also blockiert er: Da nun alle Thread in der Warteschlange sind, kann keiner weiter arbeiten, das Programm steht. Schritt 5 hat außerdem gezeigt : der Verbraucher c1 hat einen anderen Verbraucher (c2) geweckt. Dies war der falsche“ Thread. ” Die Lösung des Problems kann nun darin bestehen, dass nicht ein Thread, sondern alle Thread geweckt werden. Da nur einer ausgewählt wird und jeder in einer while-Schleife prüft, ob er arbeiten kann, wird das funktionieren. 94 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Erklärung 7 Der Thread, auf den als einziges umgeschaltet werden kann, ist der Verbraucher c2. Er versucht einen Wert zu lesen, der Puffer ist leer, also blockiert er: Da nun alle Thread in der Warteschlange sind, kann keiner weiter arbeiten, das Programm steht. Schritt 5 hat außerdem gezeigt : der Verbraucher c1 hat einen anderen Verbraucher (c2) geweckt. Dies war der falsche“ Thread. ” Die Lösung des Problems kann nun darin bestehen, dass nicht ein Thread, sondern alle Thread geweckt werden. Da nur einer ausgewählt wird und jeder in einer while-Schleife prüft, ob er arbeiten kann, wird das funktionieren. 94 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Korrekte Lösung durch wait-notifyAll Die Klasse Objekt besitzt neben dem einfachen notify(), eine weitere Methode zum Aufwecken von Threads: notifyAll() weckt alle in der Warteschlange eines Objekts befindlichen Threads. Die korrekte Lösung besteht also darin, notify() durch notifyAll() zu ersetzen: ProduceConsume3.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Buffer { ... public synchronized void put ( int x ) { while ( available ) { try { wait ();} catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } data = x ; available = true ; notifyAll (); ← } public synchronized int get () { while (! available ) { try { wait ();} catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } available = false ; notifyAll (); ← return data ; } } 95 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Regel für notifyAll Prinzipiell kann man immer notify() durch notifyAll() ersetzen, die Umkehrung allerdings wäre falsch (wie im letzten Beispiel gesehen). Die Methode notifyAll() ist zu verwenden, wenn mindestens eine der beiden folgenden Situationen zutrifft: 1 2 In der Warteschlange befinden sich Threads, mit unterschiedlichen Wartebedingungen (z.B. Puffer leer, Puffer voll). Dann kann bei Verwendung von notify() der falsche“ Thread geweckt werden. ” Durch die Veränderung des Zustands eines Objekts können mehrere Threads weiterlaufen (Wert im Puffer - alle wartenden Verbraucher können arbeiten). 96 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Lösungsansätze in Java Modellierung von wait- und notifyAll durch Petri-Netze class K { public synchronized void m1 () { while (...) wait (); ← action1 (); } public synchronized void m2 () { action2 (); notifyAll (); ← } private void action1 () { ... } private void action2 () { ... } } class T extends Thread { private K einK ; public T ( K k ) { einK = k ; } public void run () { while (...) switch (...) { case ...: einK . m1 (); break ; case ...: einK . m2 (); break ; } } } 97 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Semaphore 1965 wurde von Dijkstra das Konzept der Semaphore zur Synchronisation vorgeschlagen. • Eine Semaphore ist eine Integervariable, deren • Wert Null anzeigt, dass kein Prozess (Thread) mehr zu wecken ist, • ein Wert grösser Null bedeutet, dass Wecksignale zu berücksichtigen sind. • Als Operationen wurde definiert DOWN“ (=p) und UP“ (=v). ” ” • Down“ prüft, ob der Semaphore größer als Null ist, dann wird der ” Wert vermindert; ist der Wert Null, wird der Thread schlafen gelegt und die DOWN-Operation ist beendet. • UP“ inkrementiert den Semaphore. ” Sowohl UP“ als auch DOWN“ sind dabei atomare Operationen, ” ” d.h. es ist sichergestellt, dass während der Ausführung der Operation kein anderer Thread auf den Semaphore zugreifen kann. 98 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Semaphore Implementierung in Java Semaphore.java public class Semaphore { private int value ; public Semaphore ( int init ) { if ( init < 0) init = 0; value = init ; } ← public synchronized void p () { ← Dijkstra’s while ( value == 0) { try { wait (); } ← catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } value - -; } public synchronized void v () { value ++; notify (); } operation p=down ← Dijkstra’s operation v=up ← } 99 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Gegenseitiger Ausschluss mit Semaphoren Gegenseitiger Ausschluss bezeichnet eine Situation, in der ein bestimmtes Programmstück zu einer Zeit von höchstens einem Thread ausgeführt werden darf. • In Java kann dazu eine synchronized Methode verwendet werden: auf ein Objekt kann dadurch zu einem Zeitpunkt nur von einem Thread zugegriffen werden. • Hier soll nun nicht mit synchronized gearbeitet werden, sondern es wird eine Klasse mit einer Semaphore verwendet. Als kritischen Abschnitt bezeichnet man das Programmstück, das von höchstens einem Thread betreten werden darf. 100 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Gegenseitiger Ausschluss mit Semaphoren Im folgenden Beispiel werden die Aktionen, die ein Thread im kritischen Abschnitt ausführt simuliert durch eine Sleep-Operation. In einer realen Anwendung würde hier das Codefragment stehen, das auf die gemeinsamen Objekte zugreift. Der kritische Abschnitt wird durch Semaphore geschützt, indem 1 vor Betreten die Semaphor-Methode down(), 2 danach die Methode up() aufgerufen wird. 101 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore MutualExclusion.java 1 2 class MutexThread extends Thread { private Semaphore mutex ; public MutexThread ( Semaphore mutex , String name ) { super ( name ); this . mutex = mutex ; start (); } 4 5 6 7 8 public void run () { while ( true ) { mutex . p (); ← System . out . println ( " kritischen Abschnitt betreten : " + getName ()); try { sleep (( int )( Math . random () * 100));} catch ( I n t e r r u p t e d E x c e p t i o n e ) {} mutex . v (); ← System . out . println ( " kritischen Abschnitt verlassen : " + getName ()); } } 10 11 12 13 14 15 16 17 18 19 20 21 22 } 102 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore MutualExclusion.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MutualExcl us io n { public static void main ( String [] args ) { int n o T h r e a d s I n C r i t i c a l S e c t i o n =1; if ( args . length != 1) { System . err . println ( " usage : java Mu t ua lE x cl us io n < NoThreadsInCriticalSection > " ); System . exit (1); } else n o T h r e a d s I n C r i t i c a l S e c t i o n = Integer . parseInt ( args [0]); Semaphore mutex = new Semaphore ( n o T h r e a d s I n C r i t i c a l S e c t i o n ); for ( int i = 1; i <= 10; i ++) { new MutexThread ( mutex , " Thread " + i ); } } } 103 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Der Aufruf zeigt, dass stets nur so viele Threads im kritischen Abschnitt sind, wie man beim Aufruf angegeben hat: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ java MutualExclusion 1 kritischen Abschnitt betreten : kritischen Abschnitt verlassen : kritischen Abschnitt betreten : kritischen Abschnitt verlassen : kritischen Abschnitt betreten : kritischen Abschnitt verlassen : kritischen Abschnitt betreten : kritischen Abschnitt verlassen : ... $ java MutualExclusion 3 kritischen Abschnitt betreten : kritischen Abschnitt betreten : kritischen Abschnitt betreten : kritischen Abschnitt verlassen : kritischen Abschnitt betreten : kritischen Abschnitt verlassen : kritischen Abschnitt betreten : kritischen Abschnitt verlassen : kritischen Abschnitt betreten : ... Thread Thread Thread Thread Thread Thread Thread Thread 1 1 1 1 3 3 1 1 ← 1 Thread Thread Thread Thread Thread Thread Thread Thread Thread 1 2 3 1 1 1 4 4 1 ← 1 ← 0 ← 1 ← 0 ← 1 ← 0 ← 1 ← 0 ← 2 ← 3 ← 2 ← 3 ← 2 ← 3 ← 2 ← 3 104 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Frage Was passiert durch Aufruf von $ java MutualExclusion 0 105 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Hörsaalübung Ändern Sie das letzte Programm (EvenThread) so ab, dass die Klasse Even ohne synchronized- Methoden auskommt. Verwenden Sie dazu Semaphore. Test.java 1 2 3 4 5 6 7 8 9 10 class Even { private int n = 0; public int next () { // POST : next is always even ++ n ; try { Thread . sleep (( long )( Math . random ()*10)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) { } ++ n ; return n ; } } ← 106 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Bearbeitungsreihenfolge von Threads festlegen In vielen Anwendungen ist es erforderlich, dass Threads parallel arbeiten und dennoch eine Abarbeitungsreihenfolge einzuhalten ist. Dabei starten alle Threads gleichzeitig, führen gewisse Aktionen aus und müssen dann warten, bis andere Threads Aktivitäten abgeschlossen haben. Insgesamt lassen sich solche Abhängigkeiten durch einen Graphen darstellen: 1 Knoten sind Threads, 2 eine Kante existiert von einem Thread T1 zu T2, falls T1 seine Aktivitäten beendet haben muss, bevor T2 starten kann. Das folgende Beispiel demonstriert eine solche Situation. 107 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Die Umsetzung in Java erfolgt durch Semaphore: • Für jede Abhängigkeit (Kante im Graph) wird eine Semaphore verwendet. Die Semaphoren sind in einem Array sems zusammengefasst, so dass die nebenstehend gezeigten Abhängigkeiten definiert sind. • Bevor eine Aktion ausgeführt wird, wird die p()-Methode für alle Semaphoren, die den eingehenden Kanten entsprechen, ausgeführt; • nach der Aktion die v()-Methode auf allen Semaphoren der ausgehenden Kanten. • Der Einfachheit halber bekommen alle Threads eine Referenz auf das Semaphoren-Array, auch wenn nicht alle Threads jede Semaphore benutzen. 108 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Aus Sicht des Threads i werden folgende Aktionen ausgeführt: 1 i führt p()-Operation aus: Warten auf v-Operation von i-1 2 i-1 hat v()-Operation ausgeführt: i kann Aktion ausführen 3 i führt v-Operation aus: i+1 kann aktiv werden 109 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore TimingRelation.java 1 2 class T1 extends Thread { private Semaphore [] sems ; public T1 ( Semaphore [] sems ) { this . sems = sems ; start (); } 4 5 6 7 private void a1 () { System . out . println ( " a1 " ); try { sleep (( int )( Math . random () * 10)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } 9 10 11 12 13 14 public void run () { a1 (); sems [0]. v (); sems [1]. v (); sems [2]. v (); } 16 17 18 19 20 21 22 } 110 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore TimingRelation.java 1 2 class T2 extends Thread { private Semaphore [] sems ; public T2 ( Semaphore [] sems ) { this . sems = sems ; start (); } 4 5 6 7 private void a2 () { System . out . println ( " a2 " ); try { sleep (( int )( Math . random () * 10)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } 9 10 11 12 13 14 public void run () { sems [0]. p (); ← a2 (); sems [3]. v (); ← } 16 17 18 19 20 21 } 111 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore TimingRelation.java 1 2 class T3 extends Thread { private Semaphore [] sems ; public T3 ( Semaphore [] sems ) { this . sems = sems ; start (); } 4 5 6 7 private void a3 () { System . out . println ( " a3 " ); try { sleep (( int )( Math . random () * 10)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } 9 10 11 12 13 14 public void run () { sems [1]. p (); a3 (); sems [4]. v (); } 16 17 18 19 20 21 } 112 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore TimingRelation.java 1 2 class T4 extends Thread { private Semaphore [] sems ; public T4 ( Semaphore [] sems ) { this . sems = sems ; start (); } 4 5 6 7 private void a4 () { System . out . println ( " a4 " ); try { sleep (( int )( Math . random () * 10)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } 9 10 11 12 13 14 public void run () { sems [2]. p (); a4 (); sems [5]. v (); } 16 17 18 19 20 21 } 113 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore TimingRelation.java 1 2 class T5 extends Thread { private Semaphore [] sems ; public T5 ( Semaphore [] sems ) { this . sems = sems ; start (); } 4 5 6 7 private void a5 () { System . out . println ( " a5 " ); try { sleep (( int )( Math . random () * 10)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } 9 10 11 12 13 14 public void run () { sems [3]. p (); sems [4]. p (); sems [5]. p (); a5 (); } 16 17 18 19 20 21 22 } 114 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore TimingRelation.java 1 2 3 4 5 6 7 8 9 10 11 12 13 public class TimingRelation { public static void main ( String [] args ) { Semaphore [] sems = new Semaphore [6]; for ( int i = 0; i < 6; i ++) { sems [ i ] = new Semaphore (0); } new T4 ( sems ); new T5 ( sems ); new T1 ( sems ); new T2 ( sems ); new T3 ( sems ); } } $ java TimingRelation a1 a3 a2 a4 a5 $ java TimingRelation a1 a2 a3 a4 a5 $ 115 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Additive Semaphoren Semaphoren sind Integerwerte, wobei die p()- und v()-Operationen den Integerwert jeweils um eins inkrementieren bzw. dekrementieren. Eine Verallgemeinerung davon stellen additive Semaphore dar: der Integer kann um beliebige Werte inkrementiert bzw. dekrementiert werden. 116 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Die Methoden p() und v() haben ein Integerargument, das angibt, um welchen Wert erniedrigt bzw. erhöht werden soll. Dieses Argument muss positiv sein, ansonsten könnte z.B. eine pOperation die Semaphore erhöhen. AdditiveSemaphore.java 1 2 public class Add iti veS e m a p h o r e { private int value ; public synchronized void p ( int x ) { if ( x <= 0) return ; while ( value - x < 0) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } value -= x ; } 4 5 6 7 8 9 10 11 12 13 14 ... 16 17 } 117 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore AdditiveSemaphore.java 1 2 3 4 5 6 8 9 10 11 12 13 public synchronized void v ( int x ) { if ( x <= 0) return ; value += x ; notifyAll (); // NOT notify ← } public void change ( int x ) { if ( x > 0) v ( x ); else if ( x < 0) p ( - x ); } Die Methode v(int) muss notifyAll() verwenden. Würde notify() verwendet werden, könnten u.U. mehrere Threads weiterlaufen. 118 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Der Programmcode zeigt, dass eine additive Semaphore auf einen ” Schlag“ erhöht oder erniedrigt wird. Dies bedeutet, dass die Verwendung von einer p-Operation p(n) nicht identisch ist mit n p-Operationen p(1): • Nehmen wir an, zwei Threads verwenden eine additive Semaphore zur Synchronisation; der aktuelle Wert sei 4. Beide wollen den Wert um 3 erniedrigen. • richtige Vorgehensweise: • T1 und T2 führen p(3) aus. • T1 wird ausgewählt und p(3) ist abgeschlossen. T2 blockiert beim Aufruf P(3). 119 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Der Programmcode zeigt, dass eine additive Semaphore auf einen ” Schlag“ erhöht oder erniedrigt wird. Dies bedeutet, dass die Verwendung von einer p-Operation p(n) nicht identisch ist mit n p-Operationen p(1): • Nehmen wir an, zwei Threads verwenden eine additive Semaphore zur Synchronisation; der aktuelle Wert sei 4. Beide wollen den Wert um 3 erniedrigen. • richtige Vorgehensweise: • T1 und T2 führen p(3) aus. • T1 wird ausgewählt und p(3) ist abgeschlossen. T2 blockiert beim Aufruf P(3). • falsche Vorgehensweise: • T1 und T2 führen jeweils p(1); p(1); p(1); aus. • T1 wird ausgewählt und (p(1); p(1);) ist abgeschlossen (Semaphor == 2), dann wird auf T2 umgeschaltet. • T2 führt (p(1); p(1);) nun blockiert beim Aufruf p(1); • T1 blockiert beim Aufruf p(1) → Verklemmung 119 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Der Programmcode zeigt, dass eine additive Semaphore auf einen ” Schlag“ erhöht oder erniedrigt wird. Dies bedeutet, dass die Verwendung von einer p-Operation p(n) nicht identisch ist mit n p-Operationen p(1): • Nehmen wir an, zwei Threads verwenden eine additive Semaphore zur Synchronisation; der aktuelle Wert sei 4. Beide wollen den Wert um 3 erniedrigen. • richtige Vorgehensweise: • T1 und T2 führen p(3) aus. • T1 wird ausgewählt und p(3) ist abgeschlossen. T2 blockiert beim Aufruf P(3). • falsche Vorgehensweise: • T1 und T2 führen jeweils p(1); p(1); p(1); aus. • T1 wird ausgewählt und (p(1); p(1);) ist abgeschlossen (Semaphor == 2), dann wird auf T2 umgeschaltet. • T2 führt (p(1); p(1);) nun blockiert beim Aufruf p(1); • T1 blockiert beim Aufruf p(1) → Verklemmung 119 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Semaphorgruppen Eine Semaphorgruppe ist eine Verallgemeinerung einer additiven Semaphore: • Ein Aufruf der Methode change() erhöht oder erniedrigt eine Menge von Semaphoren, die zur selben Gruppe gehören. • Die Änderungen werden nur durchgeführt, wenn alle Semaphoren der Gruppe nicht durch die Änderung negativ werden; in diesem Fall wird gewartet ohne dass eine Änderung vollzogen wird. 120 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore Die Realisierung der Klasse SemaphoreGroup verwendet ein Integerarray (values) zur Darstellung der Semaphorwerte. Die Umsetzung mit mehreren Objekten vom Typ AdditiveSemaphore würde zu Verklemmungssituationen führen. SemaphoreGroup.java 1 2 public class SemaphoreGroup { private int [] values ; public SemaphoreGroup ( int nu m be rO fM e mb er s ) { if ( numberOfMembers <= 0) return ; values = new int [ n um be r Of Me mb e rs ]; } ... 4 5 6 7 8 9 10 } Die Anzahl der Elemente der Semaphorgruppe wird im Konstruktor übergeben. Es gibt keine p()- und v()-Methode mehr; die Implementierung benutzt nur die öffentliche Methode changeValues() zur Änderung von Werten. 121 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore SemaphoreGroup.java 1 2 3 4 5 6 7 8 9 10 public synchronized void changeValues ( int [] deltas ) { if ( deltas . length != values . length ) return ; while (! canChange ( deltas )) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } doChange ( deltas ); notifyAll (); } Der Parameter der Methode changeValues gibt an, um welchen Wert die Semaphore jeweils verändert werden soll: deltas[i] gibt an, wie values[i] geändert werden soll; delta[i] kann positiv oder negativ sein. 122 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Semaphore SemaphoreGroup.java private boolean canChange ( int [] deltas ) { for ( int i = 0; i < values . length ; i ++) if ( values [ i ] + deltas [ i ] < 0) return false ; return true ; 1 2 3 4 5 6 } 8 private void doChange ( int [] deltas ) { for ( int i = 0; i < values . length ; i ++) values [ i ] = values [ i ] + deltas [ i ]; } 9 10 11 13 14 15 public int ge tN u mb e r O f M e m b e r s () { return values . length ; } Die private Methode canChange() gibt an, ob alle Änderungen durchführbar sind oder nicht. Die private Methode doChange führt die Änderungen durch. Danach werden in changeValues() alle wartenden Thread informiert (notifyAll()). Die öffentliche Methode getNumberOfMembers() dient zum Abfragen der Größe der Semaphorgruppe. Das Applet semgrp.html verdeutlicht den Gebrauch von Semaphorgruppen. 123 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Monitore und andere Primitive Monitore und andere Primitive Semaphore in ihren unterschiedlichen Ausprägungen sind Primitive der Interprozesskommunikation. Andere Primitive wurden entwickelt, sie sind aber alle jeweils durch Semaphore abbildbar, z.B.: • Hoare und Brich Hansen haben 1975 Monitore vorgeschlagen: ein Monitor ist eine gekapselte Datenstruktur, in der eine Bindungsvariable durch die Operationen WAIT und SIGNAL verwaltet wird. (vgl. Java Beispiele mit wait und notify) • Campell und Habermann führten 1974 Pfadausdrucke ein. • Atkinson und Hewitt diskutierten 1979 Serializer. 124 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation CSP - Communication Sequential Processes CSP - Communication Sequential Processes • Communicating Sequential Processes ist eine von C.A.R. Hoare an der Universität Oxford entwickelte Prozessalgebra zur Beschreibung von Interaktion zwischen kommunizierenden Prozessen. • CSP war Ausgangspunkt zur Entwicklung der Programmiersprache Occam (vgl. CSP). • CSP bildet die Grundlage für das Co-Routinen Konzept der Programmiersprache go. 125 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation CSP - Communication Sequential Processes go - Das erste Programm • Die Programmiersprache go ist von google als Sprache zur Systemprogrammierung entworfen worden. (vgl. go). • Wir werden go mit dem go-Routinen und Channel-Konzept betrachten. hello.go 1 package main 3 import " fmt " 5 func main () { fmt . Println ( " Hello , world " ) } 6 7 126 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation CSP - Communication Sequential Processes go - goroutinen g1.go 1 2 4 5 6 7 8 10 11 package main import " fmt " func f ( n int ) { for i := 0; i < 10; i ++ { fmt . Println (n , " : " , i ) } } func main () { go f (0) var input string fmt . Scanln (& input ) 13 14 15 ← } 2 go Routinen • main • go f(0) 127 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation CSP - Communication Sequential Processes go - goroutinen g3.go 1 ... 3 func f ( n int ) { for i := 0; i < 10; i ++ { fmt . Println (n , " : " , i ) amt := time . Duration ( rand . Intn (250)) time . Sleep ( time . Millisecond * amt ) } } 4 5 6 7 8 9 11 12 13 14 func main () { for i := 0; i < 10; i ++ { go f ( i ) } ← var input string fmt . Scanln (& input ) 16 17 18 ← } • go run g3.go: Die Ausgabe ist nicht vorhersehbar - warum ? • Wir benötigen Synchronisationskonzepte 128 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation CSP - Communication Sequential Processes go - channel • Channel sind Kommunikationsmittel, mit denen goroutinen Nachrichten austauschen können und sich dabei synchronisieren. • Channel haben eine Typ und mit dem Operator ’< −’ können Werte gesendet und empfangen werden. g4.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func producer ( c chan int ) { ← for i := 0; i <10; i ++ { c <- i ← } } func consumer ( c chan int ) { ← for { msg := <-c ← fmt . Println ( msg ) time . Sleep ( time . Second * 1) } } func main () { var c chan int = make ( chan int ) go consumer ( c ) go producer ( c ) var input string fmt . Scanln (& input ) } ← 129 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation CSP - Communication Sequential Processes go - channel • Ein Sender kann einen Kanal schließen, um anzuzeigen, dass keine Werte mehr gesendet werden • Ein Empfänger kann testen, ob der Kanal noch offen ist: ’v, ok := <-ch’ g5.go 1 2 3 4 5 6 7 8 10 11 12 13 14 15 16 func fibonacci ( n int , c chan int ) { x , y := 0 , 1 for i := 0; i < n ; i ++ { c <- x x, y = y, x+y } close ( c ) ← } func main () { c := make ( chan int , 5) go fibonacci ( cap ( c ) , c ) for i := range c { fmt . Println ( i ) } } ← ← 130 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation CSP - Communication Sequential Processes go - select • Ein select ermöglicht es, dass auf Kommunikation über mehrere Kanäle reagiert wird. • Ein select blockiert, bis auf einem seiner Kanäle Kommunikation möglich ist. Bei gleichzeitigem Eintreffen wird zufällig ein Kanal gewählt. 131 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation CSP - Communication Sequential Processes go - select g6.go 1 2 3 4 5 6 7 8 9 10 11 12 14 15 16 17 18 19 20 21 22 23 24 func fibonacci (c , quit chan int ) { 2 Kanäle x , y := 0 , 1 for { select { case c <- x : x, y = y, x+y case <- quit : fmt . Println ( " quit " ) return } } } func main () { c := make ( chan int ) quit := make ( chan int ) go func () { for i := 0; i < 10; i ++ { fmt . Println ( < - c ) } quit <- 0 }() fibonacci (c , quit ) } ← ← ← ← ← ← ← ← 132 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Nachrichtenaustausch Die bisher betrachteten Primitive haben vorausgesetzt, dass es einen gemeinsam nutzbaren Speicher gibt. Die Idee des Nachrichtenaustausch ist es, • ein Kommunikationsmedium (etwa ein Netz, einen Bus) zu verwenden, so dass • ein Prozess (Sender) einem anderen Prozess (Empfänger) eine Nachricht sendet. • Beide Prozesse synchronisieren sich automatisch, indem der Empfänger wartet, bis er eine Nachricht erhalten hat. 133 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Nachrichtenaustausch Die bisher betrachteten Primitive haben vorausgesetzt, dass es einen gemeinsam nutzbaren Speicher gibt. Die Idee des Nachrichtenaustausch ist es, • ein Kommunikationsmedium (etwa ein Netz, einen Bus) zu verwenden, so dass • ein Prozess (Sender) einem anderen Prozess (Empfänger) eine Nachricht sendet. • Beide Prozesse synchronisieren sich automatisch, indem der Empfänger wartet, bis er eine Nachricht erhalten hat. Als Kommunikationsprimitive sind erforderlich: • send(destination, &message) und • receive(source, &message). 133 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Nachrichtenaustausch Die bisher betrachteten Primitive haben vorausgesetzt, dass es einen gemeinsam nutzbaren Speicher gibt. Die Idee des Nachrichtenaustausch ist es, • ein Kommunikationsmedium (etwa ein Netz, einen Bus) zu verwenden, so dass • ein Prozess (Sender) einem anderen Prozess (Empfänger) eine Nachricht sendet. • Beide Prozesse synchronisieren sich automatisch, indem der Empfänger wartet, bis er eine Nachricht erhalten hat. Als Kommunikationsprimitive sind erforderlich: • send(destination, &message) und • receive(source, &message). Diese Thematik wird u.a. in der Vorlesung Verteilte Systeme“ ” behandelt. Hier wird wieder mittels Java auf • Message Queues und • Pipes eingegangen. 133 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Nachrichtenaustausch Die bisher betrachteten Primitive haben vorausgesetzt, dass es einen gemeinsam nutzbaren Speicher gibt. Die Idee des Nachrichtenaustausch ist es, • ein Kommunikationsmedium (etwa ein Netz, einen Bus) zu verwenden, so dass • ein Prozess (Sender) einem anderen Prozess (Empfänger) eine Nachricht sendet. • Beide Prozesse synchronisieren sich automatisch, indem der Empfänger wartet, bis er eine Nachricht erhalten hat. Als Kommunikationsprimitive sind erforderlich: • send(destination, &message) und • receive(source, &message). Diese Thematik wird u.a. in der Vorlesung Verteilte Systeme“ ” behandelt. Hier wird wieder mittels Java auf • Message Queues und • Pipes eingegangen. 133 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Nachrichtenaustausch Zunächst wird ein Puffer mit n Elemente definiert, der es erlaubt, Daten fester Grösse zwischen Sender und Empfänger auszutauschen. Dann wird gezeigt, wie Daten beliebiger Länger transferiert werden können. 134 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Puffer mit n Elementen • Der Erzeuger fügt Daten am Pufferende (Position tail ) ein. • Der Verbraucher entnimmt den Wert am Pufferanfang (an Position 0 head ) und • reorganisiert den Puffer, d.h. alle Elemente werden nach oben verschoben. Was halten Sie von dieser Lösung? 135 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Puffer mit n Elementen Das Reorganisieren kann vermieden werden, wenn der Puffer zyklisch organisiert wird: 136 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Puffer in Java BufferN.java 1 2 3 4 5 public class BufferN { private int head ; private int tail ; private int numberOf E l e m e n t s ; private int [] data ; public BufferN ( int n ) { data = new int [ n ]; head = 0; tail = 0; n u mberOfElements = 0; } ... 7 8 9 10 11 12 13 14 } 137 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Puffer in Java BufferN.java 1 2 3 4 5 6 7 8 9 10 11 12 public synchronized void put ( int x ) { while ( numberOfElem e n t s == data . length ) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } data [ tail ++] = x ; if ( tail == data . length ) tail = 0; n u mberOfElements ++; notifyAll (); } Wenn der Puffer voll geworden ist, muss die Methode put() warten. Die Schleife ist erforderlich, da wir notifyAll() verwenden müssen. Wenn eine freie Position vorhanden ist, wird der Wert gespeichert und die Variable tail“ zyklisch inkrementiert, danach wird ein wartender Thread geweckt. ” 138 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Puffer in Java BufferN.java 1 2 3 4 5 6 7 8 9 10 11 12 13 public synchronized int get () { while ( numberOfElem e n t s == 0) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } int result = data [ head ++]; if ( head == data . length ) head = 0; numberOfElements - -; notifyAll (); return result ; } Die Methode get() funktioniert analog. 139 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Message Queues Nun wird gezeigt, wie Daten beliebiger Länge ausgetauscht werden können. Dabei muss die Nachrichtengrenze bewahrt bleiben, d.h. wird z.B. Byte-Array gesendet so wird exakt diese Menge an Daten empfangen: 140 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Message Queues MessagaQueue.java 1 2 3 4 5 6 public class MessageQueue { private byte [][] msgQueue = null ; private int qsize = 0; // size of message queue as // number of entries ( not number of bytes ) private int head = 0; private int tail = 0; public MessageQueue ( int capacity ) { if ( capacity <= 0) return ; msgQueue = new byte [ capacity ][]; } ... 8 9 10 11 12 13 14 } Damit der Sender die Orginal Nachricht bearbeiten kann, nachdem er sie gesendet hat, wird zuerst eine Kopie erstellt. 141 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Message Queues MessageQueue.java 1 2 3 4 5 6 public synchronized void send ( byte [] msg ) { while ( qsize == msgQueue . length ) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } msgQueue [ tail ] = new byte [ msg . length ]; // copy message // and store the copy for ( int i = 0; i < msg . length ; i ++) msgQueue [ tail ][ i ] = msg [ i ]; qsize ++; tail ++; if ( tail == msgQueue . length ) tail = 0; notifyAll (); 8 9 10 11 12 13 14 15 16 17 } 142 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Message Queues MessagaQueue.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public synchronized byte [] receive () { while ( qsize == 0) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } byte [] result = msgQueue [ head ]; msgQueue [ head ] = null ; qsize - -; head ++; if ( head == msgQueue . length ) head = 0; notifyAll (); return result ; } Durch ein Applet kann das Verhalten von MessagaQueue demonstriert werden. 143 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Pipes Message Queues bewahren die Nachrichtengrenzen; deshalb wird diese Art der Kommunikation auch nachrichtenorientiert“ genannt. ” Demgegenüber ist es für den Empfänger einer Nachricht bei streamorientierten“ Kommunkation nicht möglich, die einzelnen ” Nachrichten zu unterscheiden, die an der Kommunikation beteiligt sind. Diese Verhalten verdeutlicht die Kommunikation über Pipes (mit einem Byte-Array fester Grösse) → pipe 144 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Pipe in Java Pipe.java 1 2 3 4 5 public class Pipe { private byte [] buffer = null ; private int bsize = 0; private int head = 0; private int tail = 0; public Pipe ( int capacity ) { if ( capacity <= 0) return ; buffer = new byte [ capacity ]; } ... 7 8 9 10 11 12 13 } 145 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Die send()-Operation, die Daten in eine Pipe schreibt, muss als atomare Operation realisiert werden: wenn die Nachricht grösser ist, als der noch verfügbar freie Platz in der Pipe, muss der Sender blockieren, bis ein Empfänger durch receive() Platz in der Pipe gemacht hat. Pipe.java 1 2 3 4 5 6 7 9 10 11 12 13 14 15 16 17 18 19 public synchronized void send ( byte [] msg ) { if ( msg . length <= buffer . length ) { // sent as atomic operation while ( msg . length > buffer . length - bsize ) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } // copy message into buffer for ( int i = 0; i < msg . length ; i ++) { buffer [ tail ] = msg [ i ]; tail ++; if ( tail == buffer . length ) tail = 0; } bsize += msg . length ; notifyAll (); } else { ... 146 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Wenn die gesamte Länge der Nachricht grösser ist, als die Kapazität der Pipe, so wird die Nachricht in kleine Stücke geteilt und jeweils nur ein Stück gesendet. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public synchronized void send ( byte [] msg ) { ... else { // send in portions int offset = 0; int stillToSend = msg . length ; while ( stillToSend > 0) { while ( bsize == buffer . length ) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } int sendNow = buffer . length - bsize ; if ( stillToSend < sendNow ) sendNow = stillToSend ; for ( int i = 0; i < sendNow ; i ++) { buffer [ tail ] = msg [ offset ]; tail ++; if ( tail == buffer . length ) tail = 0; offset ++; } bsize += sendNow ; stillToSend -= sendNow ; notifyAll (); } } } 147 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Interprozesskommunikation Nachrichtenaustausch Beim Empfang einer Nachricht, muss blockiert werden, bis Daten in der Pipe verfügbar sind. Ein Parameter beim receive() gibt die erwartete Grösse der Nachricht an; wenn weniger Daten in der Pipe sind, wird nicht blockiert, sondern nur der verfügbare Teil gelesen. Pipe.java public synchronized byte [] receive ( int noBytes ) { while ( bsize == 0) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } if ( noBytes > bsize ) noBytes = bsize ; byte [] result = new byte [ noBytes ]; for ( int i = 0; i < noBytes ; i ++) { result [ i ] = buffer [ head ]; head ++; if ( head == buffer . length ) head = 0; } bsize -= noBytes ; notifyAll (); return result ; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 } 148 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme IPC Problem 1 Prozessmodell 2 Interprozesskommunikation 3 IPC Probleme Problem speisender Philosophen Lese-Schreiber Problem Das Problem des schlafenden Friseurs 4 Scheduling 5 Literatur 149 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Problem speisender Philosophen Problem speisender Philosophen (Dijkstra, 1965) • Fünf Philosophen sitzen um einen runden Tisch. • Jeder hat einen Teller mit Spagetti vor sich auf dem Teller. • Zwischen jedem Teller liegt eine Gabel. Um Essen zu können, braucht man immer zwei Gabeln. • Das Leben eines Philosophen besteht aus Essen und Denken. • Wenn ein Philosoph hungrig wird, versucht er die linke und rechte Gabel zu nehmen und zu Essen. Das Problem: Es ist ein Programm für einen Philosophen zu finden, das, auf alle Philosophen angewendet, nie dazu führt, dass einer der Philosophen verhungert. 150 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Problem speisender Philosophen Dieses Problem ist typisch für Betriebssysteme: es verdeutlicht den Wettbewerb um eine begrenzte Anzahl von exklusiv nutzbaren Betriebsmitteln, wie CPU oder E/A Geräte. Eine offensichtliche (aber nicht korrekte) Lösung in C ist: 1 2 3 4 5 6 7 8 9 10 11 const int N =5; philosophers ( int i ) { while ( true ) { think (); takeFork ( i ); take_fork (( i +1)% N ); eat (); putFork ( i ); putFork (( i +1)% N ); } } // take left fork // take right fork // put left fork back // put right fork back Die Funktion takeFork() wartet, bis die entsprechende Gabel frei ist und nimmt dann die Gabel. Ist die Gabel nicht frei, wird eine Zeit lang gewartet und erneut versucht, die Gabel zu nehmen. Was halten Sie von dieser Lösung? 151 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Problem speisender Philosophen 1 2 3 4 5 6 7 8 9 10 11 const int N =5; philosophers ( int i ) { while ( true ) { think (); takeFork ( i ); take_fork (( i +1)% N ); eat (); putFork ( i ); putFork (( i +1)% N ); } } // take left fork // take right fork // put left fork back // put right fork back Die Lösung funktioniert nicht, wenn z.B. alle Philosophen gleichzeitig die linke Gabel nehmen, da niemand die rechte Gabel nehmen kann und so alle verhungern (Deadlock). 152 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Problem speisender Philosophen Mit dem synchronized-Konzept von Java ist eine Lösung realisierbar, denn das Nehmen einer Gabel wird atomar: Philosophers.java 1 2 4 5 6 7 8 10 11 12 14 15 16 17 18 19 class Table { private boolean [] usedFork ; public Table ( int numberForks ) { usedFork = new boolean [ numberForks ]; for ( int i = 0; i < usedFork . length ; i ++) usedFork [ i ] = false ; } private int left ( int i ) { return i ; } private int right ( int i ) { if ( i +1 < usedFork . length ) return i +1; else return 0; } 20 153 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Problem speisender Philosophen 21 22 23 24 25 26 27 28 29 31 32 33 34 35 36 public synchronized void takeForks ( int position ) { while ( usedFork [ left ( position )] || usedFork [ right ( position )]) { try { wait (); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} } usedFork [ left ( position )] = true ; usedFork [ right ( position )] = true ; } public synchronized void p o s i t i o n B a c k F o r k s ( int position ) { usedFork [ left ( position )] = false ; usedFork [ right ( position )] = false ; notifyAll (); } } // Table 37 154 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Problem speisender Philosophen 38 39 40 42 43 44 45 46 48 49 50 51 52 53 54 55 57 58 59 60 61 62 63 class Philosoph extends Thread { private Table Table ; private int position ; public Philosoph ( Table Table , int position ) { this . Table = Table ; this . position = position ; start (); } public void run () { life of a philosoph while ( true ) { thinking ( position ); Table . takeForks ( position ); eating ( position ); Table . po sit ion Ba c k F o r k s ( position ); } } private void thinking ( int position ) { System . out . println ( " Philosoph " + position + " is thinking . " ); try { sleep (( int )( Math . random () * 20000)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) { } } 155 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Problem speisender Philosophen private void eating ( int position ) { System . out . println ( " Philosoph " + position + " starts eating . " ); try { sleep (( int )( Math . random () * 20000)); } catch ( I n t e r r u p t e d E x c e p t i o n e ) {} System . out . println ( " Philosoph " + position + " finished eating . " ); } 65 66 67 68 69 70 71 72 73 74 } 76 public class Philosophers { private static final int numberForks = 5; 77 public static void main ( String [] args ) { Table Table = new Table ( numberForks ); for ( int i = 0; i < numberForks ; i ++) new Philosoph ( Table , i ); } 79 80 81 82 83 84 } Hier eine Lösung, die Semaphorgruppen verwendet, um die Operation atomar zu machen. Ein Applet verdeutlicht dies. 156 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Lese-Schreiber Problem Lese-Schreiber Problem Das Lese-Schreiber Problem modelliert den Zugriff auf eine Datenbank: Auf einen Datenbestand wollen mehrere Prozesse gleichzeitig zugreifen. • Es ist erlaubt, dass mehrere Prozesse gleichzeitig lesen, • aber nur einer darf zu einem Zeitpunkt schreiben. • Wenn geschrieben wird, darf kein Prozess (auch kein lesender) zugreifen. 157 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Lese-Schreiber Problem 1 2 3 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 semaphore mutex = 1; // controls access to rc semaphore db = 1; // controls access to DB int rc = 0; // # processes reading or wanting to reader () { while ( true ) { // repeat forever down ( mutex ); rc ++; if ( rc == 1) down ( db ); // for first reader up ( mutex ); read _data_base (); down ( mutex ); rc - -; if ( rc == 0) up ( db ); // release if last reader up ( mutex ); use_data_read (); } } writer () { while ( true ) { think_up_data (); down (& db ); w ri te _data_base ()(); // update the data up ( db ); / } } Die Lösung dazu verwendet eine Semaphore db, um den Zugriff auf die Datenbank zu synchronisieren. • • • • Der erste Leser (reader) ruft für den Zugriff auf den Datenbestand die Methode down() mit der Semaphore db“ auf, ” nachfolgende Leser erhöhen lediglich den Zähler rc“. ” Wenn Leser den kritischen Bereich verlassen, erniedrigen sie den Zähler und der letzte Leser ruft up() für die Semaphore auf und weckt einen potentiellen Schreiber. Lösung in Java? → Übung 158 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Das Problem des schlafenden Friseurs Das Problem des schlafenden Friseurs In einem Friseursalon mit einem Friseurstuhl und n Stühlen für wartende Kunden arbeitet ein Friseur. • Falls kein Kunde die Haare geschnitten haben möchte (also alle Stühle leer sind), setzt sich der Friseur auf den Friseurstuhl und schläft. • Ein eintreffender Kunde muss den Friseur wecken, um die Haare geschnitten zu bekommen. • Während der Friseur Haare schneidet, müssen weitere Kunden entweder Platz nehmen und warten, bis der Friseur frei ist oder später nochmal kommen (falls alle Stühle besetzt sind). Quelle: ’Moderne BS’, Tannenbaum Lösung mit Semaphoren oder Java Synchronisation als Übung. 159 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Das Problem des schlafenden Friseurs Lösungsidee Mittels Semaphore kann man das Problem lösen: customers Anzahl der Wartenden Kunden barbers Anzahl schlafender Frisöre mutex für den Schutz der Zählvariablen für wartende Kunden 1 2 3 4 5 # define chairs 5 typedef int semaphore ; semaphore customers = 0; semaphore barbers = 0; int waiting = 0; // // // // // # chairs for waiting customers use your imagination # customers waiting for service # barbers waiting for customers customers are waiting , not being cut 160 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 IPC Probleme Das Problem des schlafenden Friseurs Frisör, Kunde 1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 18 19 20 21 22 void barber ( void ) { while ( true ) { down (& customers ); down (& mutex ); waiting - -; up (& mutex ); up (& barbers ); cut_hair (); } } // // // // // go to sleep if # customers =0 acquire access to waiting dec . count of waiting customers release waiting one barber is now ready to cut hairs void customer ( void ) { down (& mutex ); // enter critic region if ( waiting < chairs ) { // if there are no free chairs , leave waiting ++; up (& customers ); // wake up barber if necessary up (& mutex ); // release access to waiting down (& barbers ); // go to sleep if # of free barbers is 0 get_haircut (); } else up (& mutex ); // shop is full ; do not wait } 161 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling 1 Prozessmodell 2 Interprozesskommunikation 3 IPC Probleme 4 Scheduling Round-Robin Scheduling Scheduling mit Prioritäten Shortest-Job-First Garantiertes Scheduling Zweistufiges Scheduling Fallbeispiel: Linux Scheduler 5 Literatur 162 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling Der Teil des Betriebssystems, der entscheidet, • welcher der Prozesse, • die im Zustand bereit“ sind, ” • ausgewählt wird und dann • den Prozessor zugeteilt bekommt, heißt Scheduler. Das Verfahren, wie ausgewählt wird, nennt man Scheduling-Algorithmus. 163 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Kriterien für Scheduling-Algorithmus Fairness Jeder Prozess wird gleichermaßen berücksichtigt; es gibt keine privilegierten Prozesse. Effizienz Der Prozessor wird stets vollständig ausgelastet. Antwortzeit Die Antwortzeit für die interaktiv arbeitenden Benutzer wird minimiert. Verweilzeit Die Zeit, die auf Ausgabe von Stapelaufträge gewartet werden muss, ist minimal. Durchsatz Die Anzahl der Jobs, die in einem gegebenen Zeitintervall ausgeführt werden, wird maximiert. Ressourcenbedarf Der für die Implementierung benötigte Ressourcenbedarf ist minimal. Nicht alle Kriterien sind gleichzeitig zu erfüllen, da sie teilweise widersprüchlich sind (z.B. Antwortzeit – Verweilzeit). 164 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Kategorisierung Ein Betriebssystem nutzt den Unterbrechungsmechanismus moderner CPU’s, um sicher zu stellen, dass kein Prozess zu lange ausgeführt wird: Wenn die Hardware einen Interrupt (etwa 100 mal pro Sekunde) ausgelöst hat, erhält das Betriebssystem die Kontrolle und der Scheduler wählt einen neuen Prozess aus. Je nachdem, wie der Scheduler die Auswahl der Prozesse vornimmt, werden Scheduling-Algorithmen unterschieden in: • preemptive Scheduling: rechnende Prozesse können unterbrochen werden. • run to completion (non-preemptive) Scheduling: der rechnende Prozess wird nicht unterbrochen. Welche Unterschiede ergeben sich? 165 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Kategorisierung Ein Betriebssystem nutzt den Unterbrechungsmechanismus moderner CPU’s, um sicher zu stellen, dass kein Prozess zu lange ausgeführt wird: Wenn die Hardware einen Interrupt (etwa 100 mal pro Sekunde) ausgelöst hat, erhält das Betriebssystem die Kontrolle und der Scheduler wählt einen neuen Prozess aus. Je nachdem, wie der Scheduler die Auswahl der Prozesse vornimmt, werden Scheduling-Algorithmen unterschieden in: • preemptive Scheduling: rechnende Prozesse können unterbrochen werden. • run to completion (non-preemptive) Scheduling: der rechnende Prozess wird nicht unterbrochen. Welche Unterschiede ergeben sich? 165 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Round-Robin Scheduling Round-Robin Scheduling Ein einfaches und häufig angewendetes Verfahren ist das Round-Robin Verfahren: • Jeder Prozess erhält eine Zeitscheibe (Quantum), die er maximal für die Ausführung zugeteilt bekommt. • Ist diese Zeit abgelaufen, wird ihm der Prozessor entzogen und • ein anderer Prozess wird ausgewählt. • Bei Blockierung wg. E/A wird dem Prozess der Prozessor entzogen, auch wenn sein Quantum noch nicht abgelaufen ist. Zur Implementierung verwaltet der Scheduler eine Queue mit rechenbereiten Prozessen, wobei ein aktiver Prozess nach Ablauf des Quantums wieder hinten in die Liste eingereiht wird: 166 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Round-Robin Scheduling Round-Robin Scheduling Ein einfaches und häufig angewendetes Verfahren ist das Round-Robin Verfahren: • Jeder Prozess erhält eine Zeitscheibe (Quantum), die er maximal für die Ausführung zugeteilt bekommt. • Ist diese Zeit abgelaufen, wird ihm der Prozessor entzogen und • ein anderer Prozess wird ausgewählt. • Bei Blockierung wg. E/A wird dem Prozess der Prozessor entzogen, auch wenn sein Quantum noch nicht abgelaufen ist. Zur Implementierung verwaltet der Scheduler eine Queue mit rechenbereiten Prozessen, wobei ein aktiver Prozess nach Ablauf des Quantums wieder hinten in die Liste eingereiht wird: 166 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Round-Robin Scheduling Dauer für das Quantum Eine Implementierung dieses Verfahrens muss eine “richtige” Wahl für die Dauer des Quantums finden: Angenommen, ein Kontextwechsel dauert 5 Millisekunden. • Quantum=20 Millisekunden → 5/20 ∗ 100 = 25% Verwaltungsaufwand sind zu viel. • Quantum=500 Millisekunden → 5/500 ∗ 100 = 1% Verwaltungsaufwand bedeutet, dass u.U. ein Benutzer zu lange warten (5 Sekunde, wenn 10 Benutzer gleichzeitig arbeiten) muss, bevor seine Anfrage (Tastendruck während Editorsitzung) bearbeitet wird. 167 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Round-Robin Scheduling Dauer für das Quantum Folgerung: • Quantum zu klein → wegen häufiger Kontextwechsel sinkt Prozessorausnutzung • Quantum zu gross → schlechte Antwortzeiten bei vielen kurzen Anfragen • Erfahrungswert: Quantum=50 Millisekunden ist guter Kompromiss Beurteilung Round Robin: E/A intensive Prozesse, die häufig vor Ablauf des Quantums eine blockierende Systemfunktion aufrufen und damit den Prozessor entzogen bekommen, werden durch Round-Robin benachteiligt. 168 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Round-Robin Scheduling Dauer für das Quantum Folgerung: • Quantum zu klein → wegen häufiger Kontextwechsel sinkt Prozessorausnutzung • Quantum zu gross → schlechte Antwortzeiten bei vielen kurzen Anfragen • Erfahrungswert: Quantum=50 Millisekunden ist guter Kompromiss Beurteilung Round Robin: E/A intensive Prozesse, die häufig vor Ablauf des Quantums eine blockierende Systemfunktion aufrufen und damit den Prozessor entzogen bekommen, werden durch Round-Robin benachteiligt. 168 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Round-Robin Scheduling Dauer für das Quantum In Unix/Linux kann das Quantum des aktuellen Prozesses wie folgt ermittelt werden (in C): getQuantum.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # include < stdio .h > # include < unistd .h > # include < sched .h > ← /* struct timespec { time_t tv_sec ; long tv_nsec ; // Nanosekunden =10** -9 Sekunden } ; */ int main () { struct timespec t ; if ( s c h e d _ r r _ g e t _ i n t e r v a l ( getpid () ,& t )==0) ← printf ( " sec : % d millisec : % ld \ n " , t . tv_sec , t . tv_nsec /1000000); } $ getQuantum sec : 0 millisec : 24 $ 169 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten Scheduling mit Prioritäten Die Grundidee des Prioritäts-Scheduling ist: • Jeder Prozess bekommt eine Priorität zugewiesen. • Es wird immer der rechenbereite Prozess mit der höchsten Priorität ausgeführt. Problem: Wird kein weiterer Mechanismus realisiert, so kommt es vor, dass Prozesse mit hoher Priorität verhindern, dass ein Prozess mit niedriger Priorität je den Prozessor bekommen (Verhungern). 170 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten Scheduling mit Prioritäten Die Grundidee des Prioritäts-Scheduling ist: • Jeder Prozess bekommt eine Priorität zugewiesen. • Es wird immer der rechenbereite Prozess mit der höchsten Priorität ausgeführt. Problem: Wird kein weiterer Mechanismus realisiert, so kommt es vor, dass Prozesse mit hoher Priorität verhindern, dass ein Prozess mit niedriger Priorität je den Prozessor bekommen (Verhungern). 170 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten Scheduling mit Prioritäten Deshalb wird folgender Mechanismus als Lösung implementiert: • Der Scheduler verringert die Priorität des rechnenden Prozesses bei jedem Interrupt, der durch die interne Prozessoruhr ausgelöst wird. • Damit fällt die Priorität des laufenden Prozesses irgendwann unter die Priorität eines anderen rechenbereiten Prozesses; • in diesem Fall wird ein solcher rechenbereiter Prozess mit höherer Priorität ausgewählt. 171 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten Prioritäten können statisch oder dynamisch vergeben werden. statisch Die Priorität wird vor dem Prozessstart festgelegt. Z.B. kann man folgende Regel anwenden: Batchprozesse < Systemprozesse < Interaktive Prozesse < ... dynamisch Dynamische Vergabe von Prioritäten wird häufig realisiert, um Systemziele zu erreichen. Z.B. Optimierung des I/O-Verhaltens: dann muss ein Prozess, der bei E/A Ereignis rechenbereit wird, hohe Priorität bekommen, damit das E/A Ereignis behandelt werden kann. 172 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten In modernen Betriebssystemen werden Prozesse in Klassen eingeteilt, wobei • zwischen den Klassen Prioritäts-Scheduling und • innerhalb der Klasse Round-Robin Scheduling verwendet wird. 173 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten Thread Prioritäten in Java Die JVM soll plattformunabhängig implementierbar sein. Daher gibt es in Java keine Vorgaben bzgl. des Scheduling. Methoden, mit denen die Priorität eines Thread beeinflusst werden kann: • Jeder Thread hat eine Priorität zwischen MIN PRIORITY und MAX PRIORITY ([1,10]). • Wird ein neuer Thread erzeugt, erbt er die Priorität vom Vater. • Der Defaultwert, wenn ein Thread aus main() erzeugt wurde, ist Thread.NORM PRIORITY (5). • Die Methode getPriority() liefert die aktuelle Priorität eines Threads. • Mit setPriority()kann die Priorität verändert werden. 174 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten Thread Prioritäten in Java Achtung: • Prioritäten beeinflussen weder Semantik noch Korrektheit eines Java Programms. • Prioritäten dürfen beim Programmieren nicht als Ersatz für Locking verwendet werden! • Prioritäten dürfen nur verwendet werden, um die relative Wichtigkeit unterschiedlicher Threads untereinander zu definieren. 175 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten 3 Threads mit unterschiedlicher Priorität in Java I ThreadPriority.java 1 2 4 5 6 7 8 import java . util .*; import java . io .*; class ThreadPriority { public static void main ( String [] args ) { ThreadPriority t = new Thr eadPrio rity (); t . doIt (); } public void doIt () { MyThread t1 = new MyThread ( " Thread One : " ); t1 . setPriority ( t1 . getPriority () -4); // Default is 5 MyThread t2 = new MyThread ( " Thread Two : " ); MyThread t3 = new MyThread ( " Thread Three : " ); t3 . setPriority (10); t1 . start (); t3 . start (); t2 . start (); } 10 11 12 13 14 15 16 17 18 19 20 } 21 176 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten 3 Threads mit unterschiedlicher Priorität in Java II ThreadPriority.java 22 23 24 class MyThread extends Thread { static String spacerString = " " ; public String filler ; public MyThread ( String ThreadNameIn ) { filler = spacerString ; spacerString = spacerString + " setName ( ThreadNameIn ); } 26 27 28 29 30 public void run () { for ( int k =0; k < 5; k ++) { System . out . println ( filler + Thread . currentThread (). getName () + " Iteration = " + Integer . toString ( k ) ) ; } } 32 33 34 35 36 37 38 39 "; } 177 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten 3 Threads mit unterschiedlicher Priorität in Java ThreadPriority.java $ java ThreadPriority Thread Thread Thread Thread Thread One : One : One : One : One : Thread Thread Thread Thread Thread Iteration = 0 Iteration = 1 Iteration = 2 Iteration = 3 Iteration = 4 Two : Two : Two : Two : Two : Thread Three : Thread Three : Thread Three : Thread Three : Thread Three : Iteration = 0 Iteration = 1 Iteration = 2 Iteration = 3 Iteration = 4 Iteration Iteration Iteration Iteration Iteration = = = = = 0 1 2 3 4 Versuche Sie das Programm auf Ihrem Rechner. Ist die Ausgabe genau so ? 178 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Scheduling mit Prioritäten Prozessprioritäten in Unix/Linux In Linux ist die Priorität eines Prozesses ein Integer im Bereich -20 bis 20 (-20 ist höchste Priorität, Default ist 0). Das folgende Programm zeigt, wie die Priorität eines Prozesses gesetzt und abgefragt werden kann: getPriority.c 1 2 3 4 6 7 8 9 10 11 12 13 14 15 # include # include # include # include < stdio .h > < sched .h > < sys / resource .h > // wg . PRIO_USER < errno .h > extern int errno ; int main () { int prio = 0; if ( setpriority ( PRIO_USER , 0 , 10) != 0) { ← fprintf ( stderr , " Error setpriority : % s \ n " , strerror ( errno )); exit (1); } printf ( " % d \ n " , getpriority ( PRIO_USER , 0)); ← exit (0); } 179 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Shortest-Job-First Shortest-Job-First Die beiden letzten Verfahren sind für interaktive Systeme entworfen worden. Ein Verfahren für Systeme, bei denen die Ausführungszeiten der einzelnen Prozesse im Voraus bekannt sind, benutzt eine Warteschlange für die gleichgewichtigen Prozesse. Der Scheduler wählt den Prozess mit der kürzesten Ausführungszeit zuerst. Gemäss dieser Methode wird die durchschnittliche Verweilzeit (Zeit, die der Prozess im System verweilt ) von Prozessen minimiert: 180 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Shortest-Job-First Shortest-Job-First Die beiden letzten Verfahren sind für interaktive Systeme entworfen worden. Ein Verfahren für Systeme, bei denen die Ausführungszeiten der einzelnen Prozesse im Voraus bekannt sind, benutzt eine Warteschlange für die gleichgewichtigen Prozesse. Der Scheduler wählt den Prozess mit der kürzesten Ausführungszeit zuerst. Gemäss dieser Methode wird die durchschnittliche Verweilzeit (Zeit, die der Prozess im System verweilt ) von Prozessen minimiert: 180 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Shortest-Job-First Gegeben seien 4 Prozesse mit folgenden Ausführungszeiten: A 8 Sekunden B 6 Sekunden C 4 Sekunden D 6 Sekunden Ausführungsreihenfolge A, B, C, D: A B C D Verweilzeit 8 8 + 6 = 14 8 + 6 + 4 = 18 8 + 6 + 4 + 6 = 24 → durchschnittliche Verweilzeit = (8 + 14 + 18 + 24)/4 = 16 181 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Shortest-Job-First Gegeben seien 4 Prozesse mit folgenden Ausführungszeiten: A 8 Sekunden B 6 Sekunden C 4 Sekunden D 6 Sekunden Ausführungsreihenfolge C, B, C, A: C B D A Verweilzeit 4 4 + 6 = 10 4 + 6 + 6 = 16 4 + 6 + 6 + 8 = 24 → durchschnittliche Verweilzeit = (4 + 10 + 16 + 24)/4 = 13, 5 182 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Shortest-Job-First Behauptung: Den Prozess mit der kürzesten Ausführungszeit zu wählen ist optimal bzgl. der mittleren Verweilzeit. Beweis: Gegeben seien 4 Prozesse A-D mit den jeweiligen Verweilzeiten a, b, c, d, die in der Reihenfolge A,B,C,D ausgeführt werden. Verweilzeit A a B a+b C a+b+c D a+b+c +d → durchschnittliche Verweilzeit = (4a + 3b + 2c + d)/4 d.h. a beeinflusst die durchschnittliche Verweilzeit am meisten, gefolgt von b und c; daraus folgt: die durchschnittliche Verweilzeit ist minimal, wenn zuerst A der Prozess mit der kleinsten Ausführungszeit ist, B der Prozess mit der zweit kleinsten Ausführungszeit, etc 183 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Shortest-Job-First Ausführungszeit bestimmen Das Problem bei interaktiven Betriebssystemen ist es, die Ausführungszeit der Prozesse zu bestimmen. Idee: Schätze die Ausführungszeit auf Basis der gemessenen Zeit der Vergangenheit M. • Der Scheduler wählt dann den Prozess mit der kürzesten geschätzten Zeit. • Der neue geschätzte Wert S zum Zeitpunkt n+1 wird aus dem gewichteten Durchschnitt des aktuellen und des vorherigen Wertes gebildet. Sn+1 = α ∗ Mn + (1 − α) ∗ Sn α(0 ≤ α ≤ 1) ist dabei der Faktor, der den Einfluss der zurückliegenden Periode auf die Schätzung angibt; Werte nahe 1 ordnen der geschätzten Vergangenheit wenig Stellenwert zu. 184 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Shortest-Job-First Hörsaalübung Gegeben seien 5 Prozesse mit folgenden Ausführungszeiten: A 6 Sekunden B 6 Sekunden C 8 Sekunden D 2 Sekunden E 4 Sekunden Welche Ausführungsreihenfolge führt zur optimalen durchschnittlichen Verweilzeit? Prozess A B C D E Verweilzeit → durchschnittliche Verweilzeit = 185 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Garantiertes Scheduling Garantiertes Scheduling Beim garantierten Scheduling wird einem Prozess eine gewisse Performance versprochen und die Einhaltung garantiert. Mögliche Versprechen: • Bei interaktiven Systemen mit n Benutzern, erhält jeder 1/n der verfügbaren Prozessorzeit • Bei Echtzeitsystemen wird vom System garantiert, dass ein Prozess innerhalb einer Zeitschranke abgeschlossen ist. 186 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Garantiertes Scheduling interaktive Systeme Zu jeder Benutzersitzung sei die insgesamt erhaltene Prozessorzeit seit Sitzungsbeginn e. Der Scheduler berechnet die dem Benutzer zustehende Zeit z und ermittelt das Verhältnis von erhaltener Zeit e und zustehender Zeit z. Das Verhältnis 0,5 bedeutet, dass der Prozess halb so viel Zeit verbraucht hat, wie ihm garantiert wurde. Der Scheduler wählt immer den Prozess, mit dem berechneten kleinsten Verhältnis e/z. Beispiel Prozess A B C erhaltene Zeit e 14 11 20 versprochene Zeit z (14+11+20)/3=15 (14+11+20)/3=15 (14+11+20)/3=15 e/z 14/15=0,93 11/15=0,73 20/15=1,33 ausgewählter Prozess X 187 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Garantiertes Scheduling interaktive Systeme Zu jeder Benutzersitzung sei die insgesamt erhaltene Prozessorzeit seit Sitzungsbeginn e. Der Scheduler berechnet die dem Benutzer zustehende Zeit z und ermittelt das Verhältnis von erhaltener Zeit e und zustehender Zeit z. Das Verhältnis 0,5 bedeutet, dass der Prozess halb so viel Zeit verbraucht hat, wie ihm garantiert wurde. Der Scheduler wählt immer den Prozess, mit dem berechneten kleinsten Verhältnis e/z. Beispiel Prozess A B C erhaltene Zeit e 14 11 20 versprochene Zeit z (14+11+20)/3=15 (14+11+20)/3=15 (14+11+20)/3=15 e/z 14/15=0,93 11/15=0,73 20/15=1,33 ausgewählter Prozess X 187 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Garantiertes Scheduling Echtzeit-Systeme Bei Echtzeitsystemen wird vom System garantiert, dass ein Prozess innerhalb einer Zeitschranke abgeschlossen ist. Der Scheduler wählt den Prozess, bei dem die Gefahr am grössten ist, dass die Zeitschranke nicht eingehalten werden kann. Beispiel Prozess A B C zugesicherte Zeitschranke 14 11 20 bisher erhaltene Zeit 10 9 15 Dauer bis Ablauf der Zeitschranke 14-10=4 11-9=2 20-15=5 ausgewählter Prozess X 188 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Garantiertes Scheduling Echtzeit-Systeme Bei Echtzeitsystemen wird vom System garantiert, dass ein Prozess innerhalb einer Zeitschranke abgeschlossen ist. Der Scheduler wählt den Prozess, bei dem die Gefahr am grössten ist, dass die Zeitschranke nicht eingehalten werden kann. Beispiel Prozess A B C zugesicherte Zeitschranke 14 11 20 bisher erhaltene Zeit 10 9 15 Dauer bis Ablauf der Zeitschranke 14-10=4 11-9=2 20-15=5 ausgewählter Prozess X 188 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Garantiertes Scheduling Hörsaalübung 1. Ein interaktives System sei geben durch: Prozess A B C D erhaltene Zeit e 8 2 2 2 versprochene Zeit z e/z ausgewählter Prozess 2. Ein Echtzeitsystem sei gegeben durch: Prozess A B C D zugesicherte Zeitschranke 4 2 2 6 bisher erhaltend Zeit 1 1 1 2 Dauer bis Ablauf der Zeitschranke ausgewählter Prozess In welcher Reihenfolge werden die Prozesse in beiden Systemen ausgewählt (jeweils 5 Kontextwechsel, Quantum 1 Sekunde)? 189 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Zweistufiges Scheduling Zweistufiges Scheduling Die bisherigen Scheduling-Verfahren haben gemeinsam, dass alle rechenbereiten Prozesse im Hauptspeicher ablaufen. • Wenn nicht genug Hauptspeicher verfügbar ist, müssen einige dieser Prozesse auf der Festplatte abgelegt werden. • Der Aufwand für einen Prozesswechsel ist aber höher, wenn ein Prozess zunächst in den Hauptspeicher zurückgeladen werden muss. 190 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Zweistufiges Scheduling Ein zweistufiges Verfahren teilt die rechenbereiten Prozesse in 2 disjunkte Teilmengen: • Prozesse im Hauptspeicher H • Prozesse auf Platte (Sekundärspeicher S) Es existieren zwei Scheduler: • lokaler Scheduler wählt stets einen Prozess aus H • periodisch lagert ein globaler Scheduler Prozesse von H und S aus und ersetzt sie. 191 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Zweistufiges Scheduling Als Verfahren für den lokalen Scheduler kommt einer der vorherigen Algorithmen in Frage. Der globale Scheduler kann folgende Kriterien zur Auswahl des Plattenspeicher Prozesses, der ausgetauscht werden soll: • Wie lange ist der Prozess schon in S? • Wie groß ist der Prozess in S (es passen mehrere kleine in den Hauptspeicher)? • Priorität des Prozesses in S? 192 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Fallbeispiel: Linux Scheduler Die Beschreibung basiert auf dem Beitrag Der O(1)-Scheduler im Kernel ” 2.6“ von Timo Hönig im Linux-Magazin. 193 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Prozessverwaltung • Linux verwaltet alle Prozessinformationen mit Hilfe einer doppelt verketteten Liste der Taskliste. • Die Listenelemente sind die Prozessdeskriptoren (task struct) der Prozesse. • Der Deskriptor hält alle Informationen seines Prozesses fest (im Wesentlichen, das was man mit ps sieht). 194 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Den Zustand eines Prozesses speichert die Variable Prozessdeskriptors. Der Scheduler kennt insgesamt fünf Zustände: state des 1 TASK RUNNING kennzeichnet den Prozess als lauffähig. Er muss auf kein Ereignis warten und kann daher vom Scheduler der CPU zugeordnet werden. Alle Prozesse im Zustand TASK RUNNING zieht der Scheduler für die Ausführung in Betracht. 2 TASK INTERRUPTIBLE kennzeichnet blockierte Prozesse. Der Prozess wartet auf ein Ereignis. Ein Prozess im Zustand TASK INTERRUPTIBLE wird über zwei unterschiedliche Wege in den Zustand TASK RUNNING versetzt: 1 2 Entweder tritt das Ereignis ein, auf das er gewartet hat, oder der Prozess wird durch ein Signal aufgeweckt. 195 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler 3 TASK UNINTERRUPTIBLE gleicht dem Zustand TASK INTERRUPTIBLE, mit dem Unterschied, dass ein Signal den Prozess nicht aufwecken kann. Der Zustand TASK UNINTERRUPTIBLE wird nur verwendet, wenn zu erwarten ist, dass das Ereignis, auf das der Prozess wartet, zügig eintritt, oder wenn der Prozess ohne Unterbrechung warten soll. 4 Wurde ein Prozess beendet, dessen Elternprozess noch nicht den Systemaufruf wait4() ausgeführt hat, verbleibt er im Zustand TASK ZOMBIE. So kann auch nach dem Beenden eines Kindprozesses der Elternprozess noch auf seine Daten zugreifen. Nachdem der Elternprozess wait4() aufgerufen hat, wird der Kindprozess endgültig beendet, seine Datenstrukturen werden gelöscht. Endet ein Elternprozess vor seinen Kindprozessen, bekommt jedes Kind einen neuen Elternprozess zugeordnet. Dieser ist nunmehr dafür verant- wortlich, wait4() aufzurufen, sobald der Kindprozess beendet wird. Ansonsten könnten die Kindprozesse den Zustand TASK ZOMBIE nicht verlassen und würden als Leichen im Hauptspeicher zurückbleiben. 196 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler 5 Den Zustand TASK STOPPED erreicht ein Prozess, wenn er beendet wurde und nicht weiter ausführbar ist. In diesen Zustand tritt der Prozess ein, sobald er eines der Signale SIGSTOP, SIGTST, SIGTTIN oder SIGTTOU erhält. 197 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Entwicklungsziele für den Scheduler Neben den allgemeinen Zielen (Auslastung der CPU, ...) waren hier zusätzlich folgende Punkte maßgebend: • gute SMP-Skalierung • geringe Latenz auch bei hoher Systemlast • faire Prioritätenverteilung • Komplexität der Ordnung O(1) Alle Linux-Scheduler bisher besaßen eine Komplexität der Ordnung O(n): Die Kosten für das Scheduling wuchsen linear mit der Anzahl n der lauffähigen Prozesse. Bei einem Kontextwechsel wird in einer verketteten Liste nach einem Prozess gesucht, dessen Priorität am niedrigsten ist. 198 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Prozessprioritäten Die Prozessprioritäten entscheiden, welchen lauffähigen Prozess die CPU beim nächsten Kontextwechsel zugeteilt bekommt - den mit der zum Zeitpunkt des Kontextwechsels höchsten Priorität. Die Priorität eines Prozesses ändert sich dabei dynamisch während seiner Laufzeit. Es gibt zwei unterschiedliche Prioritäten: • die statische Prozesspriorität, also die vom nice-Wert bestimmte static prio Der Wertebereich des nice-Values reicht von -20 (dem höchsten) bis 19 (dem niedrigsten). • die dynamische (effektive) Prozesspriorität (prio), die der Scheduler aufgrund der Interaktivität eines Prozesses berechnet. 199 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Linux 2.6 kennt standardmäßig 140 Prioritätslevels. • Hierbei entspricht null der höchsten und 139 der niedrigsten Priorität. • Die Levels von eins bis 99 sind für Tasks mit Echtzeitpriorität reserviert. • Alle anderen Prozesse erhalten zunächst gemäß ihres nice-Werts eine Priorität: Der nice-Wert (-20 bis 19) wird einfach in den Bereich ab 101 gemappt. • Während des Ablaufs eines Prozesses verändert sich durch seinen Interaktivitätsgrad aber seine Priorität. 200 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Alle lauffähigen Prozesse verwaltet der Scheduler in einer Run-Queue (pro CPU). Sie ist die zentrale Datenstruktur, auf der der Scheduler arbeitet. struct runqueue { /* Spinlock um Run - Queue zu schützen */ spinlock_t lock ; /* Zahl der lauffähigen Prozesse */ unsigned long nr_running ; /* Zahl der bisherigen Kontextw echsel */ unsigned long nr_switches ; /* Zeitstempel des letzten Tauschs von active - und expired - Array */ unsigned long exp ire d _ t i m e s t a m p ; /* Zahl der Prozess im Zustand T A S K _ U N I N T E R R U P T I B L E */ unsigned long n r_ un i n t e r r u p t i b l e ; /* Verweis auf Pro ze s s d e s k r i p t o r des momentan ablaufenden Prozesses */ task_t * curr ; /* Verweis auf Pro ze s s d e s k r i p t o r der Idle - Task */ task_t * idle ; /* Verweis auf Memory Map des zuletzt ablaufenden Prozesses */ struct mm_struct * prev_mm ; /* Verweise auf active - und expired - Array */ prio_array_t * active , * expired ; /* Priority - Arrays */ prio_array_t arrays [2]; ... } 201 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Neben Verweisen auf die gerade laufende Task, enthält die Run-Queue Verweise zu den zwei Priority-Arrays active und expired. struct prio_array { int nr_active ; /* Zahl der Prozesse */ unsigned long bitmap [ BITMAP_SIZE ]; /* Priorität - Bitmap */ /* Für jede Priorität eine Liste der Prozesse */ struct list_head queue [ MAX_PRIO ]; }; • Das active-Array listet alle lauffähigen Prozesse, deren Zeitscheibe noch nicht abgelaufen ist. • Wenn die Zeitscheibe eines Prozesse abläuft, verschiebt der Scheduler den Eintrag vom active- in das zweite Array expired. • Beide, das active- und das expired-Array, führen für jede Priorität eine verkettete Liste der Prozesse mit entsprechender Priorität. • Eine Bitmap hält fest, für welche Priorität mindestens eine Task existiert. Alle Bits werden bei der Initialisierung auf null gesetzt. Beim Eintragen eines Prozesses in eines der beiden Priority-Arrays, wechselt entsprechend der Priorität des Prozesses das korrespondierende Bit im Priorität-Bitmap auf eins. 202 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler • Startet ein Prozess mit dem Nice null, setzt der Scheduler das 120. Bit des Priorität-Bitmaps im active-Array und reiht ihn in die Prozessliste mit Priorität 120 ein. • Analog dazu löscht sich das entsprechende Bit im Priorität-Bitmap, sobald der Scheduler den letzten Prozess einer gegebenen Priorität aus einem der beiden Priority-Arrays austrägt. • Der Scheduler muss nur das erste gesetzte Bit des Priorität-Bitmaps finden (da Bitmap konstante Größe hat folgt O(1)). Anschließend führt der Scheduler den ersten Prozess aus der verketteten Liste dieser Priorität aus. Prozesse gleicher Priorität bekommen die CPU nacheinander in einem Ringverfahren (Round Robin) zugeteilt. 203 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Prozess-Zeitscheiben und Neuberechnung der Prioritäten Die Größe der Zeitscheibe eines Prozesses ist von seiner Priorität abhängig: • Prozesse mit hoher Priorität erhalten mehr CPU-Zeit als solche mit niedriger. • Die kleinste Zeitscheibe beträgt 10, die längste 200 Millisekunden. Ein Prozess mit dem nice-Wert null erhält die StandardZeitscheibe von 100 Millisekunden. Ist die Zeitscheibe eines Prozesses aufgebraucht, muss der Scheduler sie neu berechnen und den Prozess aus dem active- in das expired-Array verschieben. Sobald active leer ist - alle lauffähigen Prozesse haben ihre Zeitscheibe aufgebraucht -, tauscht der Scheduler einfach das active- gegen das expired-Array aus. Effektiv wechseln nur die zwei Pointer der Run-Queue die Plätze. In Linux 2.4 werden die Zeitscheiben aller Prozesse auf einmal neu berechnet - immer dann, wenn alle Prozesse ihre Zeitscheiben aufgebraucht haben. Mit steigender Prozesszahl dauert die Berechnung immer länger. 204 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Die dynamische Priorität errechnet der Scheduler aus der statischen und der Prozessinter- aktivität. Gemäß seiner Interaktivität erhält ein Prozess vom Scheduler entweder einen Bonus oder ein Penalty (Malus). Interaktive Prozesse gewinnen über einen Bonus maximal fünf Prioritätslevels hinzu, während jene Prozesse, die eine geringe Interaktivität aufweisen, maximal fünf Prioritätslevels durch ein Penalty verlieren. Die dynamische Priorität eines Prozesses mit einem nice-Wert fünf beträgt demnach im besten Fall null und im schlechtesten zehn. 205 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Um den Grad der Interaktivität eines Prozesses zu bestimmen, muss bekannt sein, ob der Prozess eher I/O-lastig oder eher CPU-intensiv ist. Um Prozesse einer der beiden Kategorien zuordnen zu können, protokolliert der Kernel für jeden Prozess, wie viel Zeit er mit Schlafen verbringt, und wie lange er die CPU in Anspruch nimmt. Die Variable sleep avg (Sleep Average) im Prozessdeskriptor speichert dafür eine Entsprechung in dem Wertebereich von null und zehn (MAX SLEEP AVG). Läuft ein Prozess, verringert seine sleep avg mit jedem Timer-Tick ihren Wert. Sobald ein schlafender Prozess aufgeweckt wird und in den Zustand TASK RUNNING wechselt, wird sleep avg entsprechend seiner Schlafzeit erhöht - maximal bis zu MAX SLEEP AVG. Der Wert von sleep avg ist somit maßgebend, ob ein Prozess I/O- oder Processor-Bound ist. Interaktive Prozesse haben eine hohe sleep avg, minder interaktive eine niedrige. 206 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Mit der Funktion effective prio() berechnet der Scheduler die dynamische Priorität prio basierend auf der statischen static prio und der Interaktivität sleep avg des Prozesses. Zum Berechnen der neuen Zeitscheibe greift der Scheduler auf die dynamische Prozesspriorität zurück. Dazu mappt er den Wert in den Zeitscheibenbereich MIN TIMESLICE (Default: 10 Millisekunden) bis MAX TIMESLICE (200 Millisekunden). Interaktive Prozesse mit hohem Bonus und großer Zeitscheibe können ihre Priorität jedoch nicht missbrauchen, um die CPU zu blockieren: Da die Zeit, die der Prozess beim Ausführen im Zustand TASK RUNNING verbringt, in die Berechnung der sleep avg-Variablen eingeht, verliert solch ein Prozess schnell seinen Bonus und mit ihm seine hohe Priorität und seine große Zeitscheibe. Ein Prozess mit sehr hoher Interaktivität erhält nicht nur eine hohe dynamische Priorität und somit eine große Zeitscheibe: Der Scheduler trägt den Prozess nach Ablauf seiner Zeitscheibe auch wieder sofort in das active-Array ein, statt wie gewöhnlich ins expired-Array. Der Prozess wird dadurch seiner Priorität gemäß wieder zugeordnet und muss nicht auf das Austauschen der Priority-Arrays warten. 207 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Kontextwechsel Alle blockierten Prozesse werden in den so genannten Wait-Queues verwaltet. Prozesse, die von TASK RUNNING in den Zustand TASK INTERRUPTIBLE oder TASK UNINTERRUPTIBLE wechseln, gelangen in diese Warteschlange. Anschließend ruft der Kernel schedule() auf, damit ein anderer Prozess die CPU erhält. Sobald das Ereignis eintritt, auf das der Prozess in einer Wait-Queue wartet, wird er aufgeweckt, wechselt seinen Zustand in TASK RUNNING zurück, verlässt die Wait-Queue und betritt die Run-Queue. Falls der aufgewachte Prozess eine höhere Priorität besitzt als der gerade ablaufende, unterbricht der Scheduler den aktuell laufenden Prozess zugunsten des eben aufgewachten. 208 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Kernel-Preemption Anders als Kernel 2.4 ist der neue Linux-Kernel preemptiv: Kernelcode, der gerade ausgeführt wird, kann unterbrochen werden. Vor dem Unterbrechen muss gewährleistet sein, dass sich der Kernel in einem Zustand befindet, der eine Neuzuordnung der Prozesse zulässt. Die Struktur thread info jedes Prozesses enthält zu diesem Zweck den Zähler preempt count. Ist er null, ist der Kernel in einem sicheren Zustand und darf unterbrochen werden. Die Funktion preempt disable() erhöht den Zähler preempt count beim Setzten eines so genannten Locks um eins; die Funktion preempt enable() erniedrigt ihn um eins, sobald ein Lock aufgelöst wird. Das Setzten des Locks (und damit das Verbot der Kernel-Preemption) wird immer dann notwendig, wenn beispielsweise eine von zwei Prozessen genutzte Variable vor konkurrierenden Zugriffen zu sichern ist. 209 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Realtimefähigkeit Für Prozesse mit so Echtzeitpriorität (Priorität 1 bis 99) gibt es zwei Strategien: • • SCHED FIFO ist ein einfacher First-in/First-out-Algorithmus, der ohne Zeitscheiben arbeitet. Wird ein Echtzeitprozess mit SCHED FIFO gestartet, läuft er so lange, bis er blockiert oder freiwillig über die Funktion sched yield() den Prozessor abgibt. Alle anderen Prozesse mit einer niedrigeren Priorität sind solange blockiert und werden nicht ausgeführt. SCHED RR verfolgt die gleiche Strategie wie mit vorgegebenen Zeitscheiben. SCHED FIFO, aber zusätzlich Die CPU-Bedürfnisse der Echtzeitprozesse gleicher Priorität befriedigt der Scheduler per Round-Robin. Prozesse mit einer niedrigeren Priorität kommen überhaupt nicht zum Zuge. Der Scheduler vergibt für Echtzeitprozesse keine dynamischen Prioritäten. Prozesse ohne Echtzeitpriorität führt er mit der Strategie SCHED OTHER aus. 210 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Realtimefähigkeit Die Echtzeit-Strategien von Linux garantieren jedoch keine Antwortzeiten, was die Voraussetzung für ein hartes Echtzeit-Betriebssystem wäre. Der Kernel stellt jedoch sicher, dass ein lauffähiger Echtzeit-Prozess immer die CPU bekommt, wenn er auf kein Ereignis warten muss, er freiwillig die CPU abgibt und wenn kein lauffähiger Echtzeitprozess höherer Priorität existiert. 211 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Leistungsvergleich O(n) vs O(1) Scheduler Benötigte Zeit zur Interprozess-Kommunikation in Abhängigkeit von der Anzahl beteiligter Prozesse auf einem Singleprozessor-System mit Kernel 2.4 (rot) und 2.6 (grün): 212 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Scheduling Fallbeispiel: Linux Scheduler Benötigte Zeit zur Interprozess-Kommunikation in Abhängigkeit von der Anzahl beteiligter Prozesse auf Systemen mit ein, zwei, vier und acht CPUs. Es ist deutlich zu sehen, dass Linux 2.6 bei steigender Prozesszahl auf allen vier Systemen ungleich besser skaliert als der alte Kernel. 213 / 214 Betriebssysteme - Prozesse → [email protected] Version: (8c45d65) ARSnova 19226584 Literatur Literatur Folgende Literatur wir ergänzend empfohlen: • Alois Schütte, ’Programmieren in Occam’, Addison-Wesley 214 / 214