Nebenläufige und Verteilte Programme. Thomas Letschert

Größe: px
Ab Seite anzeigen:

Download "Nebenläufige und Verteilte Programme. Thomas Letschert"

Transkript

1 Nebenläufige und Verteilte Programme Thomas Letschert

2 i Vorwort Dieser Text richtet sich an Leserinnen und Leser, die nach einem Tutorium zur Konstruktion nebenläufiger und verteilter Anwendungen suchen. Studierende der Informatik zu Beginn des Hauptstudiums sind ebenso angesprochen wie praktizierende Software Ingenieure. Sie haben sich eventuell bereits ein wenig mit der Theorie der Nebenläufigkeit, der Rechnernetze und der objektorientierten Softwaretechniken beschäftigt. Um die Mechanismen besser zu verstehen, möchten Sie konkrete und übersichtliche Anwendungen sehen. Anwendungen in denen Verteiltheit, Nebenläufigkeit, Objektorientierung zusammenkommen, die aber noch weit von der Komplexität kommerzieller Systeme entfernt sind. Es gibt jeweils viele gute Bücher zur Theorie der Rechnernetze, der Softwaretechnik und der Nebenläufigkeit. Die dort behandelte Theorie ist Voraussetzung für eine ernsthafte Beschäftigung mit verteilten Systemen. Nach unserer Erfahrung sind die theoretischen Grundlagen, allein und vor allem isoliert betrachtet, aber nicht ausreichend, um zu einem dauerhaften und tiefgehenden Verständnis zu kommen. Man muss das Zusammenwirken der Konzepte aus den verschiedenen Bereichen sehen und das am besten im Rahmen konkreter und übersichtlicher Beispiele. Dies wird hier angestrebt. Nebenläufigkeit, Rechnernetze und Softwaretechnik werden in kleinen Programmen zusammengebracht. Damit soll der Weg von Prinzipien zu Anwendungen gezeigt werden. Es ist klar, dass die schwergewichtige und vielfältige einschlägige Theorie nur leichtherzig behandelt werden kann. Ebenso leicht tun wir uns mit der Plattform auf der unsere Beispiele basieren: den POSIX-Threads und der Socket-Schnittstelle. Die Mechanismen werden soweit erklärt, wie sie in den Beispielen benutzt werden. Es wird aber keine systematische und vollständige Behandlung irgendeiner bestimmten Schnittstelle angestrebt. Verteilte Programme sind immer auch nebenläufige Programme. Im ersten Abschnitt wird darum Nebenläufigkeit in Form schwerer und leichter Prozesse behandelt. Im zweiten Abschnitt geht es dann um verteilte Anwendungen. In beiden Abschnitten legen wir Wert auf vollständige Beispiele, in denen grundlegende Problemstellungen und ihre Lösung dargelegt werden. Wir gehen dabei von elementaren Konstrukten der benutzten Schnittstelle über zu höherwertigen eigenen Entwicklungen typischerweise in Form von Klassen. Die Leser sollen damit auch angeregt werden, ihre eigenen Abstraktionen zu entwickeln. Als Programmiersprache wird C++ benutzt. Diese Sprache bietet ein breites Spektrum vom direkten Zugriff auf die Systemschnittstelle bis zu beliebig mächtigen eigenen Konstrukten. Denkschemata in dieser oder jener Ausprägung ob man sie nun Paradigma, Idiom, oder Muster nennt spielen eine wichtige Rolle in jedem kreativen Prozess. Bei den komplexeren Beispielen wird darum Wert auf das Typische und Musterhafte der Lösung gelegt. Für ihre vielen kritischen Anmerkungen und Verbesserungsvorschläge danke den Studenten im Studiengang Informatik der Fachhochschule Gießen Friedberg und insbesondere meiner Frau Heidrun. Alle verblieben Fehler und Unklarheiten liegen natürlich in meiner Verantwortung. Thomas Letschert, Februar 1998

3 Inhaltsverzeichnis 1 Einführung 1 2 Nebenläufige Programme Prozesse Prozesse als unabhängige Kontrollflüsse Synchronisation und atomare Aktionen Unix Prozesse: Schwergewichtige Prozesse Threads: Leichtgewichtige Prozesse Threads Objektorientiert Übungen Synchronisation Das Synchronisationsproblem Gegenseitiger Ausschluss und Bedingungssynchronisation von Threads Übungen Kommunikation von Prozessen Kommunikationskonzepte Pipes: eine Puffer-Implementierung in Unix Semaphore Das Semaphorkonzept IPC Semaphore Übungen Monitore Das Monitor-Konzept Monitor-Abstraktionen Puffer zwischen Prozessen ii

4 INHALTSVERZEICHNIS iii Monitor-Programme Übungen Verteilte Programme BSD Sockets Die Socketschnittstelle Verbindungsorientierte Kommunikation Verbindungslose Kommunikation Übungen Abstraktionen der Socketkommunikation Eine funktionale Abstraktion Eine objektorientierte Abstraktion OO Abstraktion der verbindungslosen Kommunikation Sockets und Threads Hilfsmittel verteilter Programme Timer Rückrufe und Ereignisorientierte Programme Ferne Prozeduren und Verteilte Objekte Schichtenarchitektur Architektur-Muster Beispiel: Echo Protokoll Protokollspezifikation Protokolle und ihre Spezifikation Methodik der Spezifikation Schichten als bessere Objekt-Verfeinerung Übungen Protokollentwurf Analyse und Entwurf Fensterprotokoll als zeitüberwachter Monitor Implementierungsmodell Übungen Literaturverzeichnis Index

5 iv INHALTSVERZEICHNIS Literaturverzeichnis 284 Index 286

6 Kapitel 1 Einführung Thematik: Nebenläufige und verteilte Systeme Von Nebenläufigkeit (Concurrency) spricht man, wenn mehrere Aktivitäten gleichzeitig und dabei nicht unabhängig voneinander ablaufen. Mehrere Köche, die gemeinsam einen Brei zubereiten sind ein Beispiel für Nebenläufigkeit. In einem verteilten System sind ebenfalls stets mehrere Prozesse aktiv, um gemeinsam eine Problemstellung zu bearbeiten. Sie sind allerdings weiter auseinander, so weit, dass sie sich nur über Nachrichten verständigen können. Allerdings können sie sich auch nicht gegenseitig den Löffel wegnehmen. Nebenläufigkeit: Konkurrenz und Kooperation Nebenläufiges Arbeiten bedarf der Synchronisation. Zum einen darf es nicht passieren, dass zwei Köche gleichzeitig nach einem Löffel greifen und ihn dann in einer Prügelei zerbrechen. Zum anderen sollte der eine Koch den Griess erst einrühren, wenn der andere die Milch zum Kochen gebracht hat. Die erste Form der Synchronisation ist der gegenseitige Ausschluss (mutual exclusion), die zweite Form bezeichnet man als Bedingungs Synchronisation (condition synchronisation). Die beiden Aspekte der Synchronisation entstehen dadurch, dass Aktivitäten mehrerer Prozesse auf zwei Arten miteinander in Berührung kommen können. Zum einen, weil sie sich bei der Benutzung gemeinsamer Ressourcen gegenseitig in die Quere kommen (Konkurrenz), zum anderen weil sie an einem gemeinsamen Ziel arbeiten (Kooperation) und sich dabei abstimmen müssen. Multiprogrammierung Die Köche, die gemeinsam einen Brei zubereiten (oder verderben), sind ein Beispiel für eine Situation, bei der nebenläufige Aktivitäten beide Formen der Synchronisation einsetzen müssen. Häufig beschränkt sich die Synchronisation aber darauf, die Nutzung gemeinsamer Hilfsmittel zu regeln. Die verschiedenen Programme, die in einem Mehrbenutzersystem gleichzeitig aktiv sind, kooperieren in der Regel nicht miteinander. Sie sind nebenläufig, weil sie um die Komponenten des Rechners konkurrieren. 1

7 2 KAPITEL 1. EINFÜHRUNG Der Programmierer eines Anwendungsprozesses muss sich um diese Nebenläufigkeit normalerweise nicht kümmern. Das Betriebssystem erledigt die entsprechenden Verwaltungsarbeiten und gibt den einzelnen Programmen die Illusion, im alleinigen Besitz der Rechner-Ressourcen in erster Linie des Prozessors zu sein. Diese Form der Nebenläufigkeit nennt man Multiprogrammierung (multi programming). Mehrere ansonsten völlig unabhängige Programme teilen sich einen Prozessor und werden dabei quasi zeitgleich abgearbeitet. Multitasking: HW und SW Parallelität Von Multitasking (multi tasking) spricht man, wenn sich die Programme (in diesem Fall Prozesse genannt) immer noch einen Rechner teilen, aber nicht bezugslos in purer Konkurrenz agieren, sondern kooperieren. Warum sollten unterschiedliche Prozesse kooperieren? Ein Problem kann zwar immer wenn überhaupt von einem einzigen Prozess bearbeitet werden, oft ist es aber günstiger einen Algorithmus in mehrere Prozesse aufzuteilen, und sei es nur, um die Programmstruktur übersichtlicher zu machen. Die Aufteilung einer Aufgabe in mehrere Prozesse bringt reale Vorteile in Form einer höheren Geschwindigkeit, wenn mehrere Prozessoren zur Verfügung stehen. Bei einer Matrix Multiplikation können beispielsweise die Elemente der Ergebnismatrix völlig unabhängig und darum gleichzeitig berechnet werden. Jeder Prozessor kann mehr oder weniger unabhängig vom anderen eine Teilaufgabe in Form eines Prozesses bearbeiten. Die Aufgabe des Programmierers besteht dann darin, die einzelnen Prozesse so zu formulieren, dass sie so unabhängig wie möglich und so kooperierend wie nötig aktiv sind. Multitasking unterstützt hier die (Hardware ) Parallelität. Multitasking zur SW Strukturierung Ein zweiter und zumindest bis heute wesentlich wichtigerer Grund für die Anwendung von Multitasking ist die Tatsache, dass viele Algorithmen einfacher als kooperierende Prozesse denn als sequentielles Programm formuliert werden können. Beispiele für solche Algorithmen sind Betriebssysteme und Realzeitsysteme. Ein Realzeitsystem steuert beispielsweise ein Kraftwerk, ein Flugzeug oder auch einen Herzschrittmacher. Bei einer solchen Steuerung muss auf Vieles gleichzeitig geachtet werden. Beim Kraftwerk können Ventile ausfallen, Brennstoff muss nachgeschoben werden, die Jahresabrechnug muss gedruckt werden, die Bedienmannschaft möchte schöne Graphiken eines funktionierenden Systems sehen, etc. All diese Aktivitäten müssen in der richtigen Reihenfolge, mit den richtigen Prioritäten und vor allem mehr oder weniger gleichzeitig ausgeführt werden. Es ist zwar im Prinzip möglich, alle Aktivitäten in ein einziges sequentielles Programm zu packen. Wesentlich einfacher ist es aber, die mehr oder weniger unabhängigen Aktivitäten unabhängig voneinander in eigenständigen Prozessen zu formulieren und die Problematik der Synchronisation einem System der Prozess-Verwaltung zu überlassen. Multitasking wird jetzt als Mittel der Software-Technik eingesetzt: Teilaufgaben werden entkoppelt und in jeweils eigenständigen Prozessen konzentriert. Die Synchronisation der Teilaufgaben und der Rest der Algorithmen werden getrennt und damit unabhängiger voneinander. Man kann also von einer Unterstützung der Problemzerlegung oder von Software Parallelität sprechen.

8 3 Die softwaretechnische Anwendung des Multitaskings (die SW Parallelität) hat eine lange Tradition im Bereich der Betriebssysteme und Realzeitanwendungen. Sie ist, wie bereits gesagt, heute immer noch wichtiger als die Ausnutzung mehrerer Prozessoren (die HW Parallelität). Mit der aufkommenden Verbreitung von Multiprozessor Systemen wird der Parallelitätsaspekt an Bedeutung gewinnen. Anwendungen, die in Form mehrerer kooperierender Prozesse formuliert sind, könnten auf einen solchem System einen Effizienzgewinn erzielen. Multitasking und Parallelität Im letzten Abschnitt ging es um Problemstellungen und Algorithmen die inhärent nebenläufig sind und darum einfacher als nebenläufiges Programm formuliert werden. Geht man dagegen umgekehrt von paralleler Hardware aus, dann kommt es oft vor, dass eine sequentielle Problemstellung als System nebenläufiger Prozesse formuliert wird, um eine Beschleunigung der Berechnung zu erreichen. Die hierbei eingeführte zusätzliche Komplexität wird man allerdings nicht unbedingt dem Anwendungsprogrammierer zumuten wollen. Wenn möglich, wird man sie eher einem speziellen Compiler überlassen. Der Anwendungsprogrammierer schreibt ein sequentielles Programm, das von einem parallelisierenden Compiler dann in eine Kollektion nebenläufiger Prozesse zerlegt wird. Speziell numerische Anwendungen sind sehr oft so strukturiert, dass die Parallelisierung in dieser Art automatisch erfolgen kann. Eine wirklich wirksame Parallelisierung kann darüberhinaus auch nur in Abhängigkeit von der konkreten Ziel-Hardware erfolgen. Auch dies spricht dafür, sie wenn irgend möglich dem Compiler zu überlassen. Numerische Algorithmen und Parallelität werden darum in dieser Veranstaltung kaum eine Rolle spielen. Für die inhärent nebenläufigen Algorithmen der Systemprogrammierung Datenbanksysteme, Betriebssysteme, Steuerungssysteme, etc. wird man keine parallelisierenden Compiler einsetzen können: Die Nebenläufigkeit kommt aus der Problemstellung und nicht von der Hardware. Hier ist die Kunst des Programmierers gefordert. Verteiltheit Die Verteiltheit eines System ist immer eine Frage des Blickwinkels. Für HW Entwickler sind bereits Rechnerkomponenten wie der Prozessor verteilte Systeme, in dem sehr viele Dinge gleichzeitig geschehen. Für den Entwickler eines Betriebssystems gibt es immer noch parallele HW Aktivitäten, wenn auch in eingeschränktem Ausmaß. Für den Anwendungsprogrammierer ist der Rechner dagegen ein rein sequentielles System, das seine Aufträge Anweisung für Anweisung ausführt. Abgesehen von dieser Frage des Blickwinkels hat es sich allerdings allgemein eingebürgert, nur dann von einem verteilten System zu sprechen, wenn mehrere autonome Rechnersysteme ohne die Benutzung eines gemeinsamen Speichermediums kooperieren. Man kann diese Definition verallgemeinert so formulieren: Verteilte Systeme nutzen keine gemeinsamen Ressourcen, außer denen, die zum Austausch von Nachrichten notwendig sind z.b. das Kabel an dem sie beide hängen. In einem solchen System ist die Kooperation darauf beschränkt, dass die Komponenten sich gegenseitig Informationen zusenden: Die Kooperation wird durch Kommunikation realisiert. Verteilte Systeme sind also nebenläufige Systeme, bei denen das Problem des Zugriffs auf gemeinsame Ressourcen keine Rolle spielt. Ein typisches verteiltes System basiert auf einem Rechnernetz (LAN oder WAN). Ein praktischer Unterschied zu anderen nebenläufigen Systemen liegt darum auch darin, dass die Koope-

9 4 KAPITEL 1. EINFÜHRUNG ration um Größenordnungen mehr Zeit benötigt. Verteilte Systeme können zum einen Systemprogramme sein. Beispielsweise kooperieren bei einem PC LAN der Fileserver und Systemprogramme in den Arbeitsstationen um dem Anwender (und seinen Programmen!) die Illusion zu geben, Dateien auf dem Server seien lokal verfügbar. Verteilte Systemprogramme verbergen hier die Verteiltheit vor der Anwendungsschicht. In vielen Fällen kann oder will der Entwickler einer Anwendung aber nicht auf einer Plattform arbeiten, die ihm die Illusion eines monolithischen (unverteilten) Systems gibt. Etwa weil die Dienste des Systems zu langsam oder zu unflexibel sind. In diesem Fall ersche die Verteiltheit in den Anwendungsprogrammen in der Form von Systemaufrufen zum Datentransfer. Mit der Socket Schnittstelle steht eine weitverbreitete und relativ einfach zu bedienende Sammlung von Systemaufrufen zur Verfügung, die die Entwicklung von verteilten Systemen unterstützt. Sie wird darum in der Veranstaltung eine wichtige Rolle spielen. Realisation der Nebenläufigkeit Eine nebenläufige Anwendung egal ob die Nebenläufigkeit der HW oder der SW Parallelität dient trennt die Synchronisationsmechanismen vom Rest des Programms so ab, dass der Programmierer dort nur noch sagen muss, was an Synchronisation zwischen den Prozessen stattfinden soll, aber nicht mehr wie dies erreicht wird. Die ursprünglichen und immer noch wichtigsten nebenläufigen Systeme sind die Betriebssysteme. Da ein Betriebssystem per Definition auf der Hardware aufsetzt, gibt es da niemanden, auf den die Last der Realisation der Synchronisation von Prozessen abgewälzt werden kann: Ihre Abtrennung wird zur Frage der Modularisierung. Ein Teil ein Modul des Betriebssystems hat sich um diese Dinge zu kümmern und den Rest davon zu befreien. Natürlich können nicht nur Betriebssysteme, sondern auch Anwendungen zur Realisation von Mechanismen der Nebenläufigkeit direkt auf der HW aufsetzen. Das typische Beispiel wäre ein Realzeitsystem, das unter DOS aktiv ist und die dort sehr einfachen und effizienten Möglichkeiten des direkten Zugriffs auf die HW entsprechend nutzt. Bei der Implementierung einer nebenläufigen Anwendung (statt eines Systemprogramms) hat man allerdings die Chance jemanden zu finden, auf den die Prozessbehandlung abgewälzt werden kann. Genau gesagt gibt es sogar dei Kandidaten: das Betriebssystem, die Sprache, in der die Anwendung geschrieben ist, oder auch spezielle vorgefertigte SW-Komponenten. Nebenläufigkeit durch Sprachmittel oder Systemaufrufe Wenn es um die Frage geht, wie nebenläufige Systeme zu realisieren sind, geraten die Vertreter von zwei Denkschulen regelmäßig in heftigen Streit: Nebenläufigkeit wird traditionell entweder in Form von Konstrukten einer speziellen Programmiersprache oder aber direkt als Teil der Funktionalität des Betriebssystems zur Verfügung gestellt und genutzt. In dem einen Fall erfolgt die Synchronisation zwischen zwei Prozessen durch bestimmte Systemaufrufe an das Betriebssystem, im anderen Fall benutzt der Programmierer eine Sprache, die bestimmte Konstrukte zur Synchronisation der Prozesse anbietet. Rein technisch gesehen besteht kein sehr großer Unterschied darin, ob der Programmierer auf die Mechanismen des Systems (HW oder Betriebssystem) direkt zugreift, oder ob ein Compiler und Laufzeit-

10 5 system ihm dabei helfen. In der Praxis kann der Unterschied aber erheblich sein. Je nach dem, welche Dienste das System bietet, kann seine Benutzung außerordentlich mühsam und fehlerträchtig sein. Programmiersprachen können dies vereinfachen. Sie stellen dazu auch eine weitere Schicht dar, die Systemunabhängigkeit garantiert. Systemaufrufe können auf dem einen System so und auf einem anderen ganz anders definiert sein. Eine Sprachimplementierung gewährleistet dagegen die Adaption der Anwendung an unterschiedliche Plattformen. Wichtige Sprachen mit Konstrukten zur Unterstützung der Nebenläufigkeit sind beispielsweise Ada und Pearl. In die Diskussion um die Plattform, auf der eine nebenläufige und/oder verteilte Anwendung aufsetzen sollte, ist in den letzten Jahren ein neues und wichtiges Konzept eingeführt worden. Mit dem Begriff Middleware bezeichnet man Infrastruktursysteme zur sprach- und betriebssystemunabhängigen Unterstützung moderner (und damit verteilter) Anwendungen. Diese Systeme sind in gewisser Weise eine zeitgemäße Variante des Konzepts einer systemunabhängigen höheren Anwendungsplattform. Bis vor einigen Jahren war es die vorherrschende Meinung, dass es geeignete Programmiersprachen sind, die diese Plattformen bereitstellen sollten. Eine Meinung, die mit Java immer noch (oder wieder) weite Unterstützung findet. Trotz aller Vorteile von Sprachen wie Ada (Java, Pearl,...), oder moderner Middleware Konzepte, werden wir in dieser Veranstaltung Nebenläufigkeit auf der niederen Ebene der Systemaufrufe betrachten. Die verwendeten System-Schnittstellen sind (offiziell oder de facto) genormt und garantieren damit ebenfalls eine (gewisse) Portierbarkeit der Programme. Mit Einschränkungen gilt dies sogar für Portierungen aus dem Bereich der Unix Systeme hinaus. (Sockets in Unix und Win Sockets etwa sind sehr ähnlich.) Daneben sind die Systemaufrufe ausreichend vielfältig und mächtig, um alle gewünschten Mechanismen realisieren zu können. Mangelnder Bequemlichkeit in der Benutzung kann durch eigene Programmierarbeit abgeholfen werden. Die Verwendung von Systemaufrufen bietet darüber hinaus den didaktischen Vorteil eines direkteren Zugangs zu den zugrundeliegenden Mechanismen. Der wichtigste Grund ist aber die Einfachheit der Systemaufrufe im Vergleich zu speziellen Programmiersprachen oder gar weitergehenden Konzepten. Dies erlaubt die Konzentration auf die Probleme der Nebenläufigkeit und Verteiltheit. Die Entscheidung für Systemaufrufe ist also in erster Linie didaktisch motiviert und keinesfalls als Empfehlung an alle Anwendungsprogrammierer zu verstehen. Komplexe Realzeitsysteme wie Kraftwerksteuerungen sind nebenläufig aber nicht verteilt. Man entwickelt sie sicher am besten in einer Programmiersprache wie Pearl oder Ada (Java?) und lässt sie unter einem speziellen Realzeitbetriebssystem ablaufen. Kleine Steuerungsaufgaben können DOS Programme mit direktem HW Zugriff bestens erledigen. Relevante (kommerzielle) Anwendungssysteme sollten heute stets auf einer etablierten Middleware Plattform aufsetzen. Ist man in der Verlegenheit, derjenige zu sein, der die höheren Konstrukte realisieren soll, dann bleibt natürlich keine andere Wahl als der Zugriff auf Systemaufrufe. Wie auch immer die grundlegenden Konzepte der Nebenläufigkeit sind in jedem Fall die gleichen.

11 Kapitel 2 Nebenläufige Programme 6

12 2.1. PROZESSE Prozesse Prozesse als unabhängige Kontrollflüsse In einem sequentiellen System gibt es einen einzigen Kontrollfluss: Jede Anweisung legt zweifelsfrei fest, welche Anweisung als nächste ausgeführt wird. Ein nebenläufiges System dagegen hat mehrere Kontrollflüsse. Eine Anweisung legt nur die innerhalb des gleichen Kontrollflusses nächste Anweisung fest. Die Ausführung der Anweisungen verschiedener Kontrollflüsse kann beliebig verschränkt oder echt gleichzeitig erfolgen. Ein gerade in Ausführung befindlicher Kontrollfluss wird üblicherweise Prozess genannt. Mit Prozess bezeichnet man aber auch die Spezifikation eines Kontrollflusses. Die Reihenfolge, in der Anweisungen innerhalb eines Kontrollflusses abgearbeitet werden, ist extrem wichtig. Dagegen darf die Art, in der die Anweisungen eines Kontrollflusses relativ zu denen eines anderen ausgeführt werden, keinen Einfluss auf das korrekte Verhalten des Gesamtsystems haben. Ein nebenläufiges System muss immer vollständig unabhängig von der relativen Abarbeitung seiner Prozesse sein. Anders ausgedrückt: Ein nebenläufiges System ist dann korrekt, wenn es unter allen möglichen Verschränkungen und/oder parallelen Abwicklungen seiner Prozesse korrekt ist. Dass manche Abarbeitungen fairer sind als andere, ist ein weiterer aber zunächst einmal unabhängiger Aspekt. (Ob Korrektheit auch Fairness einschließt, ist auch in anderen Bereichen eine nicht immer einheitlich beantwortete Frage.) Ein gutes Beispiel für ein nebenläufiges System stellt ein Rechner im Mehrbenutzerbetrieb dar. Jeder Benutzer hat Anspruch darauf, dass er fair bedient wird und dass sein Programm unabhängig von allen anderen in der von ihm festgelegten Reihenfolge der Anweisungen ausgeführt wird. Das System kann nach eigenem Ermessen die Programme aller Benutzer entsprechend seiner Ausstattung mit Prozessoren und anderer Hardware mehr oder weniger echt parallel oder verschränkt ausführen. Da die Programme der verschiedenen Benutzer in der Regel völlig unabhängig voneinander sind, ist das gesamte nebenläufige System auf den ersten Blick unabhängig und damit trivialerweise korrekt. Die Programme sind ohne Bezug zueinander und können darum mit beliebiger relativer Geschwindigkeit (Verschränkung) abgearbeitet werden Synchronisation und atomare Aktionen Auf den zweiten Blick und tatsächlich sind die Programme allerdings nicht unabhängig voneinander. Ihre gegenseitige Abhängigkeit ist indirekt. Sie ergibt sich aus der Benutzung gemeinsamer Ressourcen. Zwei Programme könnten zwar in beliebiger Verschränkung einen Drucker benutzen, man würde allerdings einen Ausdruck, bei dem zwei Programme im Wechsel Zeile für Zeile drucken, als weniger korrekt ansehen, als eine andere, bei der zuerst das eine und dann das andere Programm alles ausdruckt. Die Lösung des Problems ist offensichtlich: Das Betriebssystem muss einfach dafür sorgen, dass Gerätschaften wie ein Drucker eben nicht in beliebigem Wechsel, sondern der Reihe nach benutzt werden: Die Prozesse müssen synchronisiert werden. Beim Drucker ist die Grundforderung, dass beliebige Verschränkungen der Prozesse möglich sein sollten, dann erfüllt, wenn die Benutzung des

13 8 KAPITEL 2. NEBENLÄUFIGE PROGRAMME Druckers eine einheitliche Aktion darstellt. Die Druckernutzung kann nur als ganzes beliebig verschränkt werden, nicht aber aufgelöst in zeilen oder gar zeichenweises Drucken. Die Synchronisation besteht darin, die Druckernutzung als unteilbare Operation zu garantieren. Die Prozesse können ihre Aktionen weiterhin in jeder beliebigen Verschränkung und relativen Geschwindigkeit zueinander ausführen. Wir definieren den Begriff Aktion aber nicht als Anweisung sondern als atomare Aktion: Eine Aktion ist atomar, wenn ihre Abarbeitung nicht unterbrochen werden darf, ohne, dass das Gesamtsystem die Eigenschaft verliert, die Prozesse in beliebiger Verschränkung ausgeführen zu können. Die Synchronisation besteht dann darin, atomare Aktionen zu definieren. Bevor wir uns dieser Thematik der Synchronisation etwas ensiver zuwenden, betrachten wir zunächst einmal einige praktische Möglichkeiten Prozesse zu definieren und zur Ausführung zu bringen Unix Prozesse: Schwergewichtige Prozesse Programme und Prozesse Ein Betriebssystem führt jedes Benutzerprogramm als Prozess aus. Ohne hier auf Einzelheiten eingehen zu wollen, tragen wir noch einmal die wesentlichen Fakten zusammen. Einem Programm werden verschiedene Datenbereiche im Speicher zugeordnet (2.1): 1. Programmcode: die ausführbaren Anweisungen; 2. Stack: die activation records der aktiven Funktionen mit den funktionslokalen Variablen (storage class automatic in C Terminologie), Rücksprungadressen, etc.; 3. Statische Variablen: initialisierte und nicht initialisierte globale Variablen (storage class static in C Terminologie); 4. Heap: Speicherbereich für dynamische Allokation; 5. Kontext: Umgebungsvariablen und Programmargumente. Das genaue Format der Datenbereiche eines Programms hängt natürlich vom verwendeten Compiler ab, es ist hier aber nicht weiter von Interesse. Das Betriebssystem aktiviert die Programme, die damit zu Prozessen werden: Ein Prozess ist ein Programm, das gerade ausgeführt wird. Ein Programm ist ein Prozess des Betriebssystems, der ein Benutzerprogramm ausführt. Jeder Prozess muss vom Betriebssystem überwacht werden. Dazu sind weitere Datenstrukturen notwendig. Deren genaues Aussehen hängt natürlich vom verwendeten Betriebssystem ab. Konzeptionell (oder eventuell tatsächlich) gehört zu jedem Prozess ein Prozesskontrollblock (kurz PCB), in dem alle Daten, die den Prozess charakterisieren, zusammengefasst sind. Ein PCB wird zumindestens folgende Informationen enthalten: Zustand: aktiv, angehalten, etc.; Progammzähler: die Adresse der nächsten auszuführenden Anweisung;

14 2.1. PROZESSE 9 Kontext Stack Speicherbereiche eines Programms Heap statische Daten Progammcode Abbildung 2.1: Datenbereiche eines Programms Registerwerte: der Inhalt aller Register und andere Informationen zum Programmzustand, die nicht im Stack, Heap oder anderen Datenbereichen des Programm zu finden sind; Verwaltungsinformation des Betriebssystems: Priorität des Prozesses, Zugehörigkeit zu einer Prozessgruppe, etc. Bei jedem Start eines Prozesses müssen die Datenbereiche des Programms und der PCB angelegt und mit Informationen gefüllt werden. Beim Prozesswechsel (Context Switch), also wenn ein Prozess zugunsten eines anderen nicht mehr weiter ausgeführt wird, müssen zumindest die PCBs der beiden betroffenen Prozesse aktualisiert werden. Da beides recht aufwendige Aktivitäten sind, bezeichnet man die Prozesse des Betriebssystems als schwergewichtige Prozesse (heavy weight processes). Prozess-Id Jeder Prozess des Unix Systems hat einen eindeutigen Bezeichner namens Prozess ID (process ID). Die Prozess ID ist eine Integerzahl und kann mit dem Systemaufruf getpid festgestellt werden.

15 10 KAPITEL 2. NEBENLÄUFIGE PROGRAMME #include <sys/types.h> #include <unistd.h> pid t getpid(void); Jedes Programm wird als Prozess ausgeführt und dieser Prozess kann seine Prozess ID feststellen. Im Folgenden unterscheiden wir nur noch wenn notwendig zwischen Programmen und den aus ihnen erzeugten Prozessen. Ein Beispiel für ein Programm, das seine eigene ID feststellt ist: myid.c #include <stdio.h> #include <sys/types.h> #include <unistd.h> void main (void) { prf ("My process ID is: %ld\n", (long)getpid()); myid.c Prozesserzeugung Jede Prozesserzeugung obliegt dem Betriebssystem. Der Auftrag eines Benutzers, ein Programm auszuführen, veranlasst normalerweise eine Prozesserzeugung durch das Betriebssystem. In Unix kann ganz allgemein jeder Prozess mit dem Systemaufruf fork die Erzeugung eines neuen Prozesses anfordern. #include <sys/types.h> #include <unistd.h> pid t fork(void) Der fork Aufruf erzeugt einen neuen Prozess als Kopie des erzeugenden Prozesses. Nach dem fork Aufruf gibt es zwei sehr ähnliche Prozesse, die beide aktiv sind. Ein Prozess ruft fork auf, zwei Prozesse kehren aus dem Aufruf zurück! Der einzige Unterschied zwischen beiden ist die Prozess Id und der Rückgabewert von fork: Der neue Prozess erhält eine neue ID. Die neue ID wird als Wert des fork Aufrufs im alten Prozess zurückgegeben. Im neuen Prozess, der als Kopie des alten ebenfalls aus dem fork Aufruf zurückkehrt, wird 0 als Rückgabewert geliefert. Der Rückgabewert von fork (0 oder 0) kann dann im weiteren Programmablauf benutzt werden, um Erzeuger und Erzeugtes zu unterscheiden:

16 2.1. PROZESSE 11 forkex.c #include <stdio.h> #include <sys/types.h> #include <unistd.h> void main (void) { pid_t fork_result; prf ("My process ID is: %ld\n", (long)getpid()); fork_result = fork(); if (fork_result == 0){ prf ("\n \n"); prf ("fork_result = %ld\n", (long)fork_result); prf ("My process ID is: %ld, I m the child\n", (long)getpid()); prf (" \n"); else if (fork_result > 0 ){ prf ("\n \n"); prf ("fork_result = %ld\n", (long)fork_result); prf ("My process ID is: %ld, I m the parent\n", (long)getpid()); prf (" \n"); else prf ("fork ERROR\n"); forkex.c Der Systemaufruf wait Mit den Systemaufrufen wait und waitpid kann ein Prozess auf die Beendigung der von ihm erzeugten Subprozesse warten. #include <sys/types.h> #include <sys/wait.h> pid t wait( *status); pid t waitpid(pid t pid, *status, options); Wenn der wait aufrufende Prozess keine Kinder hat, liefert wait den Wert -1. Andernfalls liefert er die Prozess ID (kurz PID) eines Kindes, auf das noch nicht gewartet wurde. Gibt es kein solches

17 12 KAPITEL 2. NEBENLÄUFIGE PROGRAMME Kind, dann blockiert wait solange, bis eines der Kinder endet. Der Exit Status des beendeten Kindes wird in der wait übergebenen Integervariablen status abgespeichert. Beispiel: waitex.c #include <stdio.h> #include <sys/types.h> #include <unistd.h> void main (void) { pid_t fork_res; pid_t wait_res; child_status; fork_res = fork(); if (fork_res == 0){ prf ("CHILD: My PID is: %ld, I m the child\n", (long)getpid()); exit (0); else if (fork_res > 0 ){ prf ("PARENT: My process ID is: %ld, I m the parent\n", (long)getpid()); wait_res = wait (&child_status); prf ("PARENT: Wait pid: %ld, status: %d\n", (long)wait_res, child_status); else prf ("fork ERROR\n"); waitex.c Beim Aufruf von waitpid wird nicht auf irgendein Kind gewartet, sondern auf eines mit einer ganz bestimmten PID, die als Argument übergeben wird. Mit den Optionen kann das Verhalten von waitpid weiter beeinflusst werden. Ist dies nicht gewünscht, dann übergibt man den Wert 0 als Optionen Parameter. Beispiel: Ein Prozess der viele Kinder erzeugt und explizit auf deren Ende wartet. child.c #include <stdio.h> #include <sys/types.h> #include <unistd.h> #define MAX_CHILD_NUM 10

18 2.1. PROZESSE 13 main ( argc, char *argv[]) { pid_t fork_res; pid_t wait_res; pid_t child_pid[max_child_num]; child_status; child_num, i; if (argc!= 2) exit(1); child_num = atoi(argv[1]); if (i > MAX_CHILD_NUM) child_num = MAX_CHILD_NUM; i= child_num; while (i > 0) { i--; prf ("PARENT: forking\n"); fork_res = fork(); child_pid[i] = fork_res; if (fork_res == 0){ prf ("CHILD: I m a child, my PID is: %ld, \n", (long)getpid()); exit (0); else if (fork_res < 0 ){ prf ("fork ERROR\n"); exit (1); for (i = 0; i < child_num; i++) { wait_res = waitpid (child_pid[i], &child_status, 0); prf ("PARENT: Pid Nr. %d: %ld, status: %d\n", i, (long)wait_res, child_status); exit (0); child.c Ein wait oder waitpid Aufruf wird benutzt, um den Exit Status des Kindes festzustellen. Er ist aber auch dann sinnvoll, wenn der Elternprozess keinerlei Interesse an diesem Status hat: Fast alle Datenstrukturen des beendeten Prozesses werden vom Betriebssystem freigegeben. Ein Rest wird jedoch aufgehoben bis zu einem wait Aufruf. Bleibt dieser aus, dann werden die entsprechenden Datenstrukturen auch nicht freigegeben.

19 14 KAPITEL 2. NEBENLÄUFIGE PROGRAMME Prozessende Ein Prozess endet, wenn mit einer expliziten oder impliziten (Erreichen des Endes) return Anweisung seine main-funktion verlassen wird, oder wenn die exit oder exit Anweisung aufgerufen wird. #include <stdlib.h> void exit( status); #include <unistd.h> void exit( status); Der Unterschied zwischen exit und exit besteht darin, dass exit noch gewisse Aufräumaktionen durchführt. Mit exit wird dagegen der Prozess sofort beendet. (exit ruft ern exit auf.) Der Status Parameter wird ebenso wie ein Argument von return aus main weitergegeben und kann mit einer wait Routine abgefragt werden. Der Statuswert 0 zeigt üblicherweise ein normales Ende an. Alle anderen Werte weisen auf einen Fehler hin. Wird eine Prozess mit return oder exit nicht aber mit exit verlassen, dann wird eine vorher installierte Behandlungsroutine ausgeführt. Eine Behandlungsroutine wird mit der C Routine atexit installiert. #include <stdlib.h> atexit(void (*function)(void)); Werden mehrere Routinen mit atexit installiert, dann werden alle in umgekehrter Installationsreihenfolge ausgeführt. Beispiel: exitex.c #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #define MAX_CHILD_NUM 10 void exit_parent (void){ prf ("-E-X-I-T P-A-R-E-N-T-\n");

20 2.1. PROZESSE 15 void exit_child (void){ prf ("-E-X-I-T C-H-I-L-D\n"); main ( argc, char *argv[]) { pid_t fork_res; pid_t wait_res; pid_t child_pid[max_child_num]; child_status; child_num, i; (void) atexit (exit_parent); if (argc!= 2) exit(1); child_num = atoi(argv[1]); if (i > MAX_CHILD_NUM) child_num = MAX_CHILD_NUM; i= child_num; while (i > 0) { i--; fork_res = fork(); child_pid[i] = fork_res; if (fork_res == 0){ prf ("CHILD: I m a child, my PID is: %ld, \n", (long)getpid()); (void) atexit (exit_child); exit (0); else if (fork_res < 0 ){ prf ("fork ERROR\n"); exit (1); for (i = 0; i < child_num; i++) { wait_res = waitpid (child_pid[i], &child_status, 0); exitex.c

21 16 KAPITEL 2. NEBENLÄUFIGE PROGRAMME Threads: Leichtgewichtige Prozesse Prozesse und Threads Systemprozesse und Anwendungsprozesse Der Systemaufruf fork erzeugt Prozesse, die direkt vom Betriebssystem verwaltet werden. Sie sind recht streng voneinander abgegrenzt, und das Betriebssystem garantiert sowohl eine faire Behandlung als auch den gegenseitigen Schutz vor unabsichtlichen und absichtlichen Fehlgriffen. Das ist sinnvoll und notwendig, da diese Prozesse in erster Linie dazu dienen, die Programme verschiedener Benutzer abzuarbeiten. Jeder Prozess nicht nur einer des Betriebssystems kann fork aufrufen und neue Prozesse erzeugen. Dadurch kann und wird seit langem fork auch dazu benutzt, nebenläufige Benutzer Programme zu implementieren. Prozesse als Mittel nebenläufige Programme zu realisieren und Betriebssystem- Prozesse als Mittel eine nebenläufige Ausführung von Benutzerprogrammen zu erreichen sind zwar im Prinzip das Gleiche, praktisch gibt es allerdings einige Unterschiede. Abschottung oder Kooperation Nebenläufig ausgeführte Programme verschiedener Benutzer müssen möglichst gut gegeneinander abgeschottet werden. Bei Prozessen innerhalb einer Anwendung ist das von weit geringerer Bedeutung. Dafür ist hier die Geschwindigkeit eines Kontextwechsels von größerer Bedeutung: er kommt wesentlich häufiger vor. Ähnliches gilt für die faire Abarbeitung der Prozesse. Wenn die Prozesse dazu dienen, die Programme unterschiedlicher Benutzer auszuführen, dann muss jemand darüber wachen, dass alle ihren gerechten Anteil an Prozessor-Zeit bekommen. Sind die verschiedenen Prozesse Bestandteil einer Anwendung, dann kann der Programmierer den relativen Vorrang seiner Prozesse besser selbst bestimmen. Dem Prozess zur Notabschaltung eines Kraftwerks eine höhere Priorität zu geben, als dem, der den Jahresgewinn berechnet, mag unfair sein, ist aber eventuell gewollt. Falsche Prioritäten sind hier keine Ungerechtigkeit der man hilflos ausgesetzt ist, sondern ein behebbarer Programmierfehler. Vor allem aber müssen Prozesse, wenn sie zu einer Anwendung gehören, kooperieren. Dazu müssen sie über effektive Kommunikationsmittel verfügen, da der Grad der Interaktion zwischen ihnen i.d.r. wesentlich größer sein wird. Gerade die gegenseitige Abschottung und die effektive Kooperation sind stark divergente Ziele. Unterschiedliche Prioritäten bei der Realisation des Prozesskonzepts Diese Überlegungen legen es nahe, neben den schwergewichtigen Prozessen ein alternatives Prozess-Konzept einzuführen, das speziell auf die Realisation nebenläufiger Anwendungen ausgerichtet ist. Um den Unterschied deutlich zu machen, redet man in diesem Fall meist (aber nicht immer) von Threads statt von Prozessen. Threads sind natürlich auch Prozesse. Sie dienen nur anderen Zwecken. Bei ihrer Implementierung gelten andere Prioritäten in Bezug auf gegenseitige Abschottung, Synchronisation, Kommunikation und die Geschwindigkeit eines Kontextwechsels. Sie werden darum auf andere Art als die schweren (Betriebssystem ) Prozesse realisiert. Threads gibt es vielen Varianten. Sie können gänzlich ohne die Hilfe des Betriebssystems realisiert werden, dazu gibt es diverse Implementierungen. Es gibt sie in unterschiedlichen Varianten in unterschiedlichen Betriebssystemen. Im Unix Umfeld spielen die in POSIX definierten Threads und die Threads von Unix International eine gewisse Rolle. Beide sind recht ähnlich. Im Folgenden werden wir nur die POSIX Threads behandeln. (Fragen der Implementation von Threads und Prozessen er-

22 2.1. PROZESSE 17 essieren hier nicht weiter. Man konsultiere dazu die Veranstaltungen zum Thema Betriebssysteme.) POSIX steht für Portable Operating System Interface und bezeichnet eine Serie ernationaler Standards, welche die Schnittstelle zwischen Programmen und dem System spezifizieren. Einer dieser Standards (POSIX.1c) definiert eine Serie von Aufrufen zur Erzeugung von Threads und deren Kommunikation und Synchronisation. Erzeugung eines Threads Mit fork und wait können Prozesse erzeugt und ihr Ende abgewartet werden. Ein entsprechender Mechanismus steht auch bei Threads zur Verfügung. Wir zeigen zunächst ein einfaches Beispiel: threadex.c #include <stdlib.h> #include <stdio.h> #include <pthread.h> pthread_attr_t thread_attr; // Der Thread--Koerper: void * thread_routine (void *arg) { i = *( *)arg; *my_status_p = ( *)malloc(sizeof()); prf ("Thread: Here I am, the thread!\n"); prf ("Thread: My Argument is %d\n", i); *my_status_p = 0; while (i>0) { prf ("Thread: I m still running!\n"); i--; (*my_status_p)++; pthread_exit ((void *)my_status_p); static tread_arg = 13; main ( argc, char *argv[]){ pthread_t thread_id; *thread_status_p; // Setzen von Thread-Attributen:

23 18 KAPITEL 2. NEBENLÄUFIGE PROGRAMME pthread_attr_init (&thread_attr); pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_JOINABLE); // Erzeugen eines Threads: if (pthread_create ( &thread_id, &thread_attr, thread_routine, (void *)&tread_arg) ) { perror ("Error while creating thread"); exit (1); prf ("Main: Created Thread with ID: %d\n", ()thread_id); prf ("Main: Waiting for thread %d\n", ()thread_id); // Warten auf das Ende des Threads: if (pthread_join (thread_id, (void **) &thread_status_p)) { perror ("Error while joining with thread"); exit (1); prf ("Main: Thread %d is finished. Statuts = %d\n", ()thread_id, *thread_status_p); threadex.c Ein Prozess entsteht als Klon seines Erzeugers. Ein Thread dagegen hat einen eigenen Körper, der in Form einer C Routine definiert wird. Diese Routine wird als drittes Argument bei der Erzeugung des Threads angegeben. Im Beispiel:... void * thread_routine (void *arg) { pthread_create ( &thread_id, &thread_attr, thread_routine, (void *)&tread_arg) ) Mit der Ausführung von pthread create wird ein neuer Kontrollfluss erzeugt, der die Thread Routine abarbeitet. Der Routine kann dabei auch ein Argument vom Typ void * übergeben werden.

24 2.1. PROZESSE 19 #include <pthread.h> pthread create pthread ( t *thread, pthread attr t *attr, (void *) (*thread function)(void *arg), void *thread arg) So wie jeder Prozess eine PID besitzt, so hat jeder Thread eine Thread Id. Sie ist vom Typ pthread t und wird bei der Erzeugung im ersten Argument von pthread create abgelegt. Thread Attribute Ein Thread hat stets bestimmte Eigenschaften, Attribute genannt. Im Beispiel oben wartet das Hauptprogramm auf das Ende des von ihm aktivierten Threads. Das ist nur dann möglich, wenn der Thread die Eigenschaft joinable besitzt. Ein Thread wird mit einem oder eventuell mehreren Attributen versehen, indem eine Datenstruktur vom Typ pthread attr t mit entsprechend gesetzten Werten bei der Thread Erzeugung übergeben wird. Im Beispiel oben wird zunächst eine solche Datenstruktur angelegt: pthread_attr_t thread_attr; Sie wird dann initialisiert: pthread_attr_init (&thread_attr); und mit dem Attribut joinable belegt: pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_JOINABLE); und schließlich dem Aufruf pthread create zur Erzeugung eines Threads übergeben: pthread_create ( &thread_id, &thread_attr, thread_routine, (void *)&tread_arg) Joinable ist dabei eine bestimmte Art von Eigenschaft: sie ist eine Ausprägung des sogenannten detach state, der entweder detached oder joinable sein kann. Mit den Argumenten PTHREAD CREATE DETACHED und PTHREAD CREATE JOINABLE an pthread attr setdetachstate wird er gesetzt.

25 20 KAPITEL 2. NEBENLÄUFIGE PROGRAMME #include <pthread.h> pthread attr setdetachstate const ( pthread attr *attr, *detachstate) Endet ein detached Thread, dann werden alle Status-Informationen vom System eliminiert. Sie können nicht mehr abgefragt werden und ein pthread join ist nicht möglich. Soll also auf das Ende eines Threads gewartet werden, dann muss dieser joinable sein. Wird bei der Thread Erzeugung statt eines Verweises auf eine Attribut-Struktur (vom Typ const pthread attr t *) NULL übergeben, dann wird der Thread mit vorbelegten Attributen erzeugt. Argumente der Thread Routine Einer Thread-Routine kann darum nur ein einziges Argument übergeben werden. Es muss vom Typ void * sein. Sollen mehrere Argumente übergeben werden, dann fasst man sie in einer Struktur zusammen. Es ist auch nicht unbedingt notwendig, dass ein Verweis übergeben wird. Nach der ANSI C Definition ist es erlaubt Integertypen (, unsigned ) von und nach (void *) zu konvertieren. Das Ergebnis einer solchen Konversion ist allerdings implementierungsabhängig. Üblicherweise funktioniert das aber so wie man es erwartet. Bei der Übergabe von Verweisen ist darauf zu achten, dass keine Speicherplätze angesprochen werden, die nicht mehr allokiert sind. Wird beispielsweise ein Thread aus einer Funktion f mit einer Referenz auf eine lokale Variable i gestartet, dann muss f auf das Ende des Threads warten. Andernfalls besteht dort ein Verweis auf eine nicht mehr existente Variable. void f() { i;... if (pthread_create (&id, NULL, ) // vordefinierte Attribute routine, (void *) &i // <- Achtung! Gefahr! ) Lokale Variablen (vom Typ automatic) werden bei Verlassen der Funktion in der sie definiert sind freigegeben. Wenn eine Funktion endet, muss ein in ihr gestarteter Thread, der funktionslokale Variablen

26 2.1. PROZESSE 21 verwendet, aber noch lange nicht am Ende sein. Die Speicherfreigabe am Ende der Funktion nimmt darauf keinerlei Rücksicht! Der Rückkehrstatus eines Threads Die Funktion pthread join entspricht dem wait bei Prozessen. Die aufrufende Funktion wartet auf das Ende des entsprechenden Threads und erhält dann dessen Statusinformation. #include <pthread.h> pthread join( pthread t thread, void **status ) Der Rückkehr-Status wird an der Stelle gespeichert, auf die das Argument status zeigt. Das gilt übrigens auch, wenn pthread join aufgerufen wird, nachdem der Thread schon zu Ende war. (Genau wie auch mit wait der Status längst beendeter Prozesse abgefragt werden kann.) Wird als Statusargument NULL übergeben, dann wird die Statusinformation ignoriert und nicht abgespeichert. pthread join liefert den Wert 0, wenn alles in Ordnung war. Ansonsten wird ein Fehlercode zurückgegeben. Der Status kann benutzt werden, um über ein simples OK / Nicht OK hinaus Informationen zurückzuliefern. Als Beispiel zeigen wir einen Thread, der das Maximum zweier übergebener Werte bestimmt und als Status zurückgibt. argjoinex.c #include <stdlib.h> #include <stdio.h> #include <pthread.h> typedef struct pair { a; b; Pair; pthread_attr_t thread_attr; void * thread_max (void *arg) { result; x = ((Pair *)arg)->a; y = ((Pair *)arg)->b; prf ("Thread: My Arguments are %d, %d\n", x, y);

27 22 KAPITEL 2. NEBENLÄUFIGE PROGRAMME if (x>y) result = x; else result = y; // Rueckgabe eines Integerwertes (kein Verweis) // erlaubte Konversion eines Skalars nach (void *) pthread_exit ((void *)result); main ( argc, char *argv[]){ pthread_t thread_id; thread_result; Pair *thread_arg; pthread_attr_init (&thread_attr); pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_JOINABLE); thread_arg = (Pair *)malloc (sizeof(pair)); thread_arg->a = 21; thread_arg->b = 15; if (pthread_create ( &thread_id, &thread_attr, thread_max, (void *)thread_arg) ) { perror ("Error while creating thread"); exit (1); prf ("Main: Waiting for thread\n"); if (pthread_join (thread_id, (void **) &thread_result)) { perror ("Error while joining with thread"); exit (1); prf ("Main: Thread finished. Result = %d\n", thread_result); argjoinex.c Der Rückkehrstatus wird gesetzt mit: #include <pthread.h> pthread exit( void *status )

28 2.1. PROZESSE 23 pthread exit beendet den aufrufenden Thread unmittelbar und setzt den Statuswert. Ein impliziter Aufruf dieser Routine erfolgt am Ende der Thread Routine. In diesem Fall sollte (in einer korrekten Implementierung) der normale Return Wert als Statuswert weitergegeben werden. Beispiel: Fibonacci Zahlen Die bekannte Fibonacci Funktion ist folgendermaßen definiert: 0 : n = 0 fib(n) = 1 : n = 1 fib(n 1) + fib(n 2) : n > 1 Bei der Berechnung von fib(n) muss sowohl fib(n 1) als auch fib(n 2) berechnet werden. Es bietet sich an, beide Berechnungen parallel in jeweils eigenen Threads auszuführen. Ein entsprechendes Programm lässt sich schnell formulieren: fib.c #include <stdlib.h> #include <stdio.h> #include <pthread.h> pthread_attr_t thread_attr; void * par_fib (void *arg) { pthread_t thread_n_1, thread_n_2; result; n = ()arg; if (n <= 0) result = 0; else if (n == 1) result = 1; else { n_1 = n-1; n_2 = n-2; n_1_result; n_2_result; // Berechnung von fib(n-1): if (pthread_create ( &thread_n_1, &thread_attr, par_fib, (void *)n_1) ) {

29 24 KAPITEL 2. NEBENLÄUFIGE PROGRAMME perror ("Error while creating thread"); exit (1); // Berechnung von fib(n-2): if (pthread_create ( &thread_n_2, &thread_attr, par_fib, (void *)n_2) ) { perror ("Error while creating thread"); exit (1); if (pthread_join (thread_n_1, (void **) &n_1_result)) { perror ("Error while joining with thread"); exit (1); if (pthread_join (thread_n_2, (void **) &n_2_result)) { perror ("Error while joining with thread"); exit (1); // Berechnung von fib(n) result = n_1_result + n_2_result; pthread_exit ((void *)result); main ( argc, char *argv[]){ pthread_t thread_id; n; thread_result; thread_arg; pthread_attr_init (&thread_attr); pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_JOINABLE); thread_arg = atoi (argv[1]); if (pthread_create ( &thread_id, &thread_attr, par_fib,

30 2.1. PROZESSE 25 (void *)thread_arg) ) { perror ("Error while creating thread"); exit (1); prf ("Main: Waiting for thread\n"); if (pthread_join (thread_id, (void **) &thread_result)) { perror ("Error while joining with thread"); exit (1); prf ("Main: Result = %d\n", thread_result); fib.c Dieses Programm stellt natürlich nur ein rein akademisches Beispiel dar. Es ist ganz und gar nicht effizient. Auch nicht auf einem Rechner mit mehreren Prozessoren, auf dem die einzelnen Threads tatsächlich parallel ausgeführt werden. Zwischen der Erzeugung und Vernichtung der Threads und dem was sie tatsächlich an produktiver Arbeit leisten die Addition von zwei Zahlen, herrscht ein zu krasses Missverhältnis. Derart feinkörnige Zerlegungen in Prozesse sind bestenfalls auf einer massiv parallelen Hardware und mit statisch erzeugten Prozessen sinnvoll. Beispiel: Mergesort Der bekannte Mergesort Algorithmus lässt sich in Pseudocode wie folgt spezifizieren: SORT (x) { x: zu sortierender Vektor; a, b := Unter- und Obergrenze x; if (a < b) { m := (a+b)/2; xu := SORT (x(a..m)); xo := SORT (x(m+1..b)); x := MISCHE (xu, xo); Der zu sortierende Vektor wird in zwei Teile zerlegt, die sortiert und dann gemischt werden. Ein Vektor, dessen Untergrenze nicht kleiner ist als seine Obergrenze der also aus maximal einem Element besteht, ist bereits sortiert und muss nicht weiter behandelt werden. Das Sortieren der Teilvektoren ist völlig unabhängig voneinander und kann infolgedessen (pseudo ) parallel in jeweils eigenen Threads erfolgen. merge.c #include <stdlib.h>

PThreads. Pthreads. Jeder Hersteller hatte eine eigene Implementierung von Threads oder light weight processes

PThreads. Pthreads. Jeder Hersteller hatte eine eigene Implementierung von Threads oder light weight processes PThreads Prozesse und Threads Ein Unix-Prozess hat IDs (process,user,group) Umgebungsvariablen Verzeichnis Programmcode Register, Stack, Heap Dateideskriptoren, Signale message queues, pipes, shared memory

Mehr

U9-3 Vergleich von Thread-Konzepten. U9-2 Motivation von Threads. U9-3 Vergleich von Thread-Konzepten (2) U9-1 Überblick

U9-3 Vergleich von Thread-Konzepten. U9-2 Motivation von Threads. U9-3 Vergleich von Thread-Konzepten (2) U9-1 Überblick U9 9. Übung U9 9. Übung U9-1 Überblick Besprechung Aufgabe 6 (printdir) Posix-Threads U9.1 User-Level Threads: Federgewichtige Prozesse Realisierung von Threads auf Anwendungsebene innerhalb eines Prozesses

Mehr

Softwaresysteme I Übungen Jürgen Kleinöder Universität Erlangen-Nürnberg Informatik 4, 2007 U9.fm

Softwaresysteme I Übungen Jürgen Kleinöder Universität Erlangen-Nürnberg Informatik 4, 2007 U9.fm U9 9. Übung U9 9. Übung U9-1 Überblick Besprechung Aufgabe 6 (printdir) Posix-Threads U9.1 U9-2 Motivation von Threads U9-2 Motivation von Threads UNIX-Prozesskonzept: eine Ausführungsumgebung (virtueller

Mehr

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Maren Bennewitz

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Maren Bennewitz Systeme I: Betriebssysteme Kapitel 4 Prozesse Maren Bennewitz Version 13.11.2013 1 Inhalt Vorlesung Aufbau einfacher Rechner Überblick: Aufgabe, Historische Entwicklung, unterschiedliche Arten von Betriebssystemen

Mehr

Threads. Foliensatz 8: Threads Folie 1. Hans-Georg Eßer, TH Nürnberg Systemprogrammierung, Sommersemester 2015

Threads. Foliensatz 8: Threads Folie 1. Hans-Georg Eßer, TH Nürnberg Systemprogrammierung, Sommersemester 2015 Sep 19 14:20:18 amd64 sshd[20494]: Accepted rsa for esser from ::ffff:87.234.201.207 port 61557 Sep 19 14:27:41 amd64 syslog-ng[7653]: STATS: dropped 0 Sep 20 01:00:01 amd64 /usr/sbin/cron[29278]: (root)

Mehr

U8-1 Motivation von Threads. U8-2 Vergleich von Thread-Konzepten. U8-2 Vergleich von Thread-Konzepten (2) Motivation

U8-1 Motivation von Threads. U8-2 Vergleich von Thread-Konzepten. U8-2 Vergleich von Thread-Konzepten (2) Motivation U8 POSIX-Threads U8 POSIX-Threads U8-1 Motivation von Threads U8-1 Motivation von Threads Motivation Thread-Konzepte UNIX-Prozesskonzept: eine Ausführungsumgebung (virtueller Adressraum, Rechte, Priorität,...)

Mehr

U6-1 Organisatories. U6-2 Motivation von Threads. U6-3 Vergleich von Thread-Konzepten. Organisatorisches

U6-1 Organisatories. U6-2 Motivation von Threads. U6-3 Vergleich von Thread-Konzepten. Organisatorisches U6 6. Übung U6 6. Übung U6-1 Organisatories U6-1 Organisatories Organisatorisches Zusätzliche Tafelübung zur S1-Klaurvorbereitung Besprechung Aufgabe 5 (crawl) OSIX-Threads Motivation Thread-Konzepte am

Mehr

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Wolfram Burgard

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Wolfram Burgard Systeme I: Betriebssysteme Kapitel 4 Prozesse Wolfram Burgard Version 18.11.2015 1 Inhalt Vorlesung Aufbau einfacher Rechner Überblick: Aufgabe, Historische Entwicklung, unterschiedliche Arten von Betriebssystemen

Mehr

PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN

PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN 2. UNIX/Linux-Prozessverwaltung und zugehörige Systemaufrufe Wintersemester 2016/17 2. Die UNIX/LINUX-Prozessverwaltung Aufgaben: 1. Erzeugen neuer Prozesse

Mehr

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Maren Bennewitz

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Maren Bennewitz Systeme I: Betriebssysteme Kapitel 4 Prozesse Maren Bennewitz Version 21.11.2012 1 Begrüßung Heute ist Tag der offenen Tür Willkommen allen Schülerinnen und Schülern! 2 Testat nach Weihnachten Mittwoch

Mehr

Besprechung Aufgabe 5 (crawl) POSIX-Threads. Problem: UNIX-Prozesskonzept ist für viele heutige Anwendungen unzureichend

Besprechung Aufgabe 5 (crawl) POSIX-Threads. Problem: UNIX-Prozesskonzept ist für viele heutige Anwendungen unzureichend U7 6. Übung U7 6. Übung U7-1 Motivation von Threads U7-1 Motivation von Threads Besprechung Aufgabe 5 (crawl) OSIX-Threads Motivation Thread-Konzepte pthread-ai Koordinierung UNIX-rozesskonzept: eine Ausführungsumgebung

Mehr

Übungspaket 29 Dynamische Speicherverwaltung: malloc() und free()

Übungspaket 29 Dynamische Speicherverwaltung: malloc() und free() Übungspaket 29 Dynamische Speicherverwaltung malloc() und free() Übungsziele Skript In diesem Übungspaket üben wir das dynamische Alloziieren 1. und Freigeben von Speicherbereichen 2. von Zeichenketten

Mehr

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften Programmiertechnik Teil 4 C++ Funktionen: Prototypen Overloading Parameter C++ Funktionen: Eigenschaften Funktionen (Unterprogramme, Prozeduren) fassen Folgen von Anweisungen zusammen, die immer wieder

Mehr

Inhaltsverzeichnis. Carsten Vogt. Nebenläufige Programmierung. Ein Arbeitsbuch mit UNIX/Linux und Java ISBN:

Inhaltsverzeichnis. Carsten Vogt. Nebenläufige Programmierung. Ein Arbeitsbuch mit UNIX/Linux und Java ISBN: Inhaltsverzeichnis Carsten Vogt Nebenläufige Programmierung Ein Arbeitsbuch mit UNIX/Linux und Java ISBN: 978-3-446-42755-6 Weitere Informationen oder Bestellungen unter http://www.hanser.de/978-3-446-42755-6

Mehr

Betriebssysteme. Vorlesung im Herbstsemester 2010 Universität Mannheim. Kapitel 6: Speicherbasierte Prozessinteraktion

Betriebssysteme. Vorlesung im Herbstsemester 2010 Universität Mannheim. Kapitel 6: Speicherbasierte Prozessinteraktion Betriebssysteme Vorlesung im Herbstsemester 2010 Universität Mannheim Kapitel 6: Speicherbasierte Prozessinteraktion Felix C. Freiling Lehrstuhl für Praktische Informatik 1 Universität Mannheim Vorlesung

Mehr

e) Welche Aussage zu Speicherzuteilungsverfahren ist falsch?

e) Welche Aussage zu Speicherzuteilungsverfahren ist falsch? Aufgabe 1: (1) Bei den Multiple-Choice-Fragen ist jeweils nur eine richtige Antwort eindeutig anzukreuzen. Auf die richtige Antwort gibt es die angegebene Punktzahl. Wollen Sie eine Multiple-Choice-Antwort

Mehr

Parallele und verteilte Anwendungen in Java

Parallele und verteilte Anwendungen in Java Rainer Oechsle Parallele und verteilte Anwendungen in Java ISBN-10: 3-446-40714-6 ISBN-13: 978-3-446-40714-5 Leseprobe Weitere Informationen oder Bestellungen unter http://www.hanser.de/978-3-446-40714-5

Mehr

Einführung in die Programmiersprache C

Einführung in die Programmiersprache C Einführung in die Programmiersprache C 4 Storage classes Alexander Sczyrba Robert Homann Georg Sauthoff Universität Bielefeld, Technische Fakultät Compilation units Compilierung eines mehrteiligen Programms:

Mehr

Systemprogrammierung

Systemprogrammierung Systemprogrammierung 3Vom C-Programm zum laufenden Prozess 6. November 2008 Jürgen Kleinöder Universität Erlangen-Nürnberg Informatik 4, 2008 SS 2006 SOS 1 (03-Pro.fm 2008-11-06 08.52) 3 Vom C-Programm

Mehr

Shared-Memory Programmiermodelle

Shared-Memory Programmiermodelle Shared-Memory Programmiermodelle mehrere, unabhängige Programmsegmente greifen direkt auf gemeinsame Variablen ( shared variables ) zu Prozeßmodell gemäß fork/join Prinzip, z.b. in Unix: fork: Erzeugung

Mehr

Was ist ein Prozess?

Was ist ein Prozess? Prozesse unter UNIX Definition Was ist ein Prozess? Zeitliche Abfolge von Aktionen Ein Programm, das ausgeführt wird Prozesshierachie Baumstruktur INIT-Prozess ist die Wurzel (pid=1) und wird beim Booten

Mehr

Betriebssysteme G: Parallele Prozesse (Teil A: Grundlagen)

Betriebssysteme G: Parallele Prozesse (Teil A: Grundlagen) Betriebssysteme G: Parallele Prozesse (Teil A: Grundlagen) 1 Prozesse Bei Betriebssystemen stoßen wir des öfteren auf den Begriff Prozess als wahrscheinlich am häufigsten verwendeter und am unklarsten

Mehr

Einführung in die Programmiersprache C

Einführung in die Programmiersprache C Einführung in die Programmiersprache C 4 Storage classes Alexander Sczyrba Robert Homann Georg Sauthoff Universität Bielefeld, Technische Fakultät Compilation units Compilierung eines mehrteiligen Programms:

Mehr

2Binden 3. und Bibliotheken

2Binden 3. und Bibliotheken 3 Vom C-Programm zum laufenden Prozess 3.1 Übersetzen - Objektmodule 1Übersetzen 3. - Objektmodule (2) Teil III 3Vom C-Programm zum laufenden Prozess 2. Schritt: Compilieren übersetzt C-Code in Assembler

Mehr

Methoden. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom

Methoden. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom Einstieg in die Informatik mit Java, Vorlesung vom 2.5.07 Übersicht 1 2 definition 3 Parameterübergabe, aufruf 4 Referenztypen bei 5 Überladen von 6 Hauptprogrammparameter 7 Rekursion bilden das Analogon

Mehr

Parallele Prozesse. Prozeß wartet

Parallele Prozesse. Prozeß wartet Parallele Prozesse B-66 Prozeß: Ausführung eines Programmes in seinem Adressraum (zugeordneter Speicher) Parallele Prozesse: gleichzeitig auf mehreren Prozessoren laufende Prozesse p1 p2 verzahnte Prozesse:

Mehr

Threads. Netzwerk - Programmierung. Alexander Sczyrba Jan Krüger

Threads. Netzwerk - Programmierung. Alexander Sczyrba Jan Krüger Netzwerk - Programmierung Threads Alexander Sczyrba asczyrba@cebitec.uni-bielefeld.de Jan Krüger jkrueger@cebitec.uni-bielefeld.de Übersicht Probleme mit fork Threads Perl threads API Shared Data Mutexes

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java 1 / 26 Einstieg in die Informatik mit Java Methoden Gerd Bohlender Institut für Angewandte und Numerische Mathematik Gliederung 2 / 26 1 Methoden 2 Methodendefinition 3 Parameterübergabe, Methodenaufruf

Mehr

Dr. Monika Meiler. Inhalt

Dr. Monika Meiler. Inhalt Inhalt 15 Parallele Programmierung... 15-2 15.1 Die Klasse java.lang.thread... 15-2 15.2 Beispiel 0-1-Printer als Thread... 15-3 15.3 Das Interface java.lang.runnable... 15-4 15.4 Beispiel 0-1-Printer

Mehr

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen Heap vs. vs. statisch Der Speicher des Programms ist in verschiedene Speicherbereiche untergliedert Speicherbereiche, die den eigentlichen Programmcode und den Code der Laufzeitbibliothek enthalten; einen

Mehr

Informatik 12 Kapitel 2 - Kommunikation und Synchronisation von Prozessen

Informatik 12 Kapitel 2 - Kommunikation und Synchronisation von Prozessen Fachschaft Informatik Informatik 12 Kapitel 2 - Kommunikation und Synchronisation von Prozessen Michael Steinhuber König-Karlmann-Gymnasium Altötting 9. Februar 2017 Folie 1/40 Inhaltsverzeichnis I 1 Kommunikation

Mehr

Verteilte Systeme CS5001

Verteilte Systeme CS5001 Verteilte Systeme CS5001 Th. Letschert TH Mittelhessen Gießen University of Applied Sciences Client-Server-Anwendungen: Vom passiven (shared state) Monitor zum aktiven Monitor Monitor (Hoare, Brinch-Hansen,

Mehr

Programmierung mit C Zeiger

Programmierung mit C Zeiger Programmierung mit C Zeiger Zeiger (Pointer)... ist eine Variable, die die Adresse eines Speicherbereichs enthält. Der Speicherbereich kann... kann den Wert einer Variablen enthalten oder... dynamisch

Mehr

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen Heap vs. vs. statisch Der Speicher des Programms ist in verschiedene Speicherbereiche untergliedert Speicherbereiche, die den eigentlichen Programmcode und den Code der Laufzeitbibliothek enthalten; einen

Mehr

Lösungsvorschlag Serie 2 Rekursion

Lösungsvorschlag Serie 2 Rekursion (/) Lösungsvorschlag Serie Rekursion. Algorithmen-Paradigmen Es gibt verschiedene Algorithmen-Paradigmen, also grundsätzliche Arten, wie man einen Algorithmus formulieren kann. Im funktionalen Paradigma

Mehr

Sequentielle Programm- / Funktionsausführung innerhalb eines Prozesses ( thread = Ausführungsfaden )

Sequentielle Programm- / Funktionsausführung innerhalb eines Prozesses ( thread = Ausführungsfaden ) Threads Sequentielle Programm- / Funktionsausführung innerhalb eines Prozesses ( thread = Ausführungsfaden ) Ein thread bearbeitet eine sequentielle Teilaufgabe innerhalb eines Prozesses Mehrere nebenläufige

Mehr

6 Speicherorganisation

6 Speicherorganisation Der Speicher des Programms ist in verschiedene Speicherbereiche untergliedert Speicherbereiche, die den eigentlichen Programmcode und den Code der Laufzeitbibliothek enthalten; einen Speicherbereich für

Mehr

Konzepte der Programmiersprachen

Konzepte der Programmiersprachen Konzepte der Programmiersprachen Sommersemester 2010 4. Übungsblatt Besprechung am 9. Juli 2010 http://www.iste.uni-stuttgart.de/ps/lehre/ss2010/v_konzepte/ Aufgabe 4.1: Klassen in C ++ Das folgende C

Mehr

Echtzeitbetriebssysteme (am Beispiel QNX) Dr. Stefan Enderle HS Esslingen

Echtzeitbetriebssysteme (am Beispiel QNX) Dr. Stefan Enderle HS Esslingen Echtzeitbetriebssysteme (am Beispiel QNX) Dr. Stefan Enderle HS Esslingen 3. QNX Neutrino Microkernel 3.1 Einführung Der Neutrino-Kernel (Betriebssystemkern) implementiert (nur) die wichtigsten Teile des

Mehr

Parallele Prozesse Prozeß Parallele Prozesse verzahnte Prozesse Nebenläufige Prozesse: Threads Vorlesung Software-Entwicklung / Folie 131 Ziele:

Parallele Prozesse Prozeß Parallele Prozesse verzahnte Prozesse Nebenläufige Prozesse: Threads Vorlesung Software-Entwicklung / Folie 131 Ziele: Parallele Prozesse SWE-131 Prozeß: Ausführung eines sequentiellen Programmstückes in dem zugeordneten Speicher (Adressraum). Veränderlicher Zustand: Speicherinhalt und Programmposition. Parallele Prozesse:

Mehr

Prof. Dr. Th. Letschert CS5001. Verteilte Systeme. Master of Science (Informatik) - Einleitung - Th Letschert FH Gießen-Friedberg

Prof. Dr. Th. Letschert CS5001. Verteilte Systeme. Master of Science (Informatik) - Einleitung - Th Letschert FH Gießen-Friedberg Prof. Dr. Th. Letschert CS5001 Master of Science (Informatik) - - Th Letschert FH Gießen-Friedberg Modulnr.: CS5001 Verwendbar : Master of Science (Informatik) Master of Science (Wirtschaftsinformatik)

Mehr

Java Concurrency Utilities

Java Concurrency Utilities Java Concurrency Utilities Java unterstützt seit Java 1.0 Multithreading Java unterstützt das Monitorkonzept mittels der Schlüsselworte synchronized und volatile sowie den java.lang.object Methoden wait(),

Mehr

Nebenläufige Programmierung in Java: Threads

Nebenläufige Programmierung in Java: Threads Nebenläufige Programmierung in Java: Threads Wahlpflicht: Fortgeschrittene Programmierung in Java Jan Henke HAW Hamburg 10. Juni 2011 J. Henke (HAW) Threads 10. Juni 2011 1 / 18 Gliederung 1 Grundlagen

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java 1 / 16 Einstieg in die Informatik mit Java Innere Klassen Gerd Bohlender Institut für Angewandte und Numerische Mathematik Gliederung 2 / 16 1 Einführung 2 Element-Klassen 3 Lokale Klassen 4 Anonyme Klassen

Mehr

Algorithmen und Datenstrukturen

Algorithmen und Datenstrukturen Algorithmen und Datenstrukturen Dynamische Datenobjekte Pointer/Zeiger, Verkettete Liste Eigene Typdefinitionen 1 Zeigeroperatoren & und * Ein Zeiger ist die Speicheradresse irgendeines Objektes. Eine

Mehr

Aufgabe 9: recgrep rekursive, parallele Suche (letzte Aufgabe)

Aufgabe 9: recgrep rekursive, parallele Suche (letzte Aufgabe) U6 6. Übung (Mathematik / Technomathematik) U6 6. Übung (Mathematik / Technomathematik) Besprechung Aufgabe 4 (halde) Besprechung Aufgabe 5 (crawl) Besprechung der Mini-Klausur POSIX-Threads Motivation

Mehr

4 Threads. FH Regensburg BT/SS04 Betriebssysteme Wirtschaftsinformatik. 4.1 Allgemein

4 Threads. FH Regensburg BT/SS04 Betriebssysteme Wirtschaftsinformatik. 4.1 Allgemein 4 Threads 4.1 Allgemein Prozessmodell: Zwei unabhängige Eigenschaften eines Prozesses: Er hat Resourcen: Adressen, Daten, geöffnete Dateien und ist ausführbar Thread: hat keine eigenen Resourcen (ausser

Mehr

12 Abstrakte Klassen, finale Klassen und Interfaces

12 Abstrakte Klassen, finale Klassen und Interfaces 12 Abstrakte Klassen, finale Klassen und Interfaces Eine abstrakte Objekt-Methode ist eine Methode, für die keine Implementierung bereit gestellt wird. Eine Klasse, die abstrakte Objekt-Methoden enthält,

Mehr

Vorlesung Programmieren

Vorlesung Programmieren Vorlesung Programmieren Speicherverwaltung und Parameterübergabe Prof. Dr. Stefan Fischer Institut für Telematik, Universität zu Lübeck http://www.itm.uni-luebeck.de/people/fischer Gültigkeitsbereich von

Mehr

Speicher und Adressraum

Speicher und Adressraum Linearer Speicher (Adressraum) Technische Universität München Speicher und Adressraum Freie Speicherhalde (Heap) Freier Speicherstapel (Stack) Globale Variablen Bibliotheksfunktionen Laufzeitsystem Programmcode

Mehr

4.4 Imperative Algorithmen Prozeduren

4.4 Imperative Algorithmen Prozeduren 4.4.2 Prozeduren Der Wert eines Ausdrucks u in Zustand z Z lässt sich damit auch leicht definieren (jetzt W Z statt W σ ) Dazu erweitern wir die rekursive Definition von Folie 57 (Wert eines Ausdrucks):

Mehr

Repetitorium Informatik (Java)

Repetitorium Informatik (Java) Repetitorium Informatik (Java) Tag 6 Lehrstuhl für Informatik 2 (Programmiersysteme) Übersicht 1 Klassen und Objekte Objektorientierung Begrifflichkeiten Deklaration von Klassen Instanzmethoden/-variablen

Mehr

Nebenläufige Programmierung

Nebenläufige Programmierung Nebenläufige Programmierung Perspektiven der Informatik 27. Januar 2003 Gert Smolka Telefon-Szenario Eine Telefonzelle Mehrere Personen wollen telefonieren Immer nur eine Person kann telefonieren Ressource

Mehr

Einführung in C. EDV1-04C-Einführung 1

Einführung in C. EDV1-04C-Einführung 1 Einführung in C 1 Helmut Erlenkötter C Programmieren von Anfang an Rowohlt Taschenbuch Verlag ISBN 3-4993 499-60074-9 19,90 DM http://www.erlenkoetter.de Walter Herglotz Das Einsteigerseminar C++ bhv Verlags

Mehr

C# - Einführung in die Programmiersprache Methoden. Leibniz Universität IT Services

C# - Einführung in die Programmiersprache Methoden. Leibniz Universität IT Services C# - Einführung in die Programmiersprache Methoden Leibniz Universität IT Services 02.07.12 Methoden... sind Subroutinen in einer Klasse. können einen Wert an den Aufrufer zurückgeben. verändern die Eigenschaften

Mehr

Algorithmen & Programmierung. Rekursive Funktionen (1)

Algorithmen & Programmierung. Rekursive Funktionen (1) Algorithmen & Programmierung Rekursive Funktionen (1) Berechnung der Fakultät Fakultät Die Fakultät N! einer nichtnegativen ganzen Zahl N kann folgendermaßen definiert werden: d.h. zur Berechnung werden

Mehr

Dynamischer Speicher

Dynamischer Speicher Dynamischer Speicher C-Kurs 2012, 3. Vorlesung Tino Kutschbach tino.kutschbach@campus.tu-berlin.de http://wiki.freitagsrunde.org 13. September 2012 This work is licensed under the Creative Commons Attribution-ShareAlike

Mehr

Linux Prinzipien und Programmierung

Linux Prinzipien und Programmierung Linux Prinzipien und Programmierung Dr. Klaus Höppner Hochschule Darmstadt Wintersemester 2010/2011 1 / 18 2 / 18 fork und Daten Nach dem fork teilen sich Eltern- und Kindprozess zwar den Programmbereich

Mehr

OpenMP - Threading- Spracherweiterung für C/C++ Matthias Klein, Michael Pötz Systemprogrammierung 15. Juni 2009

OpenMP - Threading- Spracherweiterung für C/C++ Matthias Klein, Michael Pötz Systemprogrammierung 15. Juni 2009 - Threading- Spracherweiterung für C/C++ Matthias Klein, Michael Pötz Systemprogrammierung 15. Juni 2009 Grundlagen der Parallelen Programmierung Hardware Threads vs. Prozesse Kritische Abschnitte Lange

Mehr

1 Klassen und Objekte

1 Klassen und Objekte 1 Klassen und Objekte Datentyp - Spezifikation des Typs von Datenobjekten Datenstruktur - logische Ordnung von Elementen eines Datentyps - zur (effizienten) Speicherung, Verwaltung, Zugriff - auf die Elemente

Mehr

C/C++-Programmierung

C/C++-Programmierung 1 C/C++-Programmierung Speicherverwaltung, 0, const Sebastian Hack Christoph Mallon (hack mallon)@cs.uni-sb.de Fachbereich Informatik Universität des Saarlandes Wintersemester 2009/2010 2 Speicherverwaltung

Mehr

Beuth Hochschule Parameter-Übergabe-Mechanismen WS17/18, S. 1

Beuth Hochschule Parameter-Übergabe-Mechanismen WS17/18, S. 1 Beuth Hochschule Parameter-Übergabe-Mechanismen WS17/18, S. 1 Parameter-Übergabe-Mechanismen in Java und in anderen Sprachen. 1. Methoden vereinbaren mit Parametern Wenn man (z.b. in Java) eine Methode

Mehr

Probeklausur: Programmierung WS04/05

Probeklausur: Programmierung WS04/05 Probeklausur: Programmierung WS04/05 Name: Hinweise zur Bearbeitung Nimm Dir für diese Klausur ausreichend Zeit, und sorge dafür, dass Du nicht gestört wirst. Die Klausur ist für 90 Minuten angesetzt,

Mehr

Einführung. Anwendung. logischer Adreßraum. Kontrollfluß (Thread) = CPU führt Instruktionen aus. Was charakterisiert einen Kontrollfluß?

Einführung. Anwendung. logischer Adreßraum. Kontrollfluß (Thread) = CPU führt Instruktionen aus. Was charakterisiert einen Kontrollfluß? Kontrollflüsse Einführung 1 Motivation Kontrollfluß Anwendung logischer Adreßraum Kontrollfluß (Thread) = führt Instruktionen aus Was charakterisiert einen Kontrollfluß? Programmzähler Registerinhalte

Mehr

RTEMS- Echtzeitbetriebssystem

RTEMS- Echtzeitbetriebssystem RTEMS- Echtzeitbetriebssystem Name: Hussein Hammoud Matrikel- Nr.: 230768 Studiengang: Technische Informatik Fach: Projekt Eingebettete Kommunikation Technische Universität Berlin Sommersemester 2006 RTEMS-

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java 1 / 25 Einstieg in die Informatik mit Java Objektorientierte Programmierung und Klassen Gerd Bohlender Institut für Angewandte und Numerische Mathematik Gliederung 2 / 25 1 Die Philosophie 2 Definition

Mehr

Felder, Zeiger und Adreßrechnung

Felder, Zeiger und Adreßrechnung Felder, Zeiger und Adreßrechnung Felder bestehen aus Variablen eines einzigen Datentyps. Bisher kennen wir eindimensionale Felder. In C sind Felder mit beliebigen Dimensionen möglich. Unsere räumliche

Mehr

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java 1 / 35 Einstieg in die Informatik mit Java Vererbung Gerd Bohlender Institut für Angewandte und Numerische Mathematik Gliederung 2 / 35 1 Grundlagen 2 Verdeckte Variablen 3 Verdeckte Methoden 4 Konstruktoren

Mehr

Speicherklassen (1) Lokale Variablen

Speicherklassen (1) Lokale Variablen Speicherklassen (1) Lokale Variablen Lokale Variablen beschränken sich auf die Funktionen, in denen sie definiert werden Sind in der Funktion gekapselt können also nur in der Funktion verändert werden

Mehr

Unterlagen. CPP-Uebungen-08/

Unterlagen.  CPP-Uebungen-08/ Unterlagen http://projects.eml.org/bcb/people/ralph/ CPP-Uebungen-08/ http://www.katjawegner.de/lectures.html Kommentare in C++ #include /* Dies ist ein langer Kommentar, der über zwei Zeilen

Mehr

Beispiel für einen IPC-Server, der seinen Dienst über den Global Name Service im Netzwerk bekannt gibt. Header-Dateien einbinden

Beispiel für einen IPC-Server, der seinen Dienst über den Global Name Service im Netzwerk bekannt gibt. Header-Dateien einbinden Dokument: gns_ipc_server.c, 1 - Seite 1 - - 1: 1 von 1 - Beispiel für einen IPC-Server, der seinen Dienst über den Global Name Service im Netzwerk bekannt gibt. Header-Dateien einbinden int main(int argc,

Mehr

Was Mathematiker schon vor Jahrhunderten erfunden haben, gibt es jetzt endlich in ihrer Programmiersprache:

Was Mathematiker schon vor Jahrhunderten erfunden haben, gibt es jetzt endlich in ihrer Programmiersprache: Kapitel 8 Operatoren Was Mathematiker schon vor Jahrhunderten erfunden haben, gibt es jetzt endlich in ihrer Programmiersprache: Operatoren definieren Es ist in C++ möglich, Operatoren wie +, oder für

Mehr

PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN

PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN 2. UNIX/Linux-Prozessverwaltung und zugehörige Systemaufrufe Wintersemester 2015/16 2. Die UNIX/LINUX-Prozessverwaltung Aufgaben: 1. Erzeugen neuer Prozesse

Mehr

Algorithmen und Datenstrukturen

Algorithmen und Datenstrukturen Algorithmen und Datenstrukturen Dipl. Inform. Andreas Wilkens aw@awilkens.com Überblick Grundlagen Definitionen Eigene Entwicklungen Datenstrukturen Elementare Datentypen Abstrakte Datentypen Elementare

Mehr

Innere Klassen. Gerd Bohlender. Institut für Angewandte und Numerische Mathematik. Vorlesung: Einstieg in die Informatik mit Java

Innere Klassen. Gerd Bohlender. Institut für Angewandte und Numerische Mathematik. Vorlesung: Einstieg in die Informatik mit Java Innere Klassen Gerd Bohlender Institut für Angewandte und Numerische Mathematik Vorlesung: Einstieg in die Informatik mit Java 13.06.07 G. Bohlender (IANM UNI Karlsruhe) Innere Klassen 13.06.07 1 / 11

Mehr

Teil 3: Konzepte von Betriebssystemen

Teil 3: Konzepte von Betriebssystemen Teil 3: Konzepte von Betriebssystemen Inhalt: Einführung Prozesse Speicherverwaltung Virtueller Speicher 1 Definition eines Betriebssystems Was ist ein Betriebssystem? einfache Definition: Als Betriebssystem

Mehr

Probeklausur Programmieren in C Sommersemester 2007 Dipl. Biol. Franz Schenk 12. April 2007, Uhr Bearbeitungszeit: 105 Minuten

Probeklausur Programmieren in C Sommersemester 2007 Dipl. Biol. Franz Schenk 12. April 2007, Uhr Bearbeitungszeit: 105 Minuten Probeklausur Programmieren in C Sommersemester 2007 Dipl. Biol. Franz Schenk 12. April 2007, 13.00-14.45 Uhr Bearbeitungszeit: 105 Minuten Schalten Sie ihr Mobiltelefon aus. Bei der Klausur ist als einziges

Mehr

Algorithmen implementieren. Implementieren von Algorithmen

Algorithmen implementieren. Implementieren von Algorithmen Algorithmen implementieren Implementieren von Algorithmen Um Algorithmen ablaufen zu lassen, muss man sie als Programm darstellen (d.h. implementieren) Wie stellt man die algorithmischen Strukturelemente

Mehr

Übung zu Grundlagen der Betriebssysteme. 10. Übung 18.12.2012

Übung zu Grundlagen der Betriebssysteme. 10. Übung 18.12.2012 Übung zu Grundlagen der Betriebssysteme 10. Übung 18.12.2012 Aufgabe 1 a) Was versteht man unter einem kritischen Abschnitt oder kritischen Gebiet (critical area)? b) Welche Aufgabe hat ein Semaphor? c)

Mehr

2 Eine einfache Programmiersprache

2 Eine einfache Programmiersprache 2 Eine einfache Programmiersprache Eine Programmiersprache soll Datenstrukturen anbieten Operationen auf Daten erlauben Kontrollstrukturen zur Ablaufsteuerung bereitstellen Als Beispiel betrachten wir

Mehr

Das diesem Dokument zugrundeliegende Vorhaben wurde mit Mitteln des Bundesministeriums für Bildung und Forschung unter dem Förderkennzeichen

Das diesem Dokument zugrundeliegende Vorhaben wurde mit Mitteln des Bundesministeriums für Bildung und Forschung unter dem Förderkennzeichen Das diesem Dokument zugrundeliegende Vorhaben wurde mit Mitteln des Bundesministeriums für Bildung und Forschung unter dem Förderkennzeichen 16OH21005 gefördert. Die Verantwortung für den Inhalt dieser

Mehr

C++ - Objektorientierte Programmierung Konstante und statische Elemente

C++ - Objektorientierte Programmierung Konstante und statische Elemente C++ - Objektorientierte Programmierung Konstante und statische Elemente hat eine Kantenlänge hat eine Füllfarbe Kantenlänge setzen Füllfarbe lesen Volumen berechnen Leibniz Universität IT Services Anja

Mehr

7 Funktionen. 7.1 Definition. Prototyp-Syntax: {Speicherklasse} {Typ} Name ({formale Parameter});

7 Funktionen. 7.1 Definition. Prototyp-Syntax: {Speicherklasse} {Typ} Name ({formale Parameter}); S. d. I.: Programieren in C Folie 7-1 7 Funktionen 7.1 Definition Prototyp-Syntax: Speicherklasse Typ Name (formale Parameter); der Funktions-Prototyp deklariert eine Funktion, d.h. er enthält noch nicht

Mehr

Vorlesung Informatik II

Vorlesung Informatik II Vorlesung Informatik II Universität Augsburg Wintersemester 2011/2012 Prof. Dr. Bernhard Bauer Folien von: Prof. Dr. Robert Lorenz Lehrprofessur für Informatik 17. JAVA Kommunikation von Threads 1 Motivation

Mehr

S. d. I.: Programieren in C Folie 4-1. im Gegensatz zu Pascal gibt es in C kein Schlüsselwort "then"

S. d. I.: Programieren in C Folie 4-1. im Gegensatz zu Pascal gibt es in C kein Schlüsselwort then S. d. I.: Programieren in C Folie 4-1 4 Anweisungen 4.1 if-anweisung 1) if (Ausdruck) 2) if (Ausdruck) } else im Gegensatz zu Pascal gibt es in C kein Schlüsselwort "then" es wird nur der numerische Wert

Mehr

Betriebssysteme, Rechnernetze und verteilte Systeme 1. Crashkurs C (2)

Betriebssysteme, Rechnernetze und verteilte Systeme 1. Crashkurs C (2) Betriebssysteme, Rechnernetze und verteilte Systeme 1 Crashkurs C (2) Olaf Spinczyk Arbeitsgruppe Eingebettete Systemsoftware Lehrstuhl für Informatik 12 TU Dortmund olaf.spinczyk@tu-dortmund.de http://ess.cs.uni-dortmund.de/teaching/ss2008/bsrvs1/

Mehr

einlesen n > 0? Ausgabe Negative Zahl

einlesen n > 0? Ausgabe Negative Zahl 1 Lösungen Kapitel 1 Aufgabe 1.1: Nassi-Shneiderman-Diagramm quadratzahlen Vervollständigen Sie das unten angegebene Nassi-Shneiderman-Diagramm für ein Programm, welches in einer (äußeren) Schleife Integer-Zahlen

Mehr

C++ - Objektorientierte Programmierung Konstruktoren und Destruktoren

C++ - Objektorientierte Programmierung Konstruktoren und Destruktoren C++ - Objektorientierte Programmierung Konstruktoren und Destruktoren hat eine Kantenlänge hat eine Füllfarbe Kantenlänge setzen Füllfarbe lesen Volumen berechnen Leibniz Universität IT Services Anja Aue

Mehr

Parallele Programmiermodelle

Parallele Programmiermodelle Parallele Programmiermodelle ProSeminar: Parallele Programmierung Semester: WS 2012/2013 Dozentin: Margarita Esponda Einleitung - Kurzer Rückblick Flynn'sche Klassifikationsschemata Unterteilung nach Speicherorganissation

Mehr

Prozesse. Prozesse sind Programme. Prozesse können aus Unterprozessen bestehen. Prozesshierarchie Unterprozesse Threads

Prozesse. Prozesse sind Programme. Prozesse können aus Unterprozessen bestehen. Prozesshierarchie Unterprozesse Threads Threads Prozesse, Parallelität, Nebenläufigkeit, Threads, Erzeugung, Ausführung, Kommunikation, Interferenz, Kritischer Bereich, Deadlock, Synchronisation. Prozesse Prozesse sind Programme mehrere Prozesse

Mehr

DieÜbersetzung funktionaler Programmiersprachen

DieÜbersetzung funktionaler Programmiersprachen DieÜbersetzung funktionaler Programmiersprachen 107 11 Die Sprache PuF Wir betrachten hier nur die Mini-Sprache PuF( Pure Functions ). Insbesondere verzichten wir(vorerst) auf: Seiteneffekte; Datenstrukturen;

Mehr

VORSTELLUNG DER DIPLOMARBEIT

VORSTELLUNG DER DIPLOMARBEIT 1 VORSTELLUNG DER DIPLOMARBEIT Thomas Werner Inhaltsverzeichnis 2 Thema Aufgabenstellung Anwendungsdebugging Threads Remote Debugging Implementierung Ausblick Quellen 3 Thema Untersuchung von Funktionsabläufen

Mehr

Grundlagen und Konzepte von C Datenstrukturen

Grundlagen und Konzepte von C Datenstrukturen Grundlagen und Konzepte von C Datenstrukturen Ausarbeitung von Andreas Gadelmaier Proseminar C Grundlagen und Konzepte Arbeitsbereich Wissenschaftliches Rechnen Fachbereich Informatik Fakultät für Mathematik,

Mehr

Typ : void* aktuelle Parameter Pointer von beliebigem Typ

Typ : void* aktuelle Parameter Pointer von beliebigem Typ 2. Funktionen - Prototypvereinbarung typangabe funktionsname(parameterliste); - Funktionsdefinition typ funktionsname(parameterliste){ Anweisung - Funktionstyp -> Typ der Funktionswertes zulaessige Typangaben

Mehr

Verteilte Systeme - Java Networking (Sockets) 2 -

Verteilte Systeme - Java Networking (Sockets) 2 - Verteilte Systeme - Java Networking (Sockets) 2 - Prof. Dr. Michael Cebulla 06. November 2014 Fachhochschule Schmalkalden Wintersemester 2014/15 1 / 30 Michael Cebulla Verteilte Systeme Gliederung Wiederholung:

Mehr

Konzepte von Betriebssystemkomponenten Referat am Thema: Adressräume, Page Faults, Demand Paging, Copy on Write Referent: Johannes Werner

Konzepte von Betriebssystemkomponenten Referat am Thema: Adressräume, Page Faults, Demand Paging, Copy on Write Referent: Johannes Werner Konzepte von Betriebssystemkomponenten Referat am 24.11.2003 Thema: Adressräume, Page Faults, Demand Paging, Copy on Write Referent: Johannes Werner Gliederung Adressräume Page Faults Demand Paging Copy

Mehr

Informatik II Übung 05. Benjamin Hepp 3 April 2017

Informatik II Übung 05. Benjamin Hepp 3 April 2017 Informatik II Übung 05 Benjamin Hepp benjamin.hepp@inf.ethz.ch 3 April 2017 Java package Hierarchie import.. nur noetig um Klassen aus anderen Packeten zu importieren Es kann auch immer der vollstaendige

Mehr

Schlussendlich geben wir die Listen aus. Es kommt zu folgender Ausgabe:

Schlussendlich geben wir die Listen aus. Es kommt zu folgender Ausgabe: Musterlösung Übung 7 Aufgabe 1 Sehen wir uns zu allererst das gegebene Forth Programm an: 0 3 new - list constant list1 list1 5 new - list constant list2 list1 6 new - list constant list3 list2 2 new -

Mehr

Berichte aus der Informatik. Dieter Pawelczak. Start in die C-Programmierung

Berichte aus der Informatik. Dieter Pawelczak. Start in die C-Programmierung Berichte aus der Informatik Dieter Pawelczak Start in die C-Programmierung Shaker Verlag Aachen 2012 Inhaltsverzeichnis Inhaltsverzeichnis i 1 Einleitung 1 1.1 Umfeld und Aufbau des Buches 1 Die Programmiersprache

Mehr

Einführung in die Programmierung II. 5. Zeiger

Einführung in die Programmierung II. 5. Zeiger Einführung in die Programmierung II 5. Zeiger Thomas Huckle, Stefan Zimmer 16. 5. 2007-1- Bezüge als Objekte Bisher kennen wir als Bezüge (Lvalues) nur Variablennamen Jetzt kommt eine neue Sorte dazu,

Mehr