Harte und weiche Echtzeitsysteme

Größe: px
Ab Seite anzeigen:

Download "Harte und weiche Echtzeitsysteme"

Transkript

1 Harte und weiche Echtzeitsysteme Material zur Vorlesung Echtzeitsysteme an der Hochschule Niederrhein. Jürgen Quade

2 Harte und weiche Echtzeitsysteme Material zur Vorlesung Echtzeitsysteme an der Hochschule Niederrhein. von Jürgen Quade V4.1, Mo 19. Sep 16:00:56 CEST 2011 Dieses Buch darf unter der Bedingung, dass die eingetragenen Copyright-Vermerke nicht modifiziert werden, kopiert und weiterverteilt werden. Die kommerzielle Nutzung (Weiterverkauf) ist ohne Zustimmung des Autors bislang nicht erlaubt. Da auch die Quellen des Buches zur Verfügung stehen, ist die Modifikation, Erweiterung und Verbesserung durch andere Autoren möglich und sogar erwünscht. $Id: ezsi.sgml,v /09/19 13:58:30 quade Exp $ $Id: legalnotice.sgml,v /01/04 21:44:42 quade Exp $ $Id: literatur.sgml,v /02/08 13:38:08 quade Exp $ $Id: architecture.sgml,v /09/18 16:42:43 quade Exp $ $Id: definitionen.sgml,v /02/08 14:49:23 quade Exp $ $Id: prozess.sgml,v /08/10 08:30:43 quade Exp $ $Id: realtime.sgml,v /09/13 19:23:44 quade Exp $ $Id: os.sgml,v /09/19 12:55:49 quade Exp $ $Id: feldbus.sgml,v /08/10 08:30:43 quade Exp $ $Id: Multiplex.sgml,v /01/04 21:44:42 quade Exp $ $Id: sicherheit.sgml,v /09/19 13:16:11 quade Exp $ $Id: verfuegbar.sgml,v /02/27 19:14:48 quade Exp $ $Id: algorithm.sgml,v /08/10 08:30:43 quade Exp $ $Id: entwicklung.sgml,v /08/10 08:30:43 quade Exp $ $Id: distributed.sgml,v /08/10 08:30:43 quade Exp $ $Id: petri.sgml,v /09/19 13:18:31 quade Exp $ $Id: socket.sgml,v /08/10 08:30:43 quade Exp $ $Id: IoSubSystem.sgml,v /01/31 14:58:56 quade Exp $ $Id: ir.sgml,v /08/10 08:30:43 quade Exp $ Versionsgeschichte Version 4.1 $Date: 2011/09/19 13:58:30 $ Geändert durch: $Author: quade $

3 Inhaltsverzeichnis 1. Überblick Definitionen Prozesse Technischer Prozess Rechenprozess Steuerung Kognitiver Prozess Kennzeichen von Realzeitsystemen Ausprägungen von Realzeitsystemen Realzeitsystem-Komponenten Realzeitbetrieb Echtzeitbedingungen Auslastung Pünktlichkeit Harte und weiche Echtzeit Latenzzeiten Unterbrechbarkeit und Prioritäten Realzeitbetriebssysteme Aufbau und Struktur Betriebssystemkern Prozess-Management Scheduling auf Einprozessormaschinen Scheduling Points Bewertungskritierien First Come First Serve (FCFS) Round Robin Scheduling Prioritätengesteuertes Scheduling Deadline-Scheduling POSIX b Scheduling in VxWorks Der O(1) Scheduler im Linux-Kernel Scheduling auf Mehrprozessormaschinen Memory-Management Segmentierung Seitenorganisation I/O Subsystem Gerätezugriff auf Applikationsebene Schnittstellenfunktionen Zugriffsarten Gerätezugriff innerhalb des Betriebssystemkerns Filesystem Systemcall-Interface Services Bibliotheken Zeitaspekte Interrupt- und Task-Latenzzeiten Unterbrechungsmodell Timing (Zeitverwaltung)...61 iii

4 4. Aspekte der nebenläufigen Echtzeit-Programmierung Taskmanagement Tasks erzeugen Tasks beenden Tasks parametrieren Schutz kritischer Abschnitte Semaphor und Mutex Prioritätsinversion Deadlock Schreib-/Lese-Locks Weitere Schutzmaßnahmen für kritische Abschnitte Programmtechnischer Umgang mit Zeiten Zeit lesen Der Zeitvergleich Differenzzeitmessung Schlafen Weckrufe per Timer Inter-Prozess-Kommunikation Pipes Shared-Memory Sockets Condition-Variable (Event) Signals Peripheriezugriff Echtzeitarchitekturen Applikationen im Kernel Mehrkernmaschine Mehrkernelansatz Echtzeitsysteme in sicherheitskritischen Anwendungen Begriffsbestimmung Mathematische Grundlagen Redundante Systeme Weitere Maßnahmen zur Zuverlässigkeitssteigerung Formale Beschreibungsmethoden Daten- und Kontrollflussdiagramme Darstellung von Programmabläufen mit Hilfe von Struktogrammen Petrinetze Echtzeitnachweis Worst Case Betrachtungen Abschätzung der Worst Case Execution Time (WCET) Abschätzung der Best Case Execution Time (BCET) Echtzeitnachweis bei prioritätengesteuertem Scheduling Echtzeitnachweis bei Deadline-Scheduling Ereignisstrom-Modell Behandlung abhängiger Ereignisse Literatur Stichwortverzeichnis iv

5 Tabellenverzeichnis 3-1. Vergleich Task und Thread Unterschiede zwischen einem Signal und einer Condition Variablen...90 v

6 Kapitel 1. Überblick Das Feld der Echtzeitsysteme erfährt gegenwärtig große Aufmerksamkeit. Das liegt nicht zuletzt daran, dass die durch die fortschreitende Minituarisierung und Leistungssteigerung der Hardware auf der einen Seite und dem damit verbundenen Eizug von Standardsoftware allen voran Linux auf der anderen Seite ganz neue Möglichkeiten und or allem auch verkürzte Entwicklungszeiten ergeben. Entsprechend gibt es zur Zeit auh tatsächlich ständig Neuentwicklungen auf dem Gebiet der Echtzeitsytseme, die dem Ingenieur oder Informatiker neue Möglichkeiten zur Realisierung von Systemen mit harter Realzeitanforderungen geben. Als Beispiel seien hier die Mehrkernsysteme (SMP) erwähnt, bei denen der Systemarchitekt durch eine geschickte Verteilung von Realzeitaufgaben und nicht Realzeitaufgaben auf die unterscheidlichen CPU- Kerne für die Einhaltung der Echtzeitbedingungen osrgen kann. Bei dem vorliegenden Skript handelt es sich um das Begleitmaterial zur Vorlesung Echtzeitsysteme an der Hochschule Niederrhein. Ziel der Vorlesung ist dabei die Vermittlung notwendigen Wissens und der notwendigen Technologie, um Realzeitsysteme konzipieren, realisieren und bewerten zu können. Die Vorlesung wurde seit dem Wintersemester 1999/2000 im Diplomstudiengang Informatik, seit dem Wintersemester 2008/2009 in den Bachelorstudiengängen Informatik und Elektrotechnik in gekürzter Form abgehalten. Eine Überarbeitung fand zum Wintersemester 2011/2012 statt Definitionen Realzeit- oder Echtzeitsysteme sind Systeme, bei denen neben den funktionalen Anforderungen noch Anforderungen zeitlicher Natur gestellt werden. Die durch den Rechner durchgeführten Berechnungen müssen damit nicht nur korrekt, sondern zudem auch zum richtigen Zeitpunkt erfolgen (logische und temporale Korrektheit). Realzeit-Steuerungssysteme bestehen aus dem technischen Prozess, den Rechenprozessen und kognitiven Prozessen. Im folgenden sollen diese Begriffe genauer erläutert werden Prozesse Ein Prozess ist nach [DIN66201] eine "Gesamtheit von aufeinander einwirkenden Vorgängen in einem System, durch die Materie, Energie oder Information umgeformt, transportiert oder gespeichert wird". Man spricht von technischen Prozessen, wenn Material oder Energie umgeformt, transportiert beziehungsweise gespeichert wird, und von Rechenprozessen bei der Umformung, beim Transport beziehungsweise bei der Speicherung von Information Technischer Prozess Ein technischer Prozess ist gekennzeichnet durch (physikalische) Zustandsgrößen. Viele dieser Zustandsgrößen können durch Sensoren (Meßwertaufnehmer) erfaßt und durch Aktoren (Stellglieder) beeinflußt werden. Typische Zustandsgrößen sind Temperatur, Druck, Feuchtigkeit oder Position. Beispiele für Sensoren: Endlagenschalter Temperaturmesser Winkelgeber Füllstandsmesser 1

7 Kapitel 1. Überblick Beispiele für Aktoren: Motoren Ventile Relais Technische Prozesse lassen sich weiter nach der Art der vorwiegend verarbeiteten Variablen klassifizieren [Lauber 76]. Diese Variablen können sein: 1. Physikalische Größen mit kontinuierlichem oder wenigstens stückweise kontinuierlichem Wertebereich. 2. Binäre Informationselemente, die Ereignissen zugeordnet werden können. 3. Informationselemente, die einzeln identifizierbaren Objekten zugeordnet werden können Rechenprozess Ein Rechenprozess ist der Umformung, Verarbeitung und des Transportes von Informationen gewidmet. Im Regelfall berechnet er aus Eingabewerten Ausgabewerte. Ein Rechenprozess wird durch ein Programm beschrieben. Ein Programm beschreibt die Abfolge der Befehle und ist damit zunächst statisch. Die Instanz eines Programmes (die dynamische Abarbeitung des Programms also) ist dann der Rechenprozess. Anstelle des Begriffes Rechenprozess wird oft auch der Begriff Task verwendet, insbesondere um eine Verwechselung mit dem technischen Prozess begrifflich auszuschließen. Moderne Rechnersysteme und vor allem auch Realzeitsysteme sind in der Lage, quasi parallel (in der Realität jedoch nur sequentiell) mehrere Rechenprozesse gleichzeitig zu bearbeiten (Multitasking) Steuerung Die Summe der Rechenprozesse und deren Ablaufumgebung (Hardware und Software) wird als Steuerung bezeichnet. Die Aufgabe der Steuerung ist: Erfassung der Zustandsgrößen des technischen Prozesses. Beeinflußung des technischen Prozesses. Koordination der Prozessabläufe. Überwachung der Prozessabläufe. Nach [DIN 19226] unterscheidet man zwischen einer Steuerung und einer Regelung je nachdem, ob ein geschlossener Regelkreis vorliegt oder nicht. Vielleicht weil im Englischen nur das Wort control existiert, wird im deutschen Sprachgebrauch auch dann oftmals der Begriff Steuerung verwendet, wenn es sich im eigentlichen Sinn um eine Regelung handelt (z.b. Speicherprogrammierbare Steuerung). 2

8 Kapitel 1. Überblick Rechen prozeß technischer Prozeß Rechen prozeß technischer Prozeß Aktor( en) Aktor( en) Sensor( en) gesteuerter Prozeß geregelter Prozeß Abbildung 1-1. Steuerung versus Regelung Man spricht also von einem geregelten - im Gegensatz zum gesteuertem - Prozess, wenn dieser jeweils mindestens einen Wandler für ein Ein- als auch ein Ausgangssignal (Aktor und Sensor) besitzt (Bild Steuerung versus Regelung). Dabei wird der Eingangswert (die Stellgröße) durch den über einen Sensor gewonnenen Zustandswert des Prozesses beeinflußt Kognitiver Prozess Als kognitive Prozesse schließlich bezeichnet man die Verarbeitung, Umformung und den Transport von Informationen innerhalb des menschlichen Bedieners. Durch entsprechende Mensch-Maschine-Schnittstellen (HMI, Human-Machine-Interface) nimmt der Bediener Einfluß auf die Rechenprozesse und auch direkt auf die technischen Prozesse. Für die Betrachtung der Rechenprozesse ist es jedoch nicht entscheidend, aus welcher Quelle die zu verarbeitenden Eingaben kommen. Kognitive Prozesse spielen daher bei den weiteren Betrachtungen eine untergeordnete Rolle (und werden wie Eingaben aus dem technischen Prozess behandelt). Ein Beispiel für einen derartigen kognitiven Prozess ist die Steuerungssoftware des Rangierbahnhofs Nord in München. Am Rangierbahnhof werden Güterzüge zusammengestellt. Da Güterzüge eine maximale Länge haben, kann es schon einmal passieren, dass nicht alle Waggons mitkommen können. Die Steuerungssoftware ist bewußt so geschrieben worden, dass in diesem Falle ein Mitarbeiter der Deutschen Bahn AG entscheidet, welche Waggons bevorzugt behandelt werden. Technischer Prozeß Kognitiver Prozeß Sensoren Userinput Aktoren Visualisierung Rechen Prozeß Abbildung 1-2. Zusammenhang zwischen unterschiedlichen Prozessformen 3

9 Kapitel 1. Überblick 1.2. Kennzeichen von Realzeitsystemen Bei einem Echtzeitsystem handelt es sich um ein System, das in der Lage ist, eine Aufgabe innerhalb eines spezifizierten Zeitfensters abzuarbeiten. Kennzeichen eines Echtzeitsystems ist daher die sogenannte Pünktlichkeit oder Rechtzeitigkeit und damit insbesondere verbunden die Eigenschaft, zeitlich deterministisch (also berechenbar oder zumindest vorhersehbar und vorhersagbar) zu sein. Eine oftmals notwendige Eigenschaft, aber auf keinen Fall ein Kennzeichen eines Echtzeitsystems ist die Schnelligkeit. Umwelt (z.b. technischer Prozeß) 1 0 Echtzeitsteuerung Wandler (z.b. Aktoren) (z.b. Sensoren) Abbildung 1-3. Struktur eines Echtzeitsystem Echtzeitsysteme (Bild Struktur eines Echtzeitsystem) kann man in allen Bereichen und beinahe überall finden. Prinzipiell verarbeiten sie Eingangswerte (z.b. Zustandsgrößen technischer Prozesse) und berechnen - zeitgerecht - Ausgangsgrößen (Werte für Stellglieder). Ein- und Ausgangsgrößen werden dem Echtzeitsystem über Wandler (z.b. Aktoren und Sensoren eines technischen Prozesses) zugeführt. Steuert bzw. regelt das Echtzeitsystem (Realzeitsystem) einen technischen Prozess, ohne dass der Benutzer direkt damit in Berührung kommt (eingebettete mikroelektronische Steuerung), spricht man von einem eingebetteten Realzeitsystem. Im Automobil beispielsweise finden sich eingebettete Realzeitsysteme, die zur Steuerung des Motors (Motormanagement), zur Steuerung des Anti-Blockier-Systems (ABS) oder zur Überwachung der Airbags zuständig sind. Weitere Beispiele für Echtzeitsysteme sind die vielfältigen Multimediaanwendungen bis hin zu den schnellen Computerspielen, die durch diverse Tricks versuchen, auf nicht echtzeitfähigen Plattformen, wie dem Windows-PC, Realzeitverhalten zu erreichen. In vielen Fällen werden Realzeitsysteme im sicherheitskritischen Umfeld (z.b. ABS) eingesetzt. Zu der Anforderung nach Rechtzeitigkeit gesellen sich damit noch die Forderungen nach Sicherheit (das System muss auch wirklich das richtige Ergebnis liefern) und Verfügbarkeit (das System darf unter keinen Umständen ausfallen). Elektronik Programm RTOS Standard OS Mechanik Handbetrieb + ISR 1 ns 1 us 1 ms 1 s 10 s 100 s 1000 s Abbildung 1-4. Realzeitsysteme und ihre Zeitanforderungen [TimMon97] 4

10 Kapitel 1. Überblick Gemäß der obigen Definition sind also alle Systeme mit zeitlichen Anforderungen Echtzeitsysteme. Bild Realzeitsysteme und ihre Zeitanforderungen [TimMon97] spiegelt entsprechend der Zeitanforderungen sinnvolle Realisierungstechnologien wider. Dabei erkennt man, dass Echtzeitsysteme sogar von Hand betrieben werden können, wenn Reaktionszeiten im Stundenbereich gefordert werden. muss auf eine zeitliche Anforderung im Minutenbereich reagiert werden, kann man mechanische Steuerelemente einsetzen. Mit Standard-Betriebssystemen ist es bereits möglich, Realzeitanforderungen im Sekundenbereich zu bedienen. Sind die Zeitanforderungen jedoch noch strenger, wird man ein Realzeitbetriebssystem einsetzen, zumindest, solange die Anforderungen nicht im Mikrosekunden-Bereich (usec) liegen. In diesem Zeitbereich lassen sich die Probleme zwar noch mit Software lösen, entsprechende Reaktionen müssen aber innerhalb der sogenannten Interrupt-Service-Routine erfolgen. Um noch stärkere Anforderungen zeitlicher Natur zu befriedigen, muss man schließlich eine Hardware-Realisierung wählen. Im folgenden wird der Schwerpunkt auf Realzeitsysteme liegen, deren Zeitanforderungen im Millisekunden-Bereich liegen, zu deren Realisierung im Normalfall ein Echtzeitbetriebssystem eingesetzt wird. Während aber auch das Spektrum der Echtzeitsysteme mit noch höheren Anforderungen betrachtet wird, werden Systeme auf mechanischer Basis bzw. Systeme mit Handsteuerung außer acht gelassen. Zusammenfassend lauten die wichtigsten Anforderungen an Echtzeitsysteme: Pünktlichkeit Rechtzeitigkeit Deterministisch Sicherheit Verfügbarkeit Ausprägungen von Realzeitsystemen Wie bereits im vorhergehenden Abschnitt deutlich wurde, gibt es unterschiedliche Ausprägungen von Echtzeitsystemen. Diese Ausprägungen lassen sich vor allem bezüglich ihrer Hardware klassifizieren. (Speicherprogrammierbare-) Steuerungen Eingebettete Systeme (embedded systems) RZ-Applikationen auf Standardarchitekturen Realzeitkommunikationssysteme Speicherprogrammierbare Steuerungen (SPS) Speicherprogrammierbare Steuerungen sind klassische Realzeitsysteme, die insbesondere auch als harte Realzeitsysteme eingesetzt werden. SPS n werden im Regelfall über spezielle Programmiersprachen und -Werkzeuge programmiert und arbeiten in einem festen Zeitraster (Zykluszeit). Eingebettete Systeme Als ein eingebettetes System bezeichnet man die Steuerung eines technischen Prozesses, mit der der Benutzer nur indirekt in Verbindung kommt: Bei einem eingebetteten System handelt es sich um eine integrierte, mikroelektronische Steuerung. Die Steuerung ist Teil eines Gesamtsystems (in das Gesamtsystem eingebettet). Das System besteht aus Hard- und aus Software. Das System wurde für eine spezifische Aufgabe entwickelt. Das Mensch-Maschine-Interface ist im Regelfall eingeschränkt. 5

11 Kapitel 1. Überblick Eingebettete Systeme weisen keine (kaum) bewegten Teile auf (diskless, fanless). Sie decken ein breites Einsatzspektrum ab. So stellt ein Handy ebenso ein eingebettetes System dar, wie das Modem, das ABS, die Waschmaschinensteuerung und der Gameboy. Echtzeitsysteme - oder Realzeitsysteme - werden im amerikanischen vielfach gleichgesetzt mit den eingebetteten Systemen (embedded systems). Diese Sichtweise ist jedoch zu einfach. Wenn auch die meisten eingebetteten Systeme Realzeitsysteme sind, so doch nicht ausschließlich alle. Nicht jedes eingebettete System ist ein Realzeitsystem, und nicht jedes Realzeitsystem ist ein eingebettetes System. Eingebettete Systeme - mit ihren zusätzlichen Anforderungen - spielen auch in dem vorliegenden Buch eine besondere Rolle. RZ-Applikationen auf Standardarchitekturen (auch Soft-SPS) Zwar für harte Realzeitsysteme nicht geeignet, werden Standardarchitekturen, wie beispielsweise eine PC-Architektur, zur Lösung von weichen Echtzeitproblemen eingesetzt. Typische Beispiele dafür sind Multimediaanwendungen und "Telefonieren über das Internet" (Voice over IP). Vorteile: Preiswerte Hardware Verfügbarkeit von Zusatz-Hardware Verfügbarkeit von Standard-Software zur Weiterverarbeitung aufgenommener Zustandsgrößen Nachteile: Preiswerte Hardware ist im rauhen Umfeld, in dem Echtzeitsysteme betrieben werden, nur eingeschränkt einsatzfähig. Standard-Betriebssysteme bieten kein kalkulierbares Realzeit-Verhalten. Realzeitkommunikationssysteme Realzeitkommunikationssysteme stellen eine Komponente eines verteilten Realzeitsystems dar. Einzelne Realzeitprozesse tauschen über diese Komponente mit deterministischem Verhalten Informationen aus Realzeitsystem-Komponenten Realzeitsysteme bestehen aus unterschiedlichen Komponenten. Je nach Ausprägung des Realzeitsystems sind dabei nicht alle Komponenten notwendig. So wird beispielsweise bei einem Computerspiel in den seltensten Fällen eine Prozessperipherie benötigt. 6

12 Kapitel 1. Überblick Applikation Regel und Steuerprogramm Visualisierung Realtime Operating System Hardware Prozeßankopplung direkte Kopplung oder Feldbus Prozeßperipherie (Sensoren, Aktoren) Abbildung 1-5. Architektur einer Echtzeitsteuerung Die Komponenten eines Realzeitsystems sind: Applikation Verarbeitungs- bzw. Steuerungsalgorithmen Visualisierung (HMI=Human-Man-Interface) Echtzeitbetriebssystem Rechnerhardware Prozessankopplung (entweder direkt oder über Realzeit-Kommunikationssysteme/Feldbusse) Prozessperipherie Ein Realzeitsystem verarbeitet fristgerecht Eingangssignale zu Ausgangssignalen. Dazu werden auf logischer Ebene externe Prozesse 1 (z.b. ein technischer Prozess) auf sogenannte Rechenprozesse (siehe Abschnitt Rechenprozess) abgebildet, die auf einer Hardware (Rechner, Mikrokontroller) ablaufen. Die Eingangswerte der Rechenprozesse korrespondieren mit Signalen des externen Prozesses. Wandler (Meßwertaufnehmer, Sensoren) transformieren dabei diese (physikalischen) Signale und Werte in maschinell verarbeitbare Digitalwerte. Entsprechend werden die von den Rechenprozessen erzeugten Ausgangs- bzw. Ausgabewerte über Wandler (Stellglieder, Aktoren) in (physikalische) Größen transformiert, mit denen sich der externe Prozess beeinflussßen (regeln, steuern) läßt. Die Wandlung der Werte, sowohl der Eingangs- als auch der Ausgangswerte, kann entweder am externen Prozess vollzogen werden oder aber direkt in der Realzeitrechnerhardware. Im ersten Fall werden die externen Prozesssignale direkt an ihrer Entstehungsquelle gewandelt und als digitale Information an den Realzeitrechner übertragen, bzw. vom Realzeitrechner als digitale Information an den externen Prozess gebracht. Für die Übertragung dieser digitalen Information wird oft ein (realzeitfähiges) Kommunikationssystem (z.b. Feldbus) eingesetzt. Werden nur wenige Prozesssignale ausgetauscht, kann der externe Prozess auch direkt angekoppelt werden. In diesem Fall ist jedes Ein- und Ausgangssignal des externen Prozesses direkt mit der Hardware des Realzeitsystems verbunden. 7

13 Kapitel 1. Überblick Eine direkte Kopplung verwendet man aber vor allem auch beim zweiten Fall, bei dem die Ein- und Ausgangswerte im Realzeitrechner selbst gewandelt werden. Hier müssen die Prozesssignale in ihrer ursprünglichen Form bis zu den entsprechenden Wandlern transportiert werden. Die Rechnerhardware selbst besteht aus einem oder mehreren Prozessoren, dem Speicher und der sogenannten Glue-Logik (Bausteinen, die das Zusammenspiel der übrigen Komponenten ermöglichen). Darüber hinaus gehören die bereits erwähnten Wandler dazu. Bei Systemen, die hohe zeitliche Anforderungen erfüllen, wird man auch oft spezielle Hardwarebausteine finden, die die Funktionalität, die normalerweise in Software realisiert worden wäre, in Hardware realisiert (Asics, FPGA s, LCA s usw.). Daneben gehören auch Sekundärspeicher (z.b. Festplatten, Flashkarten) zur Rechnerhardware. Die Rechnerhardware wird über ein Realzeitbetriebssystem oder ein Laufzeitsystem (Executive, einfaches Betriebssystem) angesteuert. Dieses Betriebssystem stellt die Ablaufumgebung für die Rechenprozesse zur Verfügung. Die Hardware wird dabei über Gerätetreiber abgebildet. In Realzeitsystemen laufen mehrere Rechenprozesse scheinbar gleichzeitig ab (quasi parallel). Neben den Rechenprozessen, die applikationsspezifisch sind, gibt es in einem Realzeitsystem oftmals auch Rechenprozesse, die zum Betriebssystem gehören (Dienstprogramme). Bei solch einem Dienstprogramm kann es sich beispielsweise um einen Rechenprozess handeln, der Netzwerkdienste (z.b. HTTP-Server) bedient. Eine andere Gruppe von Rechenprozessen könnte sich bei einem Realzeitsystem um die Visualisierung des Zustandes des externen Prozesses kümmern. Fußnoten 1. Mit externer Prozess wird hier der Prozess bezeichnet, der durch das Realzeitsystem gesteuert wird, ohne dass er direkt Teil des Realzeitsystems ist. Nach dieser Definition besteht das Gesamtsystem aus dem externen (technischen) Prozess und dem Realzeitsystem. 8

14 Kapitel 2. Realzeitbetrieb Unter schritthaltender Verarbeitung bzw. Echtzeitbetrieb versteht man die fristgerechte Bearbeitung von Anforderungen aus einem technischen Prozess. Was aus Sicht des technischen Prozesses Anforderungen sind, sind aus Sicht der Steuerung Ereignisse. Die Steuerung muss also Ereignisse fristgerecht bearbeiten. Können die Ereignisse durch die Steuerung nicht innerhalb der durch den technischen Prozess gestellten Zeitschranken bearbeitet werden, sind die Folgen möglicherweise katastrophal. Daher ist es notwendig, die zeitlichen Anforderungen und die zeitlichen Kenndaten bezüglich der Steuerung (der Rechenprozesse und deren Ablaufumgebung) zu kennen. Daher werden in diesem Kapitel die Zeitschranken des technischen Prozesses formal eingeführt. Außerdem werden Methoden vorgestellt, den sogenannten Echtzeitnachweis durchzuführen. Dabei sind folgende Aspekte von Bedeutung: Zur Bearbeitung von Aufgaben wird Zeit (Verarbeitungszeit) benötigt. Beim Anliegen mehrerer Aufgaben muss die Reihenfolge, in der die Arbeiten erledigt werden, geplant werden. Die richtige Reihenfolge der Aufgaben ist entscheidend, um die zeitgerechte Bearbeitung der Aufgaben gewährleisten zu können. Als Planungsgrundlage bekommen die einzelnen Aufgaben Prioritäten gemäß ihrer Wichtigkeit. Die Bearbeitung einer Aufgabe (durch einen Prozess) muss unterbrechbar sein, damit kurzfristige höherpriore Aufgaben erledigt werden können Echtzeitbedingungen Der technische Prozess stellt Anforderungen unterschiedlicher Art an die Echtzeitsteuerung. Alle Anforderungen müssen aber von der Steuerung bearbeitet werden können, auch wenn sämtliche Ereignisse zu einem Zeitpunkt auftreten (erste Echtzeitforderung). Der zeitliche Abstand zwischen zwei Anforderungen gleichen Typs wird Prozesszeit (t P ) genannt. Ist der zeitliche Abstand zweier Ereignisse konstant, spricht man von einem periodischen Ereignis. Vielfach schwankt jedoch der zeitliche Abstand, so dass es einen minimalen zeitlichen Abstand t Pmin und einen maximalen zeitlichen Abstand t Pmax gibt. Der Auftrittszeitpunkt eines zu einem Rechenprozess gehörenden Ereignisses wird mit t A (im englischen auch mit Releasetime) bezeichnet. Wird der Auftrittszeitpunkt nicht absolut, sondern relativ zur Prozesszeit angegeben, spricht man von der sogenannten Phase t ph. So tritt für uns bei periodischen Zeitpunkten das erste Ereignis vielfach zum Zeitpunkt»t A =0«auf, die Phase für dieses Ereignis beträgt ebenfalls t ph =0. Nimmt man ein Ereignis mit der Periode t P =5ms und der Phase t ph =4ms, tritt das Ereignis zu den folgenden Zeitpunkten t A auf: 4ms, 9ms, 14ms usw. Das zeitliche Auftreten der Ereignisse wird in einer Anforderungsfunktion dargestellt. Dazu werden die Anforderungen (sichtweise des Steuerungs- bzw. Betriebssystems) bzw. Ereignisse (Sichtweise des technischen Prozess) in Form von Pfeilen über der Zeit aufgetragen (siehe Abbildung [Anforderungsfunktion]). 9

15 Kapitel 2. Realzeitbetrieb Eintritt des Ereignisses E E E E E E E t Pmin t Pmax t[t] t = 2T Pmin t = 6T Pmax 2T+6T t s = 3T t = p = 4T 2 Abbildung 2-1. Anforderungsfunktion Für die eigentliche Bearbeitung des Ereignisses werden vom Rechner Anweisungen abgearbeitet. Die zum Ereignis gehörende Folge von Anweisungen wird hier als Codesequenz oder auch als Job bezeichnet. Jobs sind innerhalb des Rechners als Rechenprozesse realisiert, die sich selbst wiederum in Tasks und Threads unterscheiden lassen (siehe Kapitel [Prozess-Management]). Manchmal bilden auch mehrere Codesequenzen zusammen einen solchen Rechenprozess. Für die Abarbeitung des Jobs»i«benötigt der Rechner die sogenannte Verarbeitungszeit t E,i. Die Verarbeitungszeit im Englischen auch Execution-Time ist insbesondere bei modernen Prozessoren nicht konstant. Es gibt eine minimale Verarbeitungszeit t Emin und eine maximale Verarbeitungszeit t Emax. Die Execution-Time ist das Verhältnis aus aus der zu leistenden Rechenarbeit RA und der Leistung P des eingesetzten Prozessors (Rechnerkern). t E =RA/P Die Angabe der Verarbeitungszeit ist in der Praxis problematisch, da zum einen abhängig von der Aufgabenstellung oder/und bedingt durch die Eingabewerten die Rechenarbeit RA oft schwankt und zum anderen auch die Leistung des Prozessor bedingt durch Caches und z.b. DMA nicht konstant ist. Zur Berechnung bei Echtzeitsystemen muss t Emax,i und damit RA max und P min angesetzt werden. Die für den Worst-Case anzunehmende maximale Verarbeitungszeit t Emax wird in der Literatur auch Worst-Case-Execution Time (WCET) genannt (siehe Kapitel [Abschätzung der Worst Case Execution Time (WCET)]) Auslastung Abhängig von der Auftrittshäufigkeit eines Ereignisses und der damit anfallenden Verarbeitungszeit ist der Rechnerkern (Prozessor) ausgelastet. Die Auslastung (ρ, englisch utilization) durch eine Anforderung ergibt sich damit als Quotient aus notwendiger Verarbeitungszeit und Prozesszeit: ρ = t E t P Im Worst Case ist die Auslastung ρ max = t Emax t Pmin 10

16 Kapitel 2. Realzeitbetrieb Ein Realzeitsystem muss in der Lage sein, die auftretenden Anforderungen in der Summe bearbeiten zu können (oft auch als Forderung nach Gleichzeitigkeit bezeichnet). Mathematisch bedeutet dieses, dass die Gesamtauslastungρ ges kleiner als 100% sein muss. Die Gesamtauslastung ergibt sich aus der Summe der Auslastungen der einzelnen Jobs. Gleichung 2-1. Gesamtauslastung ρ ges = n i=1 t Emax,i t Pmin,i 1 Prozessor belegung t e A Rechenprozess A Rechenprozess B Rechenprozess A + B te B t ρ = A t e A t pa t e B ρ = B t p B t e A te B ρ = + A+B t PA t P B t PB t p A Abbildung 2-2. Auslastung Abbildung [Auslastung] verdeutlicht die Auslastung grafisch. Ein Echtzeitrechner bearbeitet zwei Rechenprozesse (Jobs): Rechenprozess A und Rechenprozess B. Sei die Verarbeitungszeit des Rechenprozesses A t E,A =0.8ms und die Prozesszeit t P,A =2ms ergibt sich eine Auslastung des Rechners durch den Rechenprozess A von 40%. Ist die Verarbeitungszeit des Rechenprozesses B ebenfalls t E,B =0.8ms und hat dieser auch eine Prozesszeit von t P,B =2ms ergibt sich die gleiche Auslastung wie die des Rechenprozesses A vonρ B =40%. Die Gesamtauslastung des Rechners - er bearbeitet ja beide Rechenprozesse - beträgt nun ρ Ges. =ρ A + ρ B =80%. Erste Echtzeitbedingung: Die Auslastung ρ ges eines Rechensystems muss kleiner oder gleich 100% sein. Die Auslastungsbedingung ist eine notwendige, aber keine hinreichende Bedingung Pünktlichkeit Aus dem technischen Prozess kommen immer wieder Anforderungen an die Echtzeitsteuerung. Aufgabe der Steuerung ist es, alle Anforderungen zeitgerecht zu bearbeiten. Alle bedeutet in diesem Falle, dass der Rechner leistungsfähig sein muss, um mit der vorgegebenen Last fertig zu werden. Zeitgerecht bedeutet, dass der Rechner die Anforderungen rechtzeitig bzw. pünktlich bearbeitet. Das wesentliche Kriterium für ein Realzeitsystem ist Pünktlichkeit in Abgrenzung zu Schnelligkeit. 11

17 Kapitel 2. Realzeitbetrieb Allerdings lassen sich Anforderungen an Pünktlichkeit mit schnellen Systemen leichter erfüllen als mit langsamen. Prinzipiell muss ein Realzeitsystem eine Aufgabe in einem vorgegebenen Zeitfenster erfüllt haben, wie es das tut, ist dabei unwesentlich. tatsächlich t Dmin Reaktions Bereich des Rechners erlaubt t 0 t Rmax t Dmax t Ereignis tritt ein Abbildung 2-3. Reaktionsbereich Unter Pünktlichkeit wird hier also verstanden, dass die Aufgabe 1. nicht vor einem weiteren spezifizierten Zeitpunkt t Dmin erledigt ist und 2. bis zu einem spezifizierten Zeitpunkt t Dmax erledigt wird (Rechtzeitigkeit). Die Forderung, dass eine Ausgabe nicht vor einem bestimmten Zeitpunkt durchgeführt wird, fehlt oft (t Dmin =t 0 ) und ist in den übrigen Fällen vielfach trivial zu erfüllen. Die Forderung nach Rechtzeitigkeit stellt dagegen hohe Anforderungen an das System und damit letzlich auch an den Ingenieur bzw. Informatiker, der das System konzipiert und realisiert. Formal wird die Forderung nach Rechtzeitigkeit als sogenannte maximal zulässige Reaktionszeit t Dmax ausgedrückt. Bei der maximal zulässigen Reaktionszeit handelt es sich um eine Relativzeit beginnend mit dem Eintreten eines Ereignisses. Die maximal zulässige Reaktionszeit, wie sie hier gebraucht wird, beinhaltet nur die Zeiten, die für die Rechenprozesse relevant sind. Unter maximaler zulässiger Reaktionszeit wird also der Zeitpunkt verstanden, bis zu dem der Rechenprozess seine Ausgabe gemacht haben muss. Es wird nicht der Zeitpunkt verstanden, bis zu dem der technische Prozess einen definierten Zustand eingenommen haben muss. Sind daher die Randbedingungen des technischen Prozesses bekannt, muss die maximal zulässige Reaktionszeit des Rechenprozesses daraus abgeleitet werden. Mit maximaler Reaktionszeit t Rmax wird der Zeitpunkt bezeichnet, bis zu dem durch den Rechner eine Reaktion in jedem Fall erfolgt ist. Es handelt sich um eine Worst Case Zeit. Die maximale Reaktionszeit muss also immer kleiner sein als die maximal zulässige Reaktionszeit. To You m v=1 s Weiche Barcodeleser 300 mm Umschaltzeit=200ms t 300 = s v = m m s = 300 ms t Dmax = t 300 t U = 100 ms Abbildung 2-4. Maximal zulässige Reaktionszeit bei einer Rohrpost 12

18 Kapitel 2. Realzeitbetrieb Beispiel 2-1. Rohrpostsystem In einem Rohrpostsystem bewegen sich Postbehälter, die durch einen Strichcode gekennzeichnet sind. Jeweils 30 cm vor einer Rohrpostweiche identifiziert ein Realzeitrechner mittels Barcodeleser den Postbehälter und stellt daraufhin die Weiche. Die Postbehälter haben eine Geschwindigkeit von v=1m/s. Die Weiche hat eine Stellzeit von 200 ms. Aus diesen Daten ergibt sich, dass der Postbehälter ab dem Barcodeleser bis zur Weiche 300 ms benötigt. Da die Weiche jedoch noch eine Stellzeit von 200 ms hat, muss der Realzeitrechner spätestens nach 100ms der Weiche die Anweisung übermitteln, in welches Rohr der Postbehälter weitergeleitet werden muss. Die maximal zulässige Reaktionszeit beträgt damit t Dmax =100ms. Die maximale Reaktionszeit t Rmax kann aufgrund fehlender Informationen nicht angegeben werden. Diese Zeit basiert nämlich auf der Leistung des verwendeten Realzeitsystems und der dort ablaufenden Applikation. Das Ereignis, das den Bezugspunkt für die Reaktionszeit darstellt, kann beispielweise durch eine Zustandsänderung im technischen Prozess charakterisiert sein (Postbehälter angekommen, Temperaturwert überschritten). Trägt man das Auftreten der Ereignisse über der Zeit auf, erhält man die sogenannte Anforderungsfunktion (Abb. Anforderungsfunktion). Der Zeitabstand zwischen zwei Ereignissen gleichen Typs wird als Prozesszeit t P bezeichnet. Oftmals ist die maximal zulässige Reaktionszeit t Dmax durch die Anforderung definiert, dass ein Ereignis vor dem Eintreffen eines nachfolgenden Ereignisses gleichen Typs bearbeitet sein muss (t Dmax t Pmin ). Die Prozesszeit t P ist nicht immer konstant. Daher wird man im Regelfall bei Berechnungen, ob die Realzeitbedingungen erfüllt sind oder nicht, die minimale Prozesszeit t Pmin einsetzen. Zur Berechnung der durchschnittlichen Auslastung ist eine Mittelwertbildung (bei einer Gleichverteilung zum Beispiel der statistische Mittelwert) durchzuführen. Nachdem ein Ereignis eingetreten ist, kann in den meisten Fällen der zugehörige Rechenprozess nicht direkt die Bearbeitung des Ereignisses beginnen, da beispielsweise die Bearbeitung eines anderen Ereignisses noch nicht abgeschlossen ist. Der Rechenprozess muss warten. Diese Zeit wird als Wartezeit t w oder auch Verzögerungszeit beschrieben. 1 Damit ergibt sich die Reaktionszeit t R aus der Summe der Wartezeit t W und der Verarbeitungszeit t E. Gleichung 2-2. Reaktionszeit t R = t E +t W RK t Abbildung 2-5. Rechnerkernbelegung durch 4 Rechenprozesse Trägt man die Verarbeitungszeit t E über die Zeit auf, erhält man die sogenannte Rechnerkernbelegung (Abb. Rechnerkernbelegung durch 4 Rechenprozesse). Sie gibt Auskunft darüber, zu welchen Zeiten die Verarbeitungseinheit mit welcher Aufgabe betraut ist. Oftmals trägt man die Anforderungsfunktion zusammen mit der Rechnerkernbelegung in ein Diagramm ein. 13

19 Kapitel 2. Realzeitbetrieb Merke: Um eine Anforderung i pünktlich (in Echtzeit) zu erledigen, muss die Reaktionszeit größer oder gleich der minimal zulässige Reaktionszeit, aber kleiner oder gleich der maximal zulässigen Reaktionszeit sein (für alle t R ): t Dmin,i t R t Dmax,i Gleichung 2-3. Pünktlichkeitsbedingung t Dmin t Rmin t Rmax t Dmax Harte und weiche Echtzeit Kosten Deadline harte Echtzeit weiche Echtzeit t Dmin t Dmax Zeit Abbildung 2-6. Harte und weiche Echtzeit als Kostenfunktion Die Forderung nach Pünktlichkeit ist nicht bei jedem System gleich stark. Während das nicht rechtzeitige Absprengen der Zusatztanks bei einem Raumschiff zum Verlust desselbigen führen kann, entstehen durch Verletzung der Pünktlichkeitsbedingung bei einem Multimediasystem allenfalls Komfortverluste. Vielfach findet man den Begriff harte Echtzeit, wenn die Verletzung der Realzeitanforderungen katastrophale Folgen hat und daher nicht toleriert werden kann. Man findet den Begriff der weichen Echtzeit, wenn die Verletzung der Realzeitanforderungen verkraftet werden kann. Allerdings kann man nicht ausschließlich zwischen harter und weicher Echtzeit unterscheiden, sondern jegliche Abstufung zwischen den beiden Extremen (weich und hart, soft und hard) ist möglich. Die Graduation hängt von der Aufgabenstellung ab. Die Unterschiede bei der Forderung nach Pünktlichkeit werden oft anhand einer Kostenfunktion verdeutlicht. Bei den sogenannten weichen Echtzeitsystemen bedeutet das Verletzen der Realzeitbedingung einen leichten Anstieg der Kosten. Bei den sogenannten harten Echtzeitsystemen steigen jedoch die Kosten durch die Verletzung der Realzeitbedingung massiv an. 14

20 Kapitel 2. Realzeitbetrieb Benefit 100 % no realtime requirements soft realtime requirements t 0 t Dmin hard realtime requirements t Dmax t t Dmin minimal zulässige Reaktionszeit t Dmax maximal zulässige Reaktionszeit Abbildung 2-7. Benefit-Function Eine andere Art der Darstellung ist die sogenannte Benefitfunction (Nutzenfunktion). Hier wird der Nutzen der Reaktion des Realzeitrechners auf das Ereignis über die Zeit aufgetragen. Bei Systemen ohne Echtzeitanforderungen ist der Nutzen unabhängig von dem Zeitpunkt der erfolgten Reaktion, bei einem absolut harten Echtzeitsystem ist nur dann ein Nutzen vorhanden, wenn die Reaktion innerhalb des durch t Dmin und t Dmax aufgestellten Zeitfensters erfolgt. Dazwischen ist jegliche Abstufung an Realzeitanforderungen möglich. Hier kann man (je nach Graduation) von weichen Realzeitanforderungen sprechen Latenzzeiten Mit der Latenzzeit gibt es noch weitere Zeiten, die für die Analyse eines Echtzeitsystems von Bedeutung sind. Die Zeit, die zwischen Anforderung und Start der zugehörigen Codesequenz (Bearbeitung) vergeht, nennt man Latenzzeit t L. Man unterscheidet die Interrupt-Latenzzeit von der Prozess-Latenzzeit. Die Zeit zwischen Auslösen eines Interrupts und Start der zugehörigen Interrupt-Service-Routine wird als Interrupt- Latenzzeit bezeichnet, die Zeit zwischen einer Anforderung (das kann ebenfalls ein Interrupt sein) und dem Start des zugehörigen Rechenprozesses bezeichnet man als Prozess-Latenzzeit. Vergleich Reaktions- und Latenzzeit: Reaktionszeit: Zeit zwischen einer Anforderung und dem Ende der Bearbeitung. Latenzzeit: Zeit zwischen einer Anforderung und dem Start der Bearbeitung. Soll die Reaktionszeit t R in Abhängigkeit von der Latenzzeit ausgedrückt werden, muss noch die Unterbrechungszeit eingeführt werden. Die Unterbrechungszeit t U ist die Zeit, die ein Rechenprozess nach dem Start nicht arbeitet, entweder, weil der Rechenprozess durch einen anderen verdrängt ist oder weil er auf Betriebsmittel wartet. t R =t L + t U +t E t W =t L + t U 15

21 Kapitel 2. Realzeitbetrieb 2.3. Unterbrechbarkeit und Prioritäten Ist die Auslastungsbedingung (siehe [Auslastung]) erfüllt, die Pünktlichkeitsbedingung aber nicht, kann unter Umständen eine schritthaltende Verarbeitung dennoch erreicht werden. Hierzu sind allerdings weitere Maßnahmen, insbesondere bezüglich der Systemsoftware und der Architektur der Realzeitapplikation notwendig: 1. Die gestellte Aufgabe muss ich in (unabhängige, parallel ablaufende) Teilaufgaben zerlegen lassen und 2. die einzelnen Teilaufgaben (Jobs) müssen unterbrechbar sein und müssen priorisiert werden. Hierzu ein simples Beispiel: 100 Meßwerte erfaßt Meßwerte Aufnehmen Meßwerte Sichern Abbildung 2-8. Datenflußdiagramm Meßwertaufnahme Beispiel 2-2. Unterbrechbarkeit bei der Meßwerterfassung Ein Meßwerterfassungssystem soll im Abstand von 1 ms (t P,1 =1ms) kontinuierlich Meßwerte aufnehmen. Dazu benötigt der zugehörige Rechenprozess eine Rechenzeit von t E,1 =500 µs. Jeweils 100 Meßwerte (t P,2 =100t P,1 =100ms) ergeben einen Datensatz, der vorverarbeitet und zur Archivierung weitergeleitet wird. Dazu ist eine Rechenzeit von t E,2 =40ms notwendig. Überprüft man an diesem Beispiel die 1. Echtzeitbedingung, so ist diese bei einer Auslastung von ρ=t E,1 /t P,1 + t E,2 /t P,2 = = 0.9 = 90% erfüllt. Dennoch läßt sich die Aufgabe ohne weitere Maßnahmen nicht in Echtzeit erfüllen. 16

22 Kapitel 2. Realzeitbetrieb while( 1 ) for( i=0; i<100; i++ ) Sende Timer Event in 1 ms Nimm Meßwert auf Warte auf Timer Event Verarbeite Datensatz Archiviere Ergebnis Abbildung 2-9. Struktogramm Meßwerterfassung sequentiell Anforderung t[ms] Meßwertaufnahme Verarbeitung und Archivierung Abbildung Zeitdiagramm Meßwert sequentiell Abbildung [Struktogramm Meßwerterfassung sequentiell] visualisiert eine Lösung der Aufgabenstellung, bei der die Aufgabenstellung in einem einzigen Rechenprozess abgearbeitet wird. Zunächst werden die 100 Meßwerte aufgenommen, dann verarbeitet. Wie in Abbildung [Zeitdiagramm Meßwert sequentiell] ersichtlich, startet gemäß Struktogramm nach 100ms die Codesequenz Verarbeitung, welche den Prozessor für 40ms belegt. Allerdings müssen natürlich auch während dieser Zeit weiterhin Meßwerte aufgenommen werden, das ist aber bei der gewählten Struktur nicht möglich. Um das Problem dennoch zu lösen, wird die Aufgabe auf zwei unabhängige Rechenprozesse aufgeteilt. Rechenprozess»Erfassung«ist für die Meßwertaufnahme verantwortlich, Rechenprozess»Verarbeitung«für die Verarbeitung der Werte und Sicherung der Ergebnisse. Über sogenannte Inter-Prozess-Kommunikation (IPC) informiert Rechenprozess»Erfassung«den Rechenprozess»Verarbeitung«, dass 100 Meßwerte erfaßt wurden. Aus Systemsicht stellt»erfassung«eine Anforderung respektive bekommt»verarbeitung«ein Ereignis zugestellt. Dadurch wird Rechenprozess»Verarbeitung«aktiv. Er verarbeitet die Daten und sichert das Ergebnis. Damit keine Meßwerte verloren gehen, muss jetzt noch Rechenprozess»Verarbeitung«unterbrechbar gemacht werden, das bedeutet, dass jedesmal, wenn der Timer-Event auftritt, Rechenprozess»Verarbeitung«kurz unterbrochen wird (man spricht von Preemption) und der Rechenprozess»Erfassung«den neuen Meßwert erfassen kann. Dass Aufgrund der Ereignisse ein (zugehöriger) Rechenprozess aktiv wird, besorgt die Systemsoftware (Betriebssystem). Den Wechsel zwischen den beiden Rechenprozessen bezeichnet man als Kontextswitch 2. Ein Kontextswitch benötigt Zeit, die jetzt auch noch in die Berechnung der Auslastung (1. Echtzeitbedingung) mit einfließen muss. Wir werden jedoch in unseren Betrachtungen davon ausgehen, dass die Kontextwechselzeit so gering ist, dass sie vernachlässigt werden kann. 17

23 Kapitel 2. Realzeitbetrieb Anmerkung: Die im Beispiel angesprochenen Dienste wie IPC und Unterbrechbarkeit von Rechenprozessen stellt das Echtzeitbetriebssystem zur Verfügung. Rechenprozess "Erfassung" while( 1 ) for( i=0; i<100; i++ ) Sende Timer Event in 1 ms Nimm Meßwert auf Rechenprozess "Verarbeitung" while( 1 ) RcvMsg von "Erfassung" Verarbeitung der Daten Archiviere Ergebnis Warte auf Timer Event SendMsg an "Verarbeitung" Abbildung Struktogramm Meßwerterfassung parallel Anforderung t[ms] Meßwertaufnahme Verarbeitung Abbildung Zeitdiagramm Meßwert parallel Die dargestellte Lösung, basierend auf der Parallelisierbarkeit der Aufgaben und der Möglichkeit, einzelne Rechenprozesse während der Abarbeitung kurz zu unterbrechen, funktioniert nur dann, wenn festlegbar ist, welcher Rechenprozess unter welchen Umständen andere Rechenprozesse unterbrechen darf. Im einfachsten Fall geschieht dies über die Vergabe von Prioritäten. Damit im angegebenen Beispiel die Echtzeitanforderungen erfüllt werden, muss daher der Rechenprozess»Erfassung«höhere Priorität haben als der Rechenprozess»Verarbeitung«. Es gibt keine einheitliche Definition darüber, wie eine hohe Priorität repräsentiert wird. In manchen Systemen stehen niedrige Zahlen für eine hohe Priorität, in anderen dagegen ist es genau umgekehrt. Bei uns werden die hohen Prioritäten normalerweise durch niedrige Zahlenwerte gekennzeichnet. Nicht immer ist die Prioritätenvergabe so einfach und eindeutig wie im Beispiel der Meßwerterfassung. Oft sind im System eine Vielzahl unterschiedlicher Rechenprozesse aktiv. Um hier Prioritäten vergeben zu können, kann man initial nach folgender Faustformel verfahren: Merke: Prozesse mit kurzen Rechenzeiten t E haben in der Regel auch kurze Prozesszeiten t P und bekommen hohe Prioritäten. 18

24 Kapitel 2. Realzeitbetrieb Prozesse mit langen Rechenzeiten t E haben in der Regel auch lange Prozesszeiten t P und bekommen niedrige Prioritäten. Besteht die in der Faustformel angesprochene Korrespondenz zwischen t E und t P nicht, wird man bei der Vergabe meist die kürzere Prozesszeit oder auch kürzere maximal zulässige Reaktionszeit t Dmax als Indikator für eine höhere Priorität ansetzen. Allerdings ist in einem solchen Fall immer noch der gesunde Menschenverstand (und später der noch folgende Echtzeitnachweis) erforderlich. Variable Prozesszeiten. Das Problem der Prioritätenvergabe und Auslastungsberechnung wird auch dadurch erschwert, dass in der Realität nicht alle Prozesssignale wie in den Beispielen angenommen mit konstanter Periode anliegen. Vielmehr variieren Prozesszeiten abhängig von der zu lösenden Aufgabenstellung (zum Beispiel vom physikalischen Prozess). Sind sichere Informationen, über die Auftrittshäufigkeit der Ereignisse des technischen Prozesses verfügbar, läßt sich zwar die Auslastung berechnen (und damit die Anforderungen bezüglich der 1. Echtzeitbedingung überprüfen), damit läßt sich aber nur sehr schwer das Einhalten der 2. Echtzeitbedingung überwachen (zumindest solange keine Informationen über die Mindestabstände zwischen den Ereignissen vorliegen). Fußnoten 1. Später werden wir noch eine Sleeptime (Schlafenszeit) kennenlernen. Die Schlafenszeit unterscheidet sich von der Verzögerungszeit dadurch, dass der Job selbst die Schlafensdauer festgelegt hat er möchte für die gewählte Zeit suspendiert werden. Die Wartezeit dagegen wird ihm vom System aufgezwungen, weil das System auch andere Jobs zu bedienen hat und nicht einem Job exklusiv zur Verfügung steht. 2. Die Einheit, die den Kontextswitch durchführt, wird Scheduler genannt. 19

25 Kapitel 3. Realzeitbetriebssysteme Lernziele: Aufbau und Anforderungen des Betriebssystems. Philosophien und Konzepte in Betriebssystemen. Prinzipielles Verständnis für die Funktionsweise eines Betriebssystems bekommen. Wichtige Komponenten des Betriebssystems beschreiben und erklären können (Aufgabe und Arbeitsweise). Datenfluß durch das Betriebssystem. Was ist ein Systemcall? Unterschied Systemcall - Funktionsaufruf. Schedulingverfahren. Aufgaben und Aufbau einer Memory-Management-Unit. Definition. Aus Systemsicht ist ein Betriebssystem die Bezeichnung für alle Softwarekomponenten, die die Ausführung der Benutzerprogramme, die Verteilung der Betriebsmittel (z.b. Speicher, Prozessor, Dateien), ermöglichen, steuern und überwachen. Das Betriebssystem stellt dem Benutzer die Sicht eines virtuellen Prozessors zur Verfügung, der einfacher zu benutzen ist als die reale Hardware, z.b.: Aus Sicht eines Benutzers steht der Rechnerkern (CPU) ihm allein zur Verfügung. Einfacher Zugriff auf Ressourcen wie Speicher, Geräte, Dateien (das wird erreicht durch Speichermanagement, Gerätetreiber und das Dateisystem). Das Betriebssystem besteht aus einem Betriebssystemkern und aus sonstigen Systemkomponenten wie beispielsweise den Dienstprogrammen (z.b. die Shell). An das Betriebssystem werden die folgenden Anforderungen gestellt: Zeitverhalten Schnelligkeit Bei einem RTOS insbesondere die Realisierung kurzer Antwortzeiten. Zeitlicher Determinismus Geringer Ressourcenverbrauch Hauptspeicher Prozessorzeit Zuverlässigkeit und Stabilität Programmfehler dürfen das Betriebssystem und andere Programme nicht beeinflussen. Sicherheit Dateischutz, Zugangsschutz Flexibilität und Kompatiblität Erweiterbarkeit Einhalten von Standards (z.b. POSIX) Möglichkeit, für andere BS geschriebene Programme auszuführen 20

26 Kapitel 3. Realzeitbetriebssysteme Portabilität Skalierbarkeit Ein Realzeitbetriebssystem hat insbesondere die Aufgabe: die Ressourcenverteilung sicherzustellen, den deterministischen Ablauf (insbesondere Scheduling) zu garantieren und die Angabe und das Einhalten von Zeitbedingungen zu ermöglichen. Bild Realzeitsysteme und ihre Zeitanforderungen [TimMon97] spiegelt die Größenordnung wider, unter denen Realzeitbetriebssysteme Verwendung finden. Hat man Zeitanforderungen im Minutenbereich, läßt sich ein System durchaus noch von Hand steuern, unterhalb dieser Marke (etwa 1 Minute) reicht eine Automatisierung auf Basis von Mechanik aus. Zeitanforderungen im Sekunden-Bereich lassen sich durchaus mit einem Standardbetriebssystem erfüllen, im Millisekunden-Bereich jedoch benötigt man ein Realzeitbetriebssystem. Gilt es Anforderungen im Mikrosekunden-Bereich zu erfüllen, müssen die notwendigen Aktionen innerhalb einer ISR durchgeführt werden. Unterhalb dieser Grenze hilft nur noch eine Realisierung in Hardware Aufbau und Struktur Applications Services Libraries Sonstige BS Komponenten Syscall Interface IO Management Process Management Memory Management Kernel Device Driver Layer Hardware Abbildung 3-1. Betriebssystem-Architektur Bild Betriebssystem-Architektur stellt den prinzipiellen Aufbau eines Betriebssystems dar. Die Treiberschicht abstrahiert Zugriffe auf die Hardware und stellt eine betriebssysteminterne, standardisierte Schnittstelle zur Verfügung, um in das System neue Hardwarekomponenten systemkonform zu integrieren. Gerade in Realzeitsystemen ist die Treiberschicht von zentraler Bedeutung. Denn nur wenn zusätzliche (proprietäre) Hardware systemkonform in das System integriert wird, kann der versprochene Determinismus des Betriebssystems auch gewährleistet werden. Die Integration von Hardware in das System über die Treiberschicht ermöglicht darüber hinaus auch den systemkonformen und standardisierten Zugriff auf Hardware aus der Applikation heraus. Hierfür sorgt das I/O Subsystem eines Betriebssystems. Bestimmte Gruppen von Hardware (z.b. Netzwerkkarten, SCSI, 21

27 Kapitel 3. Realzeitbetriebssysteme Filesysteme, PCI) benötigen ähnliche Funktionalitäten. Diese zur Verfügung zu stellen ist ebenfalls Aufgabe des I/O Subsystems. Eine wichtige Aufgabe für Betriebssysteme ist die Verteilung der Ressource CPU (Rechnerkern) auf mehrere Tasks, das sogenannte Scheduling. Diese Aufgabe wird durch den Block Process-Management dargestellt. Speichermanagement (Adressumsetzung und Speicherschutz) ist eine weitere wesentliche Aufgabe. Es darf nicht möglich sein, dass eine normale Applikation ein komplettes Rechnersystem zum Absturz bringt. Dazu müssen aber die Speicherräume der unterschiedlichen Applikationen gegeneinander abgeschottet sein, ein Speicherschutzmanagement ist notwendig. Die Komponente dazu heißt Memory-Management-Unit (MMU). Speicherschutz wird heutzutage massiv durch Hardware unterstützt. Über sogenannte Softwareinterrupts können Applikationen Dienste des Betriebssystems in Anspruch nehmen. Man spricht hier von sogenannten Systemcalls. Die Schnittstelle innerhalb des Betriebssytems, die gemäß des ausgelösten Systemcalls den richtigen Dienst ausführt, wird als Systemcall-Interface bezeichnet. Die bisher genannten Blöcke gehören alle zum sogenannten Betriebssystemkern. Auf der dem Kern gegenüberstehenden User-Ebene befinden sich die Applikationen, Dienstprogramme und Libraries. Dienstprogramme sind zum Betrieb des Rechnersystems notwendig. Beispielsweise werden für die Konfiguration des Systems (Zuteilung von Netzwerkadressen u.ä.) und auch zum Betrieb der Netzwerkdienste Dienstprogramme benötigt. Die Programme selbst greifen in den seltensten Fällen direkt über die Systemcall-Schnittstelle auf die Dienste des Betriebssystems zu. Im Regelfall sind diese in einer Bibliothek gekapselt, die Standardfunktionen (beispielsweise open, close, read und write) zur Verfügung stellt. Innerhalb der Library ist dann der Systemcall selbst auscodiert. Natürlich sind die Dienstprogramme des Betriebssystems im strengen Sinne auch Applikationen. Es werden hier nur deshalb zwei Begriffe verwendet, um die zum Betriebssystem gehörigen Applikationen - eben die Daemonen, Services oder einfach Dienstprogramme - von den selbstgeschriebenen Applikationen unterscheiden zu können. Beim Aufbau eines Realzeitsystems erweitert der Ingenieur/Informatiker eventuell an zwei Stellen das Betriebssystem: 1. bei der Integration neuer Hardware (Treiber), 2. bei der Integration zusätzlicher Kernelmodule Betriebssystemkern Im Folgenden werden die einzelnen Komponenten, die ein Betriebssystem bilden, vorgestellt Prozess-Management Die primäre Aufgabe des Prozess-Management ist die Verteilung der Ressource»CPU«. Die Fähigkeit der CPU, den normalen Programmablauf zu unterbrechen, wenn ein Interrupt auftritt, wird genutzt, um die quasi parallele Bearbeitung mehrere Programme (Tasks) zu ermöglichen. Tritt nämlich ein Interrupt auf, legt die CPU den Programmcounter auf den Stack (automatische Rettung des Befehlszählers) und eventuell den Inhalt des (internen) Flagregisters, sperrt weitere Interrupts und ruft die sogenannte Interrupt-Service-Routine (ISR) auf. Die Adressen der ISR s befinden sich dabei in einer 22

28 Kapitel 3. Realzeitbetriebssysteme Tabelle, wobei es für jede mögliche Unterbrechungsursache (z.b. externern Hardwareinterrupt auf Level 0, Softwareinterrupt oder unerlaubter Speicherzugriff) einen Tabelleneintrag (Vektortabelle) gibt. Am Ende der ISR steht der Befehl "Return Interrupt" (iret, reti). Dieser Befehl restauriert wieder das Prozessor-Flag-Register und lädt den zuvor auf den Stack befindlichen Wert des Programmcounters in den Programmcounter, wodurch die Abarbeitung der ursprünglichen Aufgabe fortgesetzt wird. PC Address 0x1000 0x1002 0x1004 0x1006 0x1008 0x100a Normal Program Execution Stack registers 0x ISR 0xa000 0xa002 0xa004 save registers interrupt treatment 3 4 restore registers 5 return interrupt 6 Code Memory PC = Program Counter Abbildung 3-2. Interruptverarbeitung Bild Interruptverarbeitung verdeutlicht die Vorgänge noch einmal: 1. Die CPU arbeitet ein Programm ab. 2. Während der Programmabarbeitung wird (z.b. von der Systemuhr) ein Interrupt ausgelöst. Die Abarbeitung des gerade begonnenen Befehls wird zuende geführt (moderne Prozessoren können bei Auftreten des Interrupts sogar die Abarbeitung des gerade aktuellen Befehls unterbrechen und müssen nicht erst auf das Ende der Befehlsabarbeitung warten). Dann wird der Inhalt des Befehlszählers (0x1006) auf den Stack abgelegt, abhängig vom Prozessor ebenfalls Registerinhalte (beispielsweise das Flagregister). Aufgrund der Interruptursache wird von der CPU der Befehlszähler (PC) auf die Startadresse der Interrupt-Bearbeitungsroutine (hier 0xa000) gesetzt, und die CPU arbeitet die Befehle ab dieser Adresse ab. 3. Die ISR wird zunächst die sonstigen CPU-Register retten. 4. Daran schließt sich die eigentliche Interrupt-Behandlung an. 5. Am Ende der ISR werden die vorher gesicherten CPU-Register wieder zurückgeholt 6. und der Befehl Return Interrupt ausgeführt. Der Befehl bewirkt, dass vom Stack die dort eventuell abgelegten Flagregister zurückgespeichert werden und der PC wieder mit der Rücksprungadresse (0x1006) geladen wird. 7. Der normale Programmablauf wird damit fortgesetzt. 23

29 Kapitel 3. Realzeitbetriebssysteme Interrupt zum Kontextwechsel Retten des Kontextes des unterbrochenen Rechenprozesses j eventuelle Auftragsbearbeitung Scheduler: Auswahl des nächsten Rechenprozesses i Lade Kontext des Rechenprozesses i Return zu PCi Abbildung 3-3. Interrupt-Service-Routine in einem Realzeitbetriebssystem PC Address 0x1000 0x1002 0x1004 0x1006 0x1008 0x100a Normal Program Execution 1 2 RETI Stack 0x x2000 0x2000 Task 3 7 current 0xf020 ISR 0xa000 0xa002 0xa004 save registers interrupt treatment schedule contextswitch return interrupt Code Memory Tasklist complete Task Information complete Task Information complete 0x2000 Task Information 0xf000 0xf020 0xf040 Abbildung 3-4. ISR mit Scheduler In einem Multitasking Betriebssystem wird dieser Mechanismus der Programmunterbrechung genutzt, um die quasi parallele Abarbeitung mehrerer Aufgaben zu ermöglichen. Dazu wird bei jedem Interrupt zunächst eine vom Betriebssystem zur Verfügung gestellte ISR (Bild 24

30 Kapitel 3. Realzeitbetriebssysteme Interrupt-Service-Routine in einem Realzeitbetriebssystem) aufgerufen. In dieser ISR werden die Prozessorregister und die auf dem Stack abgelegten Werte (PC und Flags) in eine zum gerade aktiven Programm gehörigen Datenstruktur (Process Control Block, Bild Process-Kontrollblock (PCB)) abgelegt. Danach kann die eigentliche Ursache des Interrupts bearbeitet werden. Am Ende der Interruptbearbeitung wird - das ist im einfachen Fall noch während der ISR - ein nächstes Programm ausgewählt, das die CPU, den Rechnerkern, zugeteilt bekommt. Dazu werden die Prozessorregister mit den Kopien der Register zum Zeitpunkt der letzten Unterbrechung der Task geladen, Flags und insbesondere der Programmcounter werden so auf dem Stack abgelegt, dass die vom Prozessor bei Eintritt des Interrupts abgelegten Werte überschrieben werden, und es wird schließlich der Befehl Return Interrupt ausgeführt. Dadurch wird der Befehlszähler mit dem neuen Wert geladen, und die Bearbeitung der neuen Task wird fortgesetzt. Unterschied zu einer gewöhnlichen ISR ist damit (Bild ISR mit Scheduler), dass 1. die Register nicht auf den Stack gerettet werden, sondern in die Speicherzellen des Process Control Blocks (PCB) der gerade aktiven Task. Die Adresse des PCB des gerade aktiven Rechenprozesses ist im Regelfall in einer globalen Variable ( current ) abgespeichert. 2. beim Contextswitch die Register des Prozessors nicht zwangsläufig mit den Werten geladen werden, die vor der Unterbrechung geladen waren. Stattdessen werden die Register aus dem PCB genommen, den der Scheduler ausgewählt hat. 3. die Rücksprungadresse auf dem Stack manipuliert wird, d.h. der durch den Prozessor dort bei der Unterbrechung abgelegte Wert wird mit dem Wert für den PC überschrieben, der sich in dem PCB der neuen aktiven Task befindet. Damit ist ersichtlich, dass insbesondere das sogenannte Prozessmanagement, aber eigentlich auch alle anderen Dienste des Betriebssystemkerns über Interrupts angefordert werden. Zwei Arten von Interrupts werden unterschieden: Softwareinterrupts von Hardwareinterrupts. Über Softwareinterrupts (Systemcalls) fordern Benutzerprogramme Dienste des Betriebssystems an. Hierbei handelt es sich um eine synchrone Unterbrechung, da die Unterbrechung synchron zum Programmablauf im Programmcode verankert erfolgt. Über Hardwareinterrupts fordern Hardwarekomponenten (Systemuhr, Platten, Modem usw.) Dienste des Betriebssystems an. Mit jedem Interrupt findet damit ein Übergang in den Kernel-Mode (Supervisor-Mode) statt. In der ISR (Interrupt-Service-Routine) wird zunächst die dem Interrupt zugehörige Aufgabe durchgeführt und danach die Kontrolle dem Scheduler übergeben, der den nächsten RechenProzess auswählt. 25

31 Kapitel 3. Realzeitbetriebssysteme Prozess Kontrollblock/ Virtuelle CPU Prozess Priorität Quantum Prozess Zustand Exitcode CPU Zustand (Register, Stack usw.) Abbildung 3-5. Process-Kontrollblock (PCB) Merke: Dienste des Betriebssystems werden immer über Interrupts angestoßen. Die den Zustand einer Task (also des im System instanziierten Programms) genau beschreibende Datenstruktur Prozess-Control-Block speichert die folgenden Informationen: Speicherplätze, in denen jeweils alle Prozessorzustände (Registerinhalte) gespeichert werden, mit welchen dieser Rechenprozess zum letzten Mal unterbrochen wurde. Angaben über den Zustand des Rechenprozesses, gegebenenfalls über die Bedingungen, auf die der Rechenprozess gerade wartet. Angaben über die Priorität des Rechenprozesses. Der Prozess-Control-Block spezifiziert damit einen Rechenprozess eindeutig. Anhand der Informationen im PCB entscheidet der Scheduler, ob eine Task die CPU zugeteilt bekommt oder nicht. Start Task lauffähig Ende der Wartebedingung ruhend "höchste Priorität" "nicht mehr höchste Priorität" schlafend Stopp Task aktiv Warten, z.b. auf Betriebsmittel E/A Aufruf Ende Zeit Abbildung 3-6. Prozess-Zustände 26

32 Kapitel 3. Realzeitbetriebssysteme Die wichtigste Information im PCB zur Auswahl des nächsten Rechenprozesses ist der Zustand der Task. Jede Task hat dabei im wesentlichen 4 mögliche Zustände, wie im Bild Prozess-Zustände ersichtlich. Ruhend Bei»ruhend«handelt es sich im eigentlichen Sinn nicht um einen Zustand, vielmehr um einen»metazustand«. Bevor ein Rechenprozess überhaupt bearbeitet wird, befindet er sich im Metazustand»ruhend«. Aus diesem Zustand kommt der Prozess nur, wenn er durch das Betriebssystem (welches im Regelfall durch einen anderen Rechenprozess dazu aufgefordert wurde) gestartet wird. Im Metazustand»ruhend«existiert im Kernel noch kein PCB für die Task. Der Metazustand»ruhend«wird oft auch als Zustand terminiert bezeichnet. Lauffähig Aktiv Der Scheduler wählt aus der Liste der Prozesse denjenigen lauffähigen Prozess als nächstes zur Bearbeitung aus, der die höchste Priorität hat. Mehrere Jobs im System können sich im Zustand lauffähig befinden. Immer nur ein Rechenprozess im System kann sich im Zustand aktiv befinden. Der aktive Job bekommt die CPU zugeteilt und wird ausgeführt, bis er entweder sich selbst beendet (in den Zustand ruhend) versetzt, oder auf ein Betriebsmittel warten muss (z.b. auf das Ende eine I/O-Aufrufes), oder nicht mehr die höchste Priorität hat, da beispielsweise die Wartebedingung eines höherprioren Prozesses erfüllt wurde. Schlafend Ein Prozess wird in den Zustand schlafend versetzt, wenn nicht mehr alle Bedingungen zur direkten Ausführung erfüllt sind. Ein Job kann dabei auf unterschiedliche Bedingungen warten, beispielsweise auf das Ende von I/O-Aufrufen, auf den Ablauf einer definierten Zeitspanne oder auf das Freiwerden sonstiger Betriebsmittel. TCB TCB Task Thread Code Segment Data Segment Stack Segment Stack Segment Abbildung 3-7. Speicherbereiche einer Task Rechenprozesse oder Jobs gibt es in zwei Ausprägungen: den Tasks und den Threads. 27

33 Kapitel 3. Realzeitbetriebssysteme Bei Threads handelt es sich um sogenannte leichtgewichtige Prozesse. Sowohl Tasks als auch Threads bestehen prinzipiell aus einem Speichersegment für Code, Daten und für den Stack (Bild Speicherbereiche einer Task). Der Unterschied zur Task ist, dass zu einer Task gehörige Threads alle ein und denselben Adressraum teilen. Das bedeutet also, dass Threads auf dasselbe Daten und Codesegment zugreifen, nur ein eigenes Stacksegment besitzen. Tabelle 3-1. Vergleich Task und Thread Task Eigener PCB Eigenes Codesegment Eigenes Datensegment Eigenes Stacksegment Thread Eigener PCB Codesegment der zugehörigen Task Datensegment der zugehörigen Task Eigenes Stacksegment Aufgrund dieses Unterschiedes lassen sich Threads schneller erzeugen (beim Erzeugen von Tasks werden im Regelfall Datensegmente kopiert, das fällt bei der Erzeugung von Threads weg). Darüber hinaus kann - abhängig von der Applikation - eine vereinfachte Inter-Prozess-Kommunikation (beispielsweise wird kein Shared-Memory benötigt) verwendet werden. Für das Erzeugen von Tasks und das Erzeugen von Prozessen stellt das Betriebssystem Systemcalls zur Verfügung (siehe Taskmanagement). Diese Systemcalls legen einen neuen PCB an und kopieren - falls notwendig - die zugehörigen Speicherbereiche (bei Tasks wird das Datensegment kopiert und nur ein neues Stacksegment angelegt, bei Threads wird nur ein neues Stacksegment angelegt) Scheduling auf Einprozessormaschinen Der Ingenieur/Informatiker ist mit Scheduling konfrontiert bei: Projektierung eines Realzeitsystems (Auswahl des Verfahrens) Zerlegung einer Aufgabe (Applikation) in Rechenprozesse/Threads Festlegung von Prioritäten Evaluierung von Scheduling-Parameters (z.b. Deadlines) Implementierung der Rechenprozesse Unter Scheduling versteht man die Strategie zur Festlegung des nächsten zu bearbeitenden Rechenprozesses. Das Scheduling ermöglicht die parallele Bearbietung mehrerer Tasks. Anhand eines konstruierten Beispiels soll die Aufgabe des Schedulers und die Wirkung bzw. Auswirkung unterschiedlicher Schedulingvarianten vorgestellt werden. Ein Realzeitsystem wird eingesetzt, um ein Experiment an Bord eines Satelliten zu steuern. Dazu sind folgende Anforderungen spezifiziert: Alle 1500ms (Zeitpunkt t 0 ) Meßdaten aufnehmen (t P =1500 ms). Die Meßwertaufnahme muss nach 400 ms abgeschlossen sein (t Dmax =400 ms). Alle 200 ms nach Start der Meßdatenaufnahme t 0 : Experiment anstossen. 28

34 Kapitel 3. Realzeitbetriebssysteme Die Vorbereitungen müssen 500 ms später abgeschlossen sein (also 700 ms nach dem Start der Meßwertaufnahme t 0, t P =1500 ms, t Dmax =500 ms). Meßergebnisse müssen innerhalb von 1100 ms (t Dmax =1100 ms) zur Erde weitergeleitet werden (alle 60s, t P =60000 ms). Zur Steuerung des Experiments steht ein Mikroprozessor zur Verfügung. Die Verarbeitungszeiten für die drei Aufgaben betragen: Task A - Meßdatenaufnahme: t E =250 ms Task B - Datenübertragung: t E =500 ms Task C - Experiment: t E =300 ms Job temax tpmin tdmin tdmax ta A 250ms 1500ms 0ms 400ms 0ms B 300ms 1500ms 0ms 500ms 200ms C 500ms 60000ms 0ms 1100ms 0ms Daten müssen aufgenommen sein Experiment muß vorbereitet sein Übertragung muß abgeschlossen sein Experiment Meßdatenübertragung Parameter aufnehmen 300ms t V 500ms t V 250ms t V t[ms] Abbildung 3-8. Zeitanforderungen (Beispiel) Aufgrund des Beispieles ergeben sich für die Konzeption des Realzeitsystems folgende Folgerungen: Der Prozessor muss mehrere Aufgaben (Tasks) bearbeiten. Dazu muss jeder Prozess unterbrechbar (preemptiv) sein. Der Prozessor bearbeitet abwechselnd jeweils einen Teil einer der zu bearbeitenden Tasks. Der Rechner muss von seiner Leistung her in der Lage sein, die Aufgabenstellung zeitgerecht zu bearbeiten (Auslastung, 1. Echtzeitbedingung). Das Betriebssystem muss sicherstellen, dass die maximal zulässigen Reaktionszeiten eingehalten werden (2. Echtzeitbedingung). Die Einheit im Betriebssystem (oder Laufzeitsystem), die dieses sicherstellt, ist der Scheduler. Unter Scheduling versteht man die Strategien in einem Betriebssystem zur Festlegung desjenigen Rechenprozesses, der als nächstes bearbeitet werden soll. Das Scheduling ermöglicht damit die quasi-parallele Bearbeitung mehrerer Aufgaben (Tasks). Sowohl für die Auswahl des nächsten Rechenprozesses als auch für den eigentlichen Wechsel (Kontexswitch) wird ebenfalls Rechenzeit benötigt. 29

35 Kapitel 3. Realzeitbetriebssysteme T3 T2 T1 CPU Zeit, die der Scheduler (incl. Kontextswitch) benötigt. t[ms] Abbildung 3-9. Prinzip des Schedulings Die Zeit, in denen eine Task den Rechnerkern (CPU) zur Verfügung hat (also rechnen darf), muss in einem vernünftigen Verhältnis zu der Zeit stehen, die für die Auswahl des nächsten Rechenprozesses (Scheduling) und das eigentliche Wechseln zu diesem Rechenprozess (Kontextswitch) verwendet wird. Die Zeiten für das Scheduling bewegen sich im µ-sekundenbereich. Man unterscheidet statisches und dynamisches Scheduling. Statisches Scheduling Festlegung eines Fahrplans (im vorhinein), nach dem die einzelnen Rechenprozesse in einem festen Schema abzuarbeiten sind. Einsatz: sicherheitskritische Anwendungen (z.b. Flugzeug-Steuerungen), da die Einhaltung von Realzeitbedingungen formal nachgewiesen werden kann; außerdem in Speicher- Programmierbaren-Steuerungen. Dynamisches Scheduling Zuteilung des Prozessors an Rechenprozesse durch den im Betriebssystem enthaltenen Scheduler aufgrund der jeweils aktuellen Bedarfssituation. Rechenprozesse müssen damit preemptiv (unterbrechbar) sein. Als Scheduler bezeichnet man die Einheit, die für die Zuteilung von Rechenzeit (CPU-Zeit) an Prozesse/Threads zuständig ist. Bei Multi-Prozessor-Systemen verteilt der Scheduler die Prozesse/Threads zusätzlich auch auf Prozessoren Scheduling Points In folgenden Situationen muss der Scheduler überprüfen, ob ein anderer Prozess die CPU erhalten sollte und gegebenenfalls einen Kontextwechsel veranlassen: Ende einer Systemfunktion (Übergang Kernel/User Mode): Die Systemfunktion hat den aktiven Prozess blockiert (z.b. Warten auf Ende von I/O) In der Systemfunktion sind die Scheduling-Prioritäten geändert worden. Der aktive Rechenprozess terminiert. Interrupts Timer-Interrupt: der aktive Prozess hat sein Quantum verbraucht (Round Robin). I/O signalisiert das Ende einer Wartebedingung (höher priorer Prozess wird Bereit ). 30

36 Kapitel 3. Realzeitbetriebssysteme Bewertungskritierien Gerechtigkeit Jeder Prozess soll einen fairen Anteil der CPU-Zeit erhalten. Effizienz Die CPU soll möglichst gut ausgelastet werden. Durchlaufzeit Ein Prozess soll so schnell wir möglich abgeschlossen sein. Durchsatz Es sollen so viele Jobs wie möglich pro Zeiteinheit ausgeführt werden. Antwortzeit Die Reaktion auf Ereignisse soll möglichst schnell erfolgen. Determinismus Das Scheduling als solches soll berechenbar sein. Anmerkung: Während für Standard-Betriebssysteme Gerechtigkeit, Effizienz, Durchlaufzeit und Durchsatz eine wesentliche Rolle spielen, sind für Realzeitbetriebssysteme insbesondere die Kriterien Antwortzeit und Determinismus entscheidend First Come First Serve (FCFS) D C B A Prozeß A ist aktiv D C D C B A A B Zeit Prozeß A blockiert, Prozeß B ist aktiv Prozeß A ist wieder bereit, wird eingereiht Abbildung Prinzip des FCFS Schedulings Prinzip Die bereiten Prozesse sind in einer Warteschlange nach ihrem Erzeugungszeitpunkt geordnet. Jeder Prozess darf bis zu seinem Ende laufen, außer er geht in den Zustand Blockiert über. Geht ein Prozess vom Zustand Blockiert in den Zustand Bereit über, wird er entsprechend seinem Erzeugungszeitpunkt wieder in die Warteschlange eingereiht, unterbricht aber den laufenden Prozess nicht. 31

37 Kapitel 3. Realzeitbetriebssysteme Deadline T1 Deadline T2 Deadline T3 B C A bereit aktiv Abbildung First Come First Serve Scheduling Anwendungen Batch-Systeme um gleiche mittlere Wartezeiten für alle Prozesse zu erreichen. Realzeiteigenschaften: nicht für Realzeitsysteme geeignet, da ein Prozess alle anderen blockieren kann! Round Robin Scheduling Prinzip Alle Prozesse werden in eine Warteschlange eingereiht. Jedem Prozess wird eine Zeitscheibe (time slice, quantum) zugeteilt Ist ein Prozess nach Ablauf seines Quantums noch im Zustand Aktiv, wird der Prozess verdrängt (preempted), d.h. in den Zustand lauffähig versetzt; wird der Prozess am Ende der Warteschlange eingereiht; wird dem ersten Prozess in der Warteschlange die CPU zugeteilt. Geht ein Prozess vom Zustand schlafend in den Zustand lauffähig über, so wird er am Ende der Warteschlange eingereiht. Kriterien für die Wahl des Quantums Das Verhältnis zwischen Quantum und Kontextwechselzeit muss vernünftig sein. Großes Quantum: effizient, aber lange Verzögerungszeiten und Wartezeiten möglich. Kleines Quantum: kurze Antwortzeiten, aber großer Overhead durch häufige Prozessumschaltung. 32

38 Kapitel 3. Realzeitbetriebssysteme Round Robin Deadline A Deadline B Deadline C B C A Rechenprozess aktiv Rechenprozess bereit t[ms] Abbildung Round Robin Schedulings Realzeiteigenschaften: Dynamisches Scheduling Da die Anzahl der bereiten Rechenprozesse nicht bekannt ist, dann der Abarbeitungszeitpunkt eines Prozesses nicht vorhergesagt werden (nicht deterministisch). Asynchrone Anforderungen werden nicht direkt bedient. Daher ist das Verfahren für Realzeitsysteme nicht geeignet. Statisches Scheduling In einer Abwandlung des Verfahrens (TDMA=Time Division Multiple Access) sind die Zeitscheiben starr und fest. Sind die Prozesse im System im vornherein bekannt, kann sich das Verfahren, abhängig von der Aufgabenstellung (z.b. SPS) für Realzeitsysteme eignen Prioritätengesteuertes Scheduling Deadline T1 Deadline T2 Deadline T3 B C A Task aktiv Task bereit Prioritäten: A = 5 (höchste) C = 2 (niedrigste) B = 4 (mittlere) t[ms] Abbildung Prioritätengesteuertes Schedulings Prinzip Für jeden Prozess wird eine Priorität vergeben. 33

39 Kapitel 3. Realzeitbetriebssysteme Der Prozess mit der höchsten Priorität bekommt die CPU. Realzeiteigenschaften: Für Realzeitsysteme geeignet, insbesondere wenn keinerlei Informationen bezüglich der maximal zulässigen Reaktionszeiten zur Verfügung stehen. Allerdings gibt es die Schwierigkeit, die Prioritäten richtig zu verteilen (siehe dazu auch Schritthaltende Verarbeitung) Deadline-Scheduling Prinzip Der Rechenprozess mit der dem Momentanzeitpunkt am nächsten gelegenen Deadline (maximal zulässige Reaktionszeit) bekommt die CPU zugeteilt. Ist Zeit t Dmax =Deadline t ts t E t Dmax t E ts zu diesem Zeitpunkt muß die Task bearbeitet sein Verarbeitungszeit (ohne Wartezeit) Zeit Spielraum bis zum spätestmöglichen Start der Verarbeitung (laxity) Abbildung Prinzip des Deadline Scheduling D1 D2 D3 D T T2 T3 T4 t Abbildung Beispiel für Deadline Scheduling 34

40 Kapitel 3. Realzeitbetriebssysteme Deadline A Deadline B Deadline C B C A Task aktiv Task bereit t[ms] Abbildung Realzeiteigenschaften beim Deadline Scheduling Realzeiteigenschaften: Das Verfahren führt zur Einhaltung der maximalen Reaktionszeiten, wenn dies überhaupt möglich ist (optimales Verfahren)! Nachteil: Deadlines (sprich die maximal zulässigen Reaktionszeiten) sind nicht immer bekannt POSIX b Prinzip Prioritätengesteuertes Scheduling Auf jeder Prioritätsebene können sich mehrere Prozesse befinden. Innerhalb einer Prioritätsebene werden Prozesse gescheduled nach: First In First Out (First Come First Serve) Round Robin Prioritätsebene 0 besitzt die niedrigste Priorität. Scheduling Strategie (Policy) Queue Prioritätsebene SCHED_RR Max Prio SCHED_FIFO SCHED_RR SCHED_RR 42 Prozeß mit der PID Abbildung Posix Scheduling Zur Parametrierung des Schedulers stehen die folgenden Funktionen zur Verfügung: 35

41 Kapitel 3. Realzeitbetriebssysteme sched_setparam Setzt die Task-Priorität sched_getparam Liest die Schedulingparameter einer Task sched_setscheduler Setzt die Schedulingparameter für eine Task sched_yield Gibt die CPU frei (erzwingt Scheduling) sched_getscheduler Liest die aktuelle Schedulingstrategie sched_get_priority_max Liest die maximal mögliche Priorität sched_get_priority_min Liest die minimale Priorität sched_rr_get_interval Ergibt die Dauer des Zeitintervalls bei Round Robin Scheduling Informationen zur Programmierung des Schedulings und zum Setzen der Priorität finden Sie im Abschnitt Taskmanagement. Unter Linux gibt es das Programm chrt, mit dem dir Priorität eines anderen Programms während der Laufzeit modifiziert werden kann Scheduling in VxWorks VxWorks bietet prinzipiell zwei Verfahren zur Auswahl: Wind-Task Scheduling POSIX-Scheduling Meist wird das Wind Scheduling eingesetzt. Dieses ist durch folgende Kenndaten geprägt: Preemptives Prioritäten Scheduling 256 Prioritätsebenen (0=höchste, 255=niedrigste Priorität) Round Robin Scheduling Innerhalb einer Prioritätsebene wird Round Robin Scheduling verwendet. 36

42 Kapitel 3. Realzeitbetriebssysteme Priorität hoch time slice T4 niedrig T1 T2 T3 T1 T2 T2 T3 t 1 t Abbildung Beispiel für Wind Scheduling Die Unterschiede zum Posix-Scheduling sind die folgenden: Der POSIX-Scheduler in VxWorks scheduled Prozesse, der Wind-Scheduler Threads. Die Prioritätenbezeichnungen (hoch, niedrig) sind einander invers. In POSIX bedeutet eine hohe Zahl (z.b. 255) eine hohe Priorität, in Wind-Scheduling eine niedrige. Unter Wind-Scheduling ist das Schedulingverfahren innerhalb einer Priorität immer gleich. Das Betriebssystem bietet zur Parametrierung des Schedulers die folgenden Funktionen an: kerneltimeslice Einstellung der RR-Parameter taskpriorityset Modifikation der Taskpriorität tasklock Scheduling ausschalten taskunlock Scheduling einschalten Der O(1) Scheduler im Linux-Kernel 2.6. Weiche Echtzeitsysteme, wie sie beispielsweise Multimediasysteme darstellen, erfordern als zusätzliche Eigenschaft von einem Scheduler Interaktivität. Interaktivität bedeutet, dass man die Zeitscheibe, die man einzelnen Tasks zur Verfügung stellt, gemäß der Anforderungen an Interaktivität variiert. Sehr interaktive Tasks bekommen eine kurze Zeitscheibe und kommen damit häufiger dran. Rechenintensiven Tasks wird dagegen eine lange Zeitscheibe zugeteilt, falls diese gescheduled werden, dürfen diese auch länger rechnen. Bei den immer größer werdenden Caches kommt dieses insbesondere der Verarbeitungsgeschwindigkeit zu gute, schließlich müssen die Caches nicht so häufig gefüllt werden. Da man im Fall des dynamischen Schedulings im vorhinein nicht die Verarbeitungszeiten und die Anforderungzeitpunkte der einzelnen Rechenprozesse kennt, muss man auf statistische Informationen aus der Vergangenheit zurückgreifen. Der O(1) Scheduler von Ingo Molnar wird folgendermassen beschrieben: hybrid priority-list and round-robin design with an array-switch method of distributing timeslices and per-cpu runqueues. 37

43 Kapitel 3. Realzeitbetriebssysteme Der Scheduler basiert prinzipiell auf einem Round Robin Verfahren. Alle rechenbereiten Prozesse sind in einer Warteschlange (hier als array implementiert) gemäß ihrer Priorität eingehängt. Im System existieren pro CPU jeweils zwei Warteschlangen: eine für die aktiven und eine für die Tasks, deren Zeitscheibe abgelaufen ist (expired) active expired Normale Priorität Realzeit Priorität Prozesslisten-Bitmap Es existiert kein rechenbereiter Prozess. Mindestens 1 rechenbereiter Prozess existiert. Berücksichtigung von Interaktivität PCB PCB PCB Listen rechenbereiter Prozesse Abbildung O(1) Scheduler im Linux-Kernel Sobald die Zeitscheibe des aktiven Rechenprozesses abgelaufen ist, wird dieser in das zweite Array (expired) gemäß seiner Priorität eingetragen. Ist das erste Array abgearbeitet (sprich leer), werden die beiden Arrays umgeschaltet. Das ehemals expired-array wird zum active-array und das ehemals active-array zum expired-array. Die Priorität innerhalb des Arrays ergibt sich aus dem Nice-Wert, den eine Task besitzt und einem Interaktivitäts-Bonus bzw. -Malus. Tasks die viel Rechenzeit verbrauchen bekommen eine größere Zeitscheibe, allerdings bei geringerer Priorität. Die verbrauchte Rechenzeit wird über den Load-Wert innerhalb der letzten 4 Sekunden bestimmt. Auch wenn das vorgestellte Verfahren sehr effizient ist, happert es doch mit der Bewertung von rechenintensiven, interaktiven Tasks, wie Tasks zum Video- oder Audiostreaming bzw. Tasks zur Oberflächendarstellung. Diese Tasks (X-Windows beispielsweise) mussten zunächst durch den Administrator als interaktiv gekennzeichnet werden. Eine erweiterte Heuristik hat dieses überflüssig gemacht. So bekommen ebenfalls die Tasks einen Interaktivitätsbonus, die sehr interaktive Tasks aufwecken. Damit ist nach bisherigen Erfahrungen eine ausgesprochen gute Erkennungsrate für interaktive Rechenprozesse zu erreichen. Die typische Zeitscheibe des Schedulers beträgt 200ms. Alle 250ms ist ein Loadbalancer aktiv, der dafür sorgt, dass die Last im Fall eines Mehrprozessorsystems gleichmässig auf die Prozessoren verteilt wird Scheduling auf Mehrprozessormaschinen Insgesamt sind drei unterschiedliche Mehrprozessorarchitekturen zu unterscheiden: 1. Simultaneous Multithreading (SMT) 38

44 Kapitel 3. Realzeitbetriebssysteme Diese von Intel eingeführte Architektur hat meist zwei Registersätze und Pipelines, so dass sehr leicht zwischen zwei Threads umgeschaltet werden kann. Allerdings gibt es nur eine Verarbeitungseinheit. Der Performancegewinn wird allgemein mit bis zu 10 Prozent angegeben. Im strengen Sinn ist das natürlich kein wirkliches Multiprozessor-System. 2. Symmetric Multi-Processing (SMP) Mehrere Prozessorkerne besitzen einen Adressraum. 3. Non-uniform Memory Architecture (NUMA) Einzelne Prozessoren können auf unterschiedliche Speicherbereiche unterschiedlich schnell zugreifen. CPU 0 Thread busy Auswahl über O(1) Migration gemäß Multi CPU Scheduling CPU 1 Auswahl über O(1) idle Abbildung Der übergeordnete Mehrprozessorscheduler verteilt die Jobs auf die einzelnen Prozessoren. Ziel des Mehrprozessorschedulings ist es, die Last auf allen Prozessoren im System gleichmässig zu verteilen. Grundsätzlich ist das Scheduling zweistufig aufgebaut: Auf jedem Prozessor arbeitet der Einprozessor-Scheduler. Der Mehrprozessorscheduler verteilt die Rechenprozesse auf die einzelnen Prozessoren, so dass der Einprozessorscheduler sich aus der Liste der ihm zugeteilten Jobs den als nächstes von ihm zu bearbeitenden heraussucht. Der Mehrprozessorscheduler ist häufig als eigener Prozess realisiert. Das Verschieben eines Rechenprozesses von einer auf die andere CPU wird als Prozessmigration bezeichnet. An folgenden Stellen respektive zu den folgenden Zeitpunkten wird der Mehrprozessorscheduler aktiv: 1. Bei Aufruf der Systemcalls fork(), clone(), exec() und exit(). 2. Wenn eine CPU idle wird, wenn also die Liste der zu bearbeitenden Rechenprozesse leer geworden ist. 3. Zeitgesteuert (periodisch). Auch bei einer Ungleichverteilung der Last ist eine Prozessmigration nicht unbedingt wünschenswert, da diese zunächst mit Verlusten bezahlt werden muss. Bei einem SMP-System wird beispielsweise bei einer Prozessmigration der Inhalt sämtlicher Caches verloren. Am preiswertesten ist die Prozessmigration auf einem SMT-System; hier ist allerdings der Gewinn auch am geringsten. Am teuersten ist die Verschiebung auf einem NUMA-System; hier sind aber auch die Gewinne möglicherweise am größten. 39

45 Kapitel 3. Realzeitbetriebssysteme CPU 2, 3 CPU 0, 1 struct sched_domain {... ; flags = SD_LOAD_BALANCE... SMP Level 1 Domain parent parent struct sched_group CPU CPU CPU CPU SMT Base Level Domain Abbildung Die Modellierung eines SMP-Systems im Linux-Kernel. Da in der Praxis meist gemischte Hardware-Architekturen vorkommen, führte der Linux-Kernel so genannte Scheduling-Domains und Scheduling-Gruppen ein. Eine Scheduling-Domain ist der Container für (untergeordnete) Scheduling-Gruppen. Eine Scheduling-Gruppe wiederum steht für eine CPU oder für eine andere (untergeordnete) Scheduling-Domain. Ein Zweiprozessorsystem beispielsweise modelliert Linux in einer Scheduling-Domain mit zwei Gruppen; für jede CPU eine. Ein Rechner mit Hyperthreading-Prozessor wird übrigens ebenfalls auf eine Domain und zwei Gruppen abgebildet. Ein heterogenes System dagegen besteht aus mehreren Domains, wobei die Scheduling Gruppen der übergeordneten Container (Domains) nicht Prozessoren, sondern eben weitere Domains repräsentieren. Ein einfaches Beispiel für eine derartig baumartige Topologie finden Sie in Abbildung Die Modellierung eines SMP-Systems im Linux-Kernel.. Hier ist eine Architektur mit zwei Hyperthreading-Prozessoren mit Hilfe dreier Scheduling-Domains abgebildet: Zwei Basis-Domains und die übergeordnete Level 1 Domain. Die Level 1 Domain umspannt alle vier Prozessoren, die Basis-Domains jeweils einen Hyperthreading Prozessor mit seinen zwei logischen Kernen. Der Mehrprozessor-Scheduler sorgt jetzt immer für die Lastverteilung innerhalb einer Scheduling-Domain. Die Entscheidung über eine Migration wird dabei abhängig von der Last innerhalb der Scheduling-Gruppen, den Kosten für die Migration und des erwarteten Gewinns gefällt. In Unix-Systemen kann über die Funktionen sched_get_affinity() und sched_set_affinity() die Affinität, also die Zugehörigkeit eines Threads zu einer CPU ausgelesen und festgelegt werden. Damit ist also die Zuordnung eines Threads auf eine spezifische CPU möglich. Durch diesen Mechanismus können Echtzeitprozesse von Prozessen ohne strenge zeitliche Anforderungen separiert und damit deterministischer abgearbeitet werden. Das folgende Programm zeigt, wie unter Linux die Affinität ausgelesen und schließlich auch gesetzt wird. Durch den Code wird eine Prozessmigration erzwungen. #include <stdio.h> #include <stdlib.h> #define USE_GNU #include <sched.h> #include <errno.h> int main( int argc, char **argv ) { cpu_set_t mask; unsigned int cpuid, pid; if( argc!= 3 ) { fprintf(stderr,"usage: %s pid cpuid\n", argv[0]); return -1; 40

46 Kapitel 3. Realzeitbetriebssysteme pid = strtoul( argv[1], NULL, 0 ); cpuid = strtoul( argv[2], NULL, 0 ); CPU_CLR( cpuid, &mask ); // Aktuelle CPU verbieten. if( sched_setaffinity( pid, sizeof(mask), &mask ) ) { perror("sched_setaffinity"); // UP-System??? return -2; return 0; // vim: aw ic ts=4 sw=4: Memory-Management Aufgaben der Memory-Management-Unit ist der Speicherschutz, die Adressumsetzung, virtuellen Speicher zur Verfügung stellen und Zugriff auf erweiterte Speicherbereiche (Highmem). Speicherschutz Applikationen (Prozesse, aber nicht Threads) werden voreinander geschützt, indem jeder Prozess seinen eigenen Adressraum bekommt. Zugriff ist damit nur möglich auf eigene Daten-, Stack- und Codesegmente. Daten bzw. Teile der Applikation werden vor Fehlzugriffen geschützt. Greift eine Applikation auf Speicherbereiche zu, die nicht zur Applikation gehören, oder versucht die Applikation aus einem Codesegment Daten zu lesen, führt dies - dank MMU - zu einer Ausnahmebehandlung. Adressumsetzung Programme sollen einen einheitlichen Adressraum bekommen, um das Laden von Programmen zu beschleunigen und Shared-Libraries zu ermöglichen. War in früheren Zeiten der Loader des Betriebssystems, der Applikationen in den Speicher geladen und danach gestartet hat, dafür verantwortlich, dem Programm die richtigen (freien) Adressen zuzuweisen, kann mit MMU der Linker bereits die Adressen vergeben. Aus Sicht jeder Applikation beginnt der eigene Adressraum ab der Adresse 0. Mehrere Tasks können sich ein (Code-) Segment teilen. Durch die Trennung von Code- und Datensegmenten und mit Hilfe der MMU können mehrere Tasks, die auf dem gleichen Programm beruhen, ein oder mehrere Codesegmente teilen. Dadurch wird der Hauptspeicherbedarf reduziert. Virtuellen Speicher zur Verfügung stellen Durch die MMU kann virtueller Speicher zur Verfügung gestellt werden. Damit können Applikationen auf mehr Speicher zugreifen, als physikalisch vorhanden ist. Als Speicherersatz wird Hintergrundspeicher (Festplatte) genommen. Der verwendete Hintergrundspeicher wird Swap-Space genannt. Der vorhandene Hauptspeicher wird durch die Speicherverwaltung in Seiten (oder so 41

47 Kapitel 3. Realzeitbetriebssysteme genannte Kacheln) eingeteilt, und wenn keine freien Kacheln mehr zur Verfügung stehen werden die Inhalte belegter Kacheln auf den Hintergrundspeicher ausgelagert. Dieser Vorgang wird Paging oder Swapping genannt. Die freigeräumte Kachel kann nach dem Freiräumen genutzt werden. In Realzeitsystemen wird Swapping nur bedingt eingesetzt, da es zu Undeterminismen führt. Zugriff auf erweiterte Speicherbereiche Für einige Applikationen reicht der Adressraum von 16- und 32-Bit Prozessoren mit 64kByte und 4GByte nicht aus. Die Prozessorhersteller haben Techniken eingebaut, um physikalisch mehr Speicher anzuschließen (bei x86-prozessoren heisst die Technik Process-Adress-Extension, PAE). Die Speicherverwaltung muss Techniken zur Verfügung stellen, diesen erweiterten Speicherbereich oft auch als Highmem bezeichnet ansprechen zu können. MMU s bestehen aus Hardware, die durch entsprechende Software initialisiert werden muss. Heutige Mikroprozessoren haben im Regelfall eine MMU integriert. Prinzipiell funktioniert der Vorgang so, dass der Prozessorkern eine logische Adresse erzeugt. Diese logische Adresse wird durch die MMU in eine physikalische Adresse umgesetzt. Die Umsetzungsregeln selbst werden dazu in die MMU geladen. Eine MMU muss also durch das Betriebssystem initialisiert werden. Prinzipiell kann eine MMU den physikalischen Speicher auf zwei Arten aufteilen: in Segmente und in Seiten. Während Speicher-Seiten feste (physikalische) Größen haben und einander auch nicht überlappen, ist die Größe der Segmente ebenso dynamisch, wie ihre wirkliche Lage im Hauptspeicher. Physik. CPU (Prozessorkern) CB AB MMU CB AB Speicher Logische Adresse Physikalische Adresse MMU = Memory Management Unit AB=Adreßbus CB=Controlbus Abbildung Adressumsetzung mittels MMU 42

48 Kapitel 3. Realzeitbetriebssysteme Segmentierung Logische Adresse 16bit + 20bit Physikalische Adresse Basis Adresse 20bit 0000 Code Segment 0000 Stack Segment 0000 Data Segment 0000 Extra Data Segment Basis Adreß Register Abbildung Segmentierung beim 8086 Insbesondere mit den Intel 8086 Prozessoren sind Speicherverwaltungseinheiten, die auf Segmenten basieren, eingeführt worden. Die Segmentierung diente hierbei vor allem der Erweiterung des logischen (man merke, nicht des physikalischen!) Adressraums (der physikalische Speicher war/ist größer als der logische Adressraum). Man findet diese Methode auch heute noch häufig bei einfachen (8 oder 16 bit) Mikroprozessoren vor und - in Kombination mit Paging - auch bei den modernen Prozessoren. Damit spielt die Segmentierung immer noch - insbesondere bei der Realisierung eingebetteter Systeme - eine große Rolle. Ein Segmentregister kennzeichnet die Anfangsadresse (Basisadresse) eines Segmentes. Diese Anfangsadresse wird zu jeder vom Prozessor erzeugten logischen Adresse hinzuaddiert. Um eine Erweiterung des Adressraumes zu erhalten, ist es natürlich notwendig, dass die Basisadresse breiter ist als die vom Prozessor erzeugte logische Adresse. Beim 8086 beispielsweise werden 16bit logische Adressen erzeugt. Die Segmentregister sind jedoch 20bit breit, wobei die letzten 4bit grundsätzlich auf 0 gesetzt sind. Damit können Segmente immer nur auf Adressen beginnen, die durch 16 teilbar sind. Da von den 20bit der Basisadresse nur 16bit variabel sind, können diese 16bit durch einen entsprechenden Maschinenbefehl belegt werden. Im System werden meistens mehrere Segmentregister vorgehalten. Beim 8086 sind dies beispielsweise: ein Segmentregister für Code, zwei für Daten und eines für den Stack. Zu Beginn einer Applikation werden die Segmentregister belegt, danach kann die Applikation mit einem Maschinenbefehl auf Speicherzellen zugreifen, solange sie innerhalb des logischen Adressraums (beim kByte) bleibt. Benötigt die Applikation jedoch mehr als 64KByte Hauptspeicher, muss vor dem Zugriff das Segmentregister wieder umgeladen werden; ein zeitaufwendiger Vorgang. Übrigens ist das Umladen der Segmentregister die Ursache für die Einführung der Memory-Modelle bei Prozessoren, die ihren physikalischen Speicherbereich durch Segmentierung erweitern. Die Memory-Modelle (Small, Medium, Large und Huge) bestimmen, welche Segmente größer als 64 KByte sein dürfen und welche nicht. Die Modelle Large und Huge allerdings unterscheiden sich nur darin, ob im C-Programm verwendete Pointer normalisiert und damit vergleichbar gemacht werden oder nicht. 43

49 Kapitel 3. Realzeitbetriebssysteme Segment Offset 0200 : 0004 Segment Offset 0000 : 2004 Segment Offset 0100 : 1004 Phys.Adresse x x x02004 Normalisieren: SegmentRegister = SegmentRegister + (Offset >> 4); Offset = Offset & 0x000F; Abbildung Beispiel zum Normalisieren Problem: eine physikalische Adresse kann über mehrere logische Adressen ausgedrückt werden. Das erschwert einen Pointer-Vergleich. Pointer müssen erst normalisiert werden. Dieser Vorgang ist zeitaufwendig und fehleranfällig. Die Segmentierung bietet keinen besonderen Speicherschutz, abgesehen davon, dass zunächst einmal - eine richtige Belegung der Segmentregister vorausgesetzt - ein Zugriff auf das Codesegment mit normalen Datenlesebefehlen nicht möglich ist Seitenorganisation Bei der Seitenorganisation wird der Hauptspeicher in gleich große (beispielsweise 4KByte) Seiten bzw. Kacheln eingeteilt. Die vom Prozessor erzeugte logische Adresse selbst besteht aus zwei semantischen Einheiten, einem Teil, der innerhalb der MMU den sogenannten Seitendeskriptor (Kachelbeschreibung) auswählt, und einem Teil, der eine Speicherzelle innerhalb der Kachel auswählt. Seitendeskriptoradresse Seitenoffset Abbildung Strukturierung der logischen Adresse 44

50 Kapitel 3. Realzeitbetriebssysteme Zugriffsrechte Kachel Nummer Schreib Zugriff erlaubt Daten Zugriff erlaubt Code Zugriff erlaubt Valid (Kachel im Speicher vorhanden) Abbildung Aufbau eines Seitendeskriptors Der Seitendeskriptor besteht ebenfalls aus zwei semantischen Teilen. Der erste Teil enthält Flags, die die Zugriffsrechte kontrollieren, der zweite Teil enthält die eigentliche physikalische Seitenadresse. Im Regelfall existieren vier Flags: Schreibflag Ist dieses Flag gesetzt, darf auf die Seite schreibend zugegriffen werden. Versucht der Prozessor auf die Seite zu schreiben, obwohl das Flag zurückgesetzt ist, wird ein Fehler (Bus-Error) ausgelöst. Daten-Zugriff Dieses Flag gibt an, ob die Seite Daten enthält oder nicht. Ist das Flag gültig gesetzt, darf der Prozessor aus dem Segment Daten lesen. Ist das Flag ungültig gesetzt, löst ein Lesezugriff einen Fehler (Bus- Error) aus. Code-Zugriff Dieses Flag gibt an, ob die Seite Code enthält oder nicht. Ist das Flag gültig gesetzt, darf der Prozessor die Daten in seinen Programmspeicher laden. Ist das Flag ungültig gesetzt und der Prozessor versucht aus der Kachel Code zu lesen, wird ein Fehler (Bus-Error) ausgelöst. Kachel im Speicher Dieses Flag gibt an, ob sich die Seite im Speicher befindet oder (auf den Hintergrundspeicher) ausgelagert wurde. Befindet sich die Seite nicht im Hauptspeicher, muss ein Fehler-Signal (Bus-Error) ausgelöst werden, so dass das Betriebssystem für das Nachladen der Seite sorgen kann. Die MMU ist entweder im Prozessor integriert und über eigene Befehle ansprechbar, oder sie befindet sich als eigener Baustein im Adressraum des Prozessors. In diesem Fall ist sichergestellt, dass beim Startup des Systems die MMU einen definierten Zustand hat, so dass auch ohne Vorbelegung das Betriebssystem die Möglichkeit hat, die MMU zu initialisieren. Wie aus der Zeichnung ersichtlich, wird der Zugriff auf den Hauptspeicher durch die MMU verzögert, da die MMU selbst auch eine Zugriffszeit besitzt. Moderne Mikroprozessoren verwenden als MMU-Speicher den Hauptspeicher, so dass im ungünstigen Fall mehrere Hauptspeicherzugriffe notwendig sind, um schließlich das gewünschte Datum zu bekommen. Eine MMU, die auf Basis der Aufteilung des Speichers in Seiten funktioniert, kann leicht mit Hilfe von Speicherbausteinen aufgebaut werden. Der Speicher beinhaltet die Seitendeskriptoren. Mit den oberen Bits der logischen Adresse wird der Speicher adressiert. Die Daten, die daraufhin am Datenausgang (Datenbus) des Speicherbausteins sichtbar werden, werden teils als Adresse der Kachel, teils als Kontrollsignale zur 45

51 Kapitel 3. Realzeitbetriebssysteme Verifikation der Zugriffsrechte, verwendet. Konkret wird also ein Teil des Datenbusses auf den Adressbus gemappt. Der Datenbus selbst muss gemultiplext sein, d.h. damit der MMU-Speicher auch belegt werden kann, muss der Datenbus auch mit dem Datenbus des Prozessorkerns verbunden werden. Ein Teil der Datenbits eines Seitendeskriptors wird mit den Zugriffs-Kontrollsignalen des Prozessorkerns (R/W, C/D) verknüpft. Diese Verknüpfung erzeugt dann entweder die Zugriffskontrollsignale für den physikalischen Teil des Adressbusses oder aber das Bus-Error-Signal. Adressumsetzspeicher Auswahl/ Adressierung des Seiten Deskriptors logische Adresse (32bit) Zugriffsrechte Kacheladresse Adresse/Offset innerhalb der Kachel (Seite) Auswahl/ Adressierung der Kachel (Seite) physikalische Adresse (24bit) Abbildung MMU auf Basis von Seitenadressierung I/O Subsystem Das I/O Subsystem hat vielfältige Aufgaben. Aus Applikationssicht schafft es eine Schnittstelle für den einheitlichen Zugriff auf unterschiedlichste Hardware, aus Hardwaresicht stellt es eine Umgebung zur Verfügung, um mit relativ wenig Aufwand Hardware systemkonform in den Kernel zu integrieren. Daneben ist das I/O Subsystem aber auch noch für die Organisationsstrukturen auf Hintergrundspeicher, den sogenannten Filesystemen, zuständig Gerätezugriff auf Applikationsebene Das Betriebssystem stellt auf Applikationsebene ein Systemcallintferface zur Verfügung, um auf Geräte zuzugreifen. Dazu gehören im wesentlichen Funktionen um Geräte zu initialisieren, um Daten zu den Geräten zu transferieren und Daten von den Geräten zu lesen. Die Applikationsschnittstelle zum I/O- Subsystem ist so gestaltet, dass sie für beinahe jegliche Art von Geräten nutzbar ist Schnittstellenfunktionen Ähnlich wie in der Hardware werden für die Applikation Zugriffe auf Peripherie auf ein einfaches Lesen und Schreiben (read und write) abgebildet. Bevor jedoch auf ein Gerät geschrieben bzw. von einem Gerät gelesen werden kann, muss dazu die Ressource beim Betriebssystem angefordert werden. Bei der Anforderung 46

52 Kapitel 3. Realzeitbetriebssysteme entscheidet das System, ob die Task den Zugriff bekommt oder ob der Zugriff abgelehnt wird. Gründe für eine Ablehnung können sein: fehlende Zugriffsrechte die Ressource ist bereits belegt. #define ZUMACHEN 0x00 #define AUFMACHEN 0x01... char Tuere;... fd=open( Aufzugtuer, O_RDWR ); Tuere = AUFMACHEN; write( fd, &Tuere, sizeof(tuere) ); // Tuere auf... close( fd ); Abbildung Geräteschnittstelle Die klassischen Zugriffsfunktionen (ANSI-C) für den Zugriff auf Geräte sind die gleichen wie für den Zugriff auf Dateien (Geräte werden also an dieser Schnittstelle wie Dateien gehandhabt!): open Mit open wird der spätere Zugriffswunsch auf ein Gerät beim Betriebssystem angemeldet. Das Gerät selbst wird in symbolischer Form spezifiziert. Außerdem muss angegeben werden, in welcher Form (lesend/schreibend) auf das Gerät zugegriffen werden soll, und in welcher Art (z.b. blockierend oder nicht blockierend). open liefert als Ergebnis einen sogenannten Descriptor zurück, wenn das Betriebssystem die Ressource der Task zuteilt. Dieser Descriptor wird von den folgenden Diensten als Kennung verwendet, so dass die produktiven Dienste (read/write) effizient bearbeitet werden können (Bild Geräteschnittstelle). close Dieser Systemcall gibt die angeforderte Ressource wieder frei. read Zum lesenden Zugriff wird der Systemcall read verwendet. write Mit dieser Funktion können Daten an die Peripherie übermittelt werden. ioctl Läßt sich eine bestimmte Funktionalität, die ein Peripheriegerät besitzt, nicht auf Schreib-/Leseaufrufe abbilden, steht der IO-Control-Aufruf zur Verfügung. Dieser Aufruf ist gerätespezifisch. Über IO- Controls werden beispielsweise die verschiedenen Betriebsparameter und Betriebsarten einer seriellen Schnittstelle konfiguriert oder auch ein atomares Lesen und Schreiben ermöglicht. 47

53 Kapitel 3. Realzeitbetriebssysteme Zugriffsarten Da man bei Echtzeitbetriebssystemen darauf achten muss, dass das Warten auf Ereignisse das System selbst nicht blockiert, lassen sich Aufrufe, die zum Warten führen können, unterschiedlich parametrieren. Insgesamt lassen sich drei Aufrufarten unterscheiden: 1. Aufruf mit implizitem Warten (blockierender Aufruf) 2. nicht blockierender Aufruf 3. Aufruf mit explizitem Warten Normalerweise wird die Task, die auf ein Gerät zugreift, solange schlafen gelegt, bis das Gerät geantwortet hat, oder eine Fehlersituation (das Gerät antwortet innerhalb einer definierten Zeitspanne nicht) eingetreten ist. Man spricht von einem Warteaufruf mit implizitem Warten oder auch von einem synchronen Zugriff. Demgegenüber spricht man von einem Warteaufruf mit explizitem Warten, wenn der Warteaufruf (Zugriff auf das Gerät) direkt zurückkehrt. Die Task, die den Aufruf durchgeführt hat, kann - unabhängig von der Bearbeitung des Warteaufrufs innerhalb des Betriebssystems - zurückkehren und weiterrechnen. Ist der Zugriff auf das Gerät durch das I/O-Subsystem abgeschlossen, wird die Task darüber entweder per Ereignis (auf das an anderer Stelle entweder explizit gewartet wird, oder welches durch die Task gepollt wird) informiert, oder das Betriebssystem ruft eine vorher übergebene Callback-Funktion auf. Bei Warteaufrufen mit explizitem Warten spricht man auch von einem asynchronen Zugriff. Ein Beispiel für eine derartige Funktion mit explizitem Warten ist unter Win32 die Funktion ReadFile mit sogenannter overlap-structure (Bild Expliziter Warteaufruf mit Win32 Interface). Aus Sicht des Betriebssystems sind Warteaufrufe mit explizitem Warten nicht trivial zu realisieren. Das liegt an der Schwierigkeit, dass von der Applikation mehrere explizite Warteaufrufe gestartet werden können. Das Betriebssystem muss sich darüber die Informationen innerhalb des Betriebssystems aber merken (daher auch die overlap-structure, die die notwendigen Informationen bezüglich des Aufrufes enthält und deren Inhalt im Betriebssystem abgelegt ist). Darüberhinaus muss das I/O-Susbsystem darauf eingerichtet sein, dass zwischen Start des Zugriffs und Ende des Zugriffs die zugehörige Applikation beendet wird. Einige Betriebssysteme (z.b. Unix) stellen Warteaufrufe mit explizitem Warten nicht direkt (native) zur Verfügung (auch wenn sich diese leicht implementieren lassen). Hier kann man sich aber an der Applikationsschnittstelle anderer Möglichkeiten bedienen, um die gleiche Funktionalität zu bekommen. Dazu wird der eigentliche Zugriff auf das Gerät auf einen zweiten Thread verteilt (Bild Asynchroner Zugriff über Threads). Der zweite Thread schläft im eigentliche Leseaufruf (Funktion read) solange, bis die Daten vorhanden sind. Der Hauptthread wartet schließlich, bis sich der zweite Thread beendet.... char *OverlappedAccess() { char buf[100]; DWORD dwbytesread; DWORD dwcurbytesread; OVERLAPPED ol; ol.offset = 10; ol.offsethigh = 0; ol.hevent = NULL; HANDLE hfile = CreateFile("overlap.test", GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); 48

54 Kapitel 3. Realzeitbetriebssysteme ReadFile(hFile, buf, 100, &dwbytesread, &ol); // perform other tasks in this thread /... // Synchronise with file I/O WaitForSingleObject(hFile, INFINITE); GetOverlappedResult(hFile, &ol, &dwcurbytesread, TRUE); CloseHandle(hFile); return(result_buffer);... Abbildung Expliziter Warteaufruf mit Win32 Interface #include <stdio.h> #include <pthread.h> static char buf[100]; // global or as argument to pthread_create void *AsyncThread( void *fp ) { fread( buf, sizeof(buf), 1, (FILE *)fp ); pthread_exit( NULL ); main( int argc, char **argv ) { pthread_t MyThread; FILE *fp; fp = fopen( "overlap.test", "r" ); if( fp==null ) { perror( "overlap.test" ); return; if( pthread_create( &MyThread, NULL, AsyncThread, fp )!= 0 ) { fprintf(stderr,"creation of thread failed\n"); exit( -1 ); // perform other tasks //... // Synchronise with file I/O pthread_join( AsyncThread, NULL ); Abbildung Asynchroner Zugriff über Threads Neben diesen beiden prinzipiellen Zugriffsmöglichkeiten gibt es die Variation des nicht blockierenden Zugriffs (non blocking mode). Beim nicht blockierenden Zugriff wartet die aufrufende Task (der Thread) nicht, wenn die angeforderten Daten nicht zur Verfügung stehen. Stattdessen kehrt der Funktionsaufruf (Systemcall) direkt mit einer entsprechenden Benachrichtigung zurück. Blockierenden oder nicht blockierenden Zugriff kann man bei Unix-Betriebssystemvarianten entweder beim Öffnen/Initialisieren (Geräte werden über die Funktion open geöffnet bzw. initialisiert) einstellen, oder nachträglich über die Funktion fcntl. 49

55 Kapitel 3. Realzeitbetriebssysteme int fd; fd = open( "/dev/modem", O_RDWR O_NONBLOCK ); if( fd < 0 ) { perror( "/dev/modem" ); return( -1 ); Abbildung Öffnen eines Gerätes im Non-Blocking Mode Weitere Möglichkeiten warten zu vermeiden bietet das I/O-Subsystem an der Applikationsschnittstelle mit dem sogenannten select Aufruf respektive poll Funktion (Beispiel Programmbeispiels select). Ein weiteres wichtiges Merkmal ist, dass diese Funktionen (Systemcalls) gleich mehrere Ein- bzw. Ausgabequellen (z.b. Geräte, Netzverbindungen u.ä.) darauf überprüfen, ob von diesen ohne zu warten Daten gelesen werden können oder ob auf diese ohne warten zu müssen Daten geschrieben werden können. Select und auch poll können zeitüberwacht aufgerufen werden. Das bedeutet, dass entweder nur nachgesehen werden kann, ob eine Veränderung stattgefunden hat, oder es kann gleichzeitig implizit eine zu definierende Zeit gewartet werden. Sind innerhalb dieser angegebenen Zeitspanne keine Daten auf den spezfizierten Ein-/Ausgabequellen angekommen oder lassen sich keine Daten darauf schreiben, bricht die Funktion mit einer entsprechenden Nachricht ab. Die Funktionen select und poll werden auch genutzt, um eine Task/Thread (ähnlich der Funktion sleep) im Millisekundenbereich warten (schlafen) zu lassen. Der Unterschied zwischen select und poll besteht darin, dass man mittels select nur den Zustand von Ein-/Ausgabequellen überprüfen kann, bei poll jedoch noch zusätzlich den Zustand von Ereignissen (events) Gerätezugriff innerhalb des Betriebssystemkerns Um an der Applikationsschnittstelle ein einheitliches Interface anbieten zu können, muss ein Peripheriemodul gemäß bestimmter Konventionen (Schnittstellen) in den Betriebssystemkern eingebunden werden. Dazu existiert innerhalb des Betriebssystemkerns die Gerätetreiberschnittstelle. Diese Schnittstelle verwendend, erstellt der Informatiker oder Ingenieur einen sogenannten Gerätetreiber (Device-Driver), der oftmals dynamisch während der Laufzeit in den Betriebssystemkern integriert wird. Verwendet dann eine Applikation eine der Zugriffsfunktionen open, close, read, write oder ioctl, wird im Gerätetreiber eine entsprechende Funktion aufgerufen, die das Peripheriemodul so ansteuert, dass dieses die spezifizierte Funktionalität erbringt. Ein Gerätetreiber ist also: ein Satz von Funktionen, die den Zugriff auf eine spezifische Hardware (Gerät) steuern. die gerätespezifische Implementierung der allgemeinen Schnittstellenfunktionen. Die Aufgabe des Betriebssystems (I/O Subsystem) im Kontext der Gerätetreiber ist: eine eindeutige Schnittstelle zur Verfügung zu stellen, die Verwaltung der Ressourcen, Mechanismen für die Realisierung von Zugriffsarten und Zugriffsschutz bereit zu stellen, die Zuordnung der allgemeinen Schnittstellenfunktionen (Systemcalls) zu den gerätespezifischen Funktionen durchzuführen. Der Treiber registriert sich beim I/O-Subsystem mit seinen Funktionen und der Majornumber. Über die Zuordnung symbolischer Name und Majornumber oder Filedeskriptor wird das Mapping zwischen I/O-Systemcall und Treiberfunktion durchgeführt. 50

56 Kapitel 3. Realzeitbetriebssysteme Das Betriebssystem ruft die mit der Majornumber korrespondierende Treiberfunktion auf. Auch heute findet man noch sehr häufig Ingenieure, die Peripheriemodule nicht über einen Treiber, sondern direkt aus der Applikation heraus ansteuern. Jedoch ist ein Treiber notwendig: Um benötigte Ressourcen vom Betriebssystem zugeteilt zu bekommen. Schließlich ist das Betriebssystem für die Verwaltung der Ressourcen zuständig. Wird ein Gerät nicht systemkonform eingebunden, kann das Betriebssystem die zugesicherten Eigenschaften nicht mehr garantieren. Es hat beispielsweise keinerlei Kontrolle darüber, ob eine Ressource (ein Interrupt, ein I/O-Bereich oder ein sonstiger Speicherbereich) bereits vergeben ist oder nicht. Das Sperren von Interrupts in ungeeigneten Systemzuständen kann außerdem zu erhöhten Latenz-Zeiten (siehe Abschnitt Interrupt- und Task-Latenzzeiten) führen. Um systemkritische Teile zu kapseln. Zugriffe auf Hardware sind sicherheitskritische Aktionen, die nur innerhalb eines Treibers durchgeführt werden dürfen. Wird aber beispielsweise die Hardware aus der Applikation heraus angesteuert (indem die Register bzw. Speicherbereiche der Hardware in den Adressraum der Applikation gemapped werden), muss die Applikation erweiterte Zugriffsrechte bekommen. Damit wiederum gefährdet sie die Sicherheit des gesamten Systems. Programmierfehler können nicht nur zum Absturz der Task, sondern sogar zum Absturz des gesamten Systems führen. Um bei Applikationsfehlern das Gerät in den sicheren Zustand überführen zu können. Durch die eindeutige Trennung zwischen Applikation und Gerätetreiber ist es für das Betriebssystem möglich, bei Applikationsfehlern das Gerät in den sicheren Zustand zu überführen. Stürzt die Applikation durch einen Fehler ab, erkennt dies das Betriebssystem. Da es außerdem weiß, dass diese (bzw. welche) Applikation auf das Gerät zugegriffen hat, kann es die im Gerätetreiber befindliche Funktionalität aktivieren, einen sicheren Gerätezustand herzustellen. Insbesondere dieser dritte Grund ist für sicherheitskritische Echtzeitsysteme wesentlich! Die meisten Betriebssysteme haben das durch Unix eingeführte Konzept übernommen, Geräte auf Applikationsebene auf Dateien abzubilden und mit den bereits vorgestellten Systemcalls (open, close, read, write, ioctl, select und poll) anzusprechen. Diese Aufrufe werden innerhalb des Betriebssystems an den Gerätetreiber weitergereicht. Verwendet also eine Applikation den open-aufruf, um eine Ressource (in diesem Falle also ein Gerät) zu reservieren, stellt das Betriebssystem fest, um welches Gerät es sich handelt und ruft dann die zugehörige open-funktion innerhalb des Treibers auf (Bild Treiberauswahl). Jeder Treiber muss also eine treiberspezifische open- Funktion zur Verfügung stellen. Es gibt damit im Betriebssystem so viele open-funktionen wie es Treiber gibt, während an der Applikationsschnittstelle genau eine open-funktion existiert. Damit die richtige Treiberfunktion ausgewählt werden kann, benötigt das Betriebssystem natürlich einen Anhaltspunkt. Dieser Anhaltspunkt ist beim Systemcall (also an der Applikationsschnittstelle) open ein symbolischer Name, über den die Zuordnung durchgeführt wird, bei den anderen Funktionen ein Deskriptor, den das Betriebssystem der Applikation als Ergebnis des open-systemcalls zurückgibt. Die Zuordnung eines symbolischen Gerätenamens zum zugehörigen Gerätetreiber wird in Unix beispielsweise über die sogenannten Majornumber durchgeführt. Im Dateisystem wird eine Datei erzeugt, die einen symbolischen Namen wie zum Beispiel mydevice besitzt. Diese Datei bekommt als Attribute eine Majornumber, zum Beispiel 122 zugeteilt (der Aufruf dazu lautet mknod mydevice c 122 0). Diese Majornumber ist im Regelfall eindeutig festgelegt und im Treiber eincodiert. Wird der Treiber schließlich vom Betriebssystem geladen (unter Linux dynamisch mit dem insmod Kommando), meldet sich dieser beim Betriebssystemkern mit der eincompilierten Majornumber an. Bei diesem Vorgang wird außerdem eine Liste der Treiberfunktionen mit übergeben, die aufgerufen werden sollen, wenn die Applikation die entsprechenden Zugriffsfunktionen (open, close, read, write, ioctl, select oder poll) aufruft. Damit sind im System 51

57 Kapitel 3. Realzeitbetriebssysteme alle notwendigen Informationen vorhanden, um bei Zugriffen durch die Applikation die entsprechenden Treiberfunktionen zu starten. Bild Einbindung des Gerätetreibers in das System zeigt dieses noch einmal anhand eines Linux-Gerätetreibers. Wird der Treiber per insmod geladen, so wird beim Laden die Funktion init_module aufgerufen. Diese Funktion registriert den Treiber, wobei die Majornumber und die Tabelle mit den Treiberfunktionen dem Kernel übergeben werden (register_chrdev(my_major_number,..., &mydevice_table);, wobei MY_MAJOR_NUMBER zum Beispiel 122 sein kann). Open Systemcall Applikation Kernel Open Funktion des Gerätetreibers wird aufgrund der Systemcallparameter ausgewählt serielle parallele Schnittstelle Schnittstelle Festplatte CD ROM Analog Input Open Funktionen der Gerätetreiber Hardware Abbildung Treiberauswahl Ist der Treiber geladen, kann die Applikation diesen nutzen. Dazu muss im Filesystem eine Spezialdatei existieren, die die Zuordnung des symbolischen Gerätenamens zur Majornumber MY_MAJOR_NUMBER (also z.b. 122) durchführt. Führt die Applikation dann den Systemcall open mit dem Gerätetreibernamen als Parameter durch, ruft der Kernel die gerätetreiberspezifische Funktion mydevice_open auf. Das gleiche gilt für die übrigen Schnittstellenfunktionen wie read oder write. Bei der Treibererstellung sind damit im wesentlichen diese internen Funktionen zu kodieren: init_device/init_module Die Funktion, die beim Laden des Treibers aufgerufen wird, hat die folgenden Aufgaben: Anmelden des Treibers beim Betriebssystem. Eventuell automatisches Suchen und Erkennen der Hardware und des Hardwaretyps (z.b. serielle Schnittstelle und diese in der Ausprägung mit Chip). Initialisierung der Hardware. Anforderung von System-Ressourcen (Interrupt, I/O-Bereich, Speicherbereich). exit_treiber/cleanup_module Diese Funktion führt folgende Aktivitäten durch: Freigabe der vom Treiber reservierten Systemressourcen. Abmelden des Treibers beim Betriebssystem. 52

58 Kapitel 3. Realzeitbetriebssysteme open (treiberspezifisch) Diese Funktion hat die folgenden Aufgaben: Überprüfen der Schreib-/Leserechte auf das Gerät. Überprüfen, ob der Zugriff zu einem Zeitpunkt für eine oder für mehrere Applikationen erlaubt ist und ob bereits eine Applikation die Ressource nutzt. Zuteilung von Hardware-Ressourcen (z.b. einem Prozess wird einer von vier DMA-Kanälen zugeteilt). close (treiberspezifisch) Die Funktion close hat die Aufgaben: Freigabe der (durch open) belegten Ressourcen. Überführen des Gerätes (der Hardware) in einen definierten und sicheren Zustand (deinitialisieren). Hat eine Applikation das Device (Gerät) geöffnet, bricht dann aber ab, ohne das Gerät wieder zu schließen (z.b. weil die Applikation abgestürzt ist), ruft das Betriebssystem anstelle der Applikation die close-funktion auf und gewährleistet damit einen sicheren Betrieb. read (treiberspezifisch) Aufgabe von read ist: Realisierung der Zugriffsarten (blockierend, nicht blockierend, siehe Abschnitt Zugriffsarten) Anpassung des Taskzustandes (Versetzen der Task im blocking-mode in den Zustand schlafend bzw. in den Zustand lauffähig ). Kopieren der Daten aus dem Kernelspace in den Userspace (Speicherbereich der aufrufenden Applikation). Das Kopieren der Daten wird über Systemfunktionen durchgeführt, da aus Sicherheitsgründen ein direkter Zugriff auf die Speicherbereiche nicht möglich ist. write (treiberspezifisch) Für diese Funktion gilt das gleiche wie bei der read-funktion: Realisierung der Zugriffsarten (blockierend, nicht blockierend, siehe Abschnitt Zugriffsarten) Anpassung des Taskzustandes (Versetzen der Task im blocking-mode in den Zustand schlafend bzw. in den Zustand lauffähig ). Kopieren der Daten aus dem Userspace in den Kernelspace. ioctl (treiberspezifisch) Mittels ioctl (I/O-Control) werden die folgenden Aufgaben durchgeführt: Realisierung gerätespezifischer Funktionalität, die sich nicht sinnvoll auf das read/write Interface abbilden lassen. Einstellung von Treiberparametern (z.b. Baudrate bei einer seriellen Schnittstelle). IO-Controlls sollten so sparsam wie möglich verwendet werden! Diese Funktion stellt ein über Plattformen hinweg nur sehr schwer zu portierende Eigenschaft des Treibers dar. Die ioctl-funktion bekommt als Parameter ein Kommando und einen Zeiger auf eine Datenstruktur mit. Entsprechend dem Kommando beinhaltet die Datenstruktur Datenbereiche im Userspace, aus denen Daten abgeholt (kopiert) werden können bzw. in die Daten geschrieben (kopiert) werden können. select Die treiberspezifische select-funktion hat nur die Aufgabe zu überprüfen: ob Daten, ohne warten zu müssen (also direkt), vom Gerät gelesen werden können, ob Daten, ohne warten zu müssen (also direkt), auf das Gerät geschrieben werden können. 53

59 Kapitel 3. Realzeitbetriebssysteme Die an der Applikationsschnittstelle übrigen Funktionalitäten (insbesondere die Überwachung mehrerer Ein-/Ausgabekanäle und die Zeitüberwachung) sind innerhalb des I/O-Subsystems realisiert. poll Die Funktion poll wird im Treiber wie die Funktion select behandelt. Auch hier wird die eigentliche Funktionalität innerhalb des Kernels realisiert. Kernelmodul: treiber.c struct file_operations fops {.open =driver_open;.release=driver_close;.write =driver_write;.read =driver_read;.ioctl =driver_ioctl;... ; int mod_init() { register_chrdev(240,..., &Fops);... void mod_exit() { unregister_chrdev(...);... int driver_open(...) {... insmod treiber.ko mknod geraete_datei c geraete_datei Applikation: main() { int fd=open("geraete_datei", O_RDWR); write( fd, "Hallo", 6 ); close( fd ); rmmod treiber Über die Major Nummer der Gerätedatei findet der Kernel die zugehörige driver_open Funktion. int driver_close(...) {... int driver_write(...) { // copy data from user space to I/O... KERNEL USER Abbildung Einbindung des Gerätetreibers in das System Filesystem Filesysteme dienen zum Abspeichern bzw. Sichern von: Programmen (Betriebssystemkern, Dienstprogrammen und Applikationen) Konfigurationsinformationen Daten (z.b. HTML-Seiten) auf sogenanntem Hintergrundspeicher. Werden in der Desktop- und Serverwelt klassischerweise Festplatten als Hintergrundspeicher verwendet, benutzt man im Bereich eingebetteter Systeme EEPROMs oder Flashspeicher. 54

60 Kapitel 3. Realzeitbetriebssysteme Damit auf einen Hintergrundspeicher mehrere Dateien/Daten abgelegt werden können, ist eine Organisationsstruktur (Filesystem, z.b. FAT, VFAT, NTFS, Linux Ext2 Filesystem) notwendig. Diese Organisationsstruktur sollte: einen schnellen Zugriff ermöglichen und wenig Overhead (bezüglich Speicherplatz) für die Verwaltungsinformation benötigen.... read( fd, buf, 128 )... Applikation Buffercache Hintergrundspeicher Abbildung Lesen über den Buffercache Um schnellen Zugriff zu ermöglichen, arbeiten einige Systeme mit einem dazwischengeschalteten Cache, dem sogenannten Buffercache (in der Windows-Welt auch unter dem Produktnamen Smartdrive bekannt). Möchte eine Applikation eine Datei, die auf dem Filesystem abgelegt ist, lesen, schaut der Betriebssystemkern im Buffercache nach, ob die gewünschte Information dort vorhanden ist oder nicht. Ist dies nicht der Fall, wird die Information in den Cache geladen und dann weiter an die Applikation gereicht. Wird ein Schreibauftrag erteilt, gehen die Daten zunächst an den Buffercache. Erst einige Zeit später werden die Daten auf dem Hintergrundspeicher mit den Daten aus dem Cache abgeglichen. Dieses Verfahren ist oftmals nicht tolerabel: Das Filesystem kann dann Inkonsistenzen enthalten, wenn das System abstürzt, der Hintergrundspeicher aber nicht mit dem Cache rechtzeitig abgeglichen worden ist. Zugriffe auf das Filesystem sind nicht deterministisch, da nicht vorhergesagt werden kann, ob angeforderte Daten im Cache gefunden werden oder zu einem Leseauftrag an den Hintergrundspeicher führen. Um dennoch das Verfahren im Umfeld von Echtzeitsystemen einsetzen zu können, wird der sync-mode verwendet. Beim sync-mode werden Dateien direkt (also unter Umgehung des Buffercaches) geschrieben, ein Lesezugriff kann aber über den deutlich schnelleren Buffercache stattfinden Systemcall-Interface Über die Systemcall-Schnittstelle lassen sich aus der Applikation heraus die Dienste des Betriebssystems nutzen. Diese Schnittstelle ist absolut unabhängig von jeglicher Programmiersprache. In den seltensten Fällen greift eine Applikation direkt auf das Systemcall-Interface zu, sondern nutzt zu diesem Zwecke Bibliotheksfunktionen. So lautet beispielsweise der Systemcall für die Ausgabe von Daten in eine Datei oder in ein Gerät write, wobei sich hinter dem Namen eine Nummer (bei Linux beispielsweise der Code 4) verbirgt. 55

61 Kapitel 3. Realzeitbetriebssysteme Systemcalls erwarten ihre Argumente entweder in Registern oder auf dem Stack. Ein Systemcall wird dann über den Assemblerbefehl INT bzw. TRAP mit einer Exceptionnummer (bei Linux beispielsweise 0x80) aufgerufen (Softwareinterrupt). Dieser Befehl führt zu einer Exception, wobei die Exceptionnummer innerhalb des Betriebssystems die Dienstaufrufsschnittstelle aktiviert. Hier wird anhand der Registerinhalte (Systemcallcode) in die entsprechenden Funktionen verzweigt. Bild Codebeispiel Systemcall macht dieses deutlich. Der Code für den Systemcall (hier 4) wird in das Register eax geschrieben. Die Register ebx, ecx und edx werden mit den Parametern des Funktionsaufrufes belegt (in diesem Fall der Dateideskriptor 1 für stdout, die Adresse der auszugebenden Daten "Hello World" und die Länge 12 des auszugebenden Strings). Danach wird der Softwareinterrupt für die Systemcall- Schnittstelle (0x80) aufgerufen..text.globl write_hello_world write_hello_world: movl $4,%eax ; //code fuer "write" systemcall movl $1,%ebx ; //file descriptor fd (1=stdout) movl $message,%ecx ; //Adresse des Textes (buffer) movl $12,%edx ; //Laenge des auszugebenden Textes int $0x80 ; //SW-Interrupt, Auftrag an das BS ret.data message:.ascii "Hello World\n" Abbildung Codebeispiel Systemcall Der angegebene Assemblercode realisiert einen Unterprogrammaufruf (ohne Rückgabewert, also vom Typ void), der in eine Bibliothek abgelegt werden könnte. Danach würde eine Applikation nur noch diese Bibliotheksfunktion (Library-Call) aufrufen, um den String auszugeben (Bild Codebeispiel Library-Call). main( int argc, char **argv ) { write_hello_world(); Abbildung Codebeispiel Library-Call Im Regelfall erweitern Bibliotheksfunktionen aber noch den Funktionsumfang von Systemcalls. Die wenigsten Applikationen verwenden beispielsweise den write-aufruf zur einfachen Ausgabe von Informationen, stattdessen verwenden sie eine Funktion wie printf, um die Ausgabeinformation noch zu formatieren. Die Funktion printf wiederum führt die Formatierung und den anschließenden Systemaufruf write durch Services Ein Betriebssystem besteht aus dem Betriebssystemkern, ebenso aber auch aus Diensten, die auf der User- Ebene ablaufen (Services oder Daemonen genannt). Das Vorhandensein und die Aufgaben dieser Dienste hängen stark vom Einatz des Realzeitbetriebsystems ab. Es lassen sich die folgenden Gruppen unterscheiden: 56

62 Dienste zur Konfiguration Kapitel 3. Realzeitbetriebssysteme Insbesondere in der Hochlaufphase des Systems müssen diverse Konfigurationen durchgeführt werden. Hier sind also Dienste aktiv, die die Hardware initialisieren, die das Netzwerk konfigurieren (z.b. IP- Adresse) oder auch Treiber laden. Protokolldienste Entgegen der sehr verbreiteten Art und Weise, dass die einzelnen (Realzeit-) Tasks Informationen auf proprietäre Weise protokollieren und sichern, sollten die vom Betriebssystem zu diesem Zweck eingerichteten Dienste in Anspruch genommen werden. Protokolliert wird dann nicht nur auf einheitliche Weise der Zustand des Betriebssystems, sondern auch der Zustand der Applikation. Netzwerkdienste Ist das Realzeitsystem vernetzt, müssen verschiedene Dienste aktiv sein, um Anfragen, die vom Netz kommen, zu beantworten. Typische Dienste hier sind der HTTP-Server (WWW) oder der FTP-Server. Hierzu zählen aber auch Dienste zur Zeitsynchronisation in einem verteilten Echtzeitsystem. Dienste der Zeitsteuerung Betriebssysteme bieten die Möglichkeit, Tasks zyklisch oder zu vorgegebenen Zeiten zu starten. Würden die Tasks diese Zeitsteuerung selber dadurch realisieren, dass sie sich für einen entsprechenden Zeitraum schlafen legten, würden im System mehr Ressourcen benötigt, da die Tasks Speicher im System (und in den Systemtabellen des Betriebssystemkerns) belegten Bibliotheken statisch dynamic Prgm. 1 Prgm. 2 Prgm. 1 Prgm. 2 Prgm. Platte printf printf printf printf Hauptspeicher RP 1 RP 2 RP 1 RP 2 RP printf printf printf printf shared dynamic loaded Abbildung Verschiedene Bibliotheksarten Wie bereits in Abschnitt Systemcall-Interface beschrieben, abstrahieren zum Betriebssystem gehörige Bibliotheken (Libraries) den Zugriff auf die Systemcalls/Dienste (nicht zu verwechseln mit den Dienstprogrammen, Services oder Daemonen). Libraries werden sowohl zu den eigenen Applikationen als auch zu den Dienstprogrammen hinzugebunden. Man unterscheidet statische von dynamischen Bibliotheken. Während statische Libraries zu dem eigentlichen Programm beim Linken hinzugebunden werden, werden dynamische Bibliotheken (auch shared libs genannt) erst dann an das Programm gebunden, wenn dieses ausgeführt werden soll. Das ergibt folgende Vorteile: 57

63 Kapitel 3. Realzeitbetriebssysteme Das eigentliche Programm wird kleiner (der Code für die Libraries muss ja nicht abgelegt werden). Programme können sich eine shared lib teilen, wodurch Hauptspeicherplatz gespart wird. dass mehrere Programme Code verwenden, der nur einmal im Speicher ist, wird durch die Trennung von Code- und Datensegment ermöglicht (Bild Verschiedene Bibliotheksarten). Nachteilig bei diesem Verfahren ist es, dass zum Ausführen einer Applikation nicht nur selbige, sondern zusätzlich auch alle zugehörigen Bibliotheken in der richtigen Version notwendig sind. Gerade bei komplexen Applikationen können diese Wechselwirkungen derart groß werden, dass Im übrigen unterstützen moderne Betriebssysteme das Laden dynamische Libraries unter Programmkontrolle (Bild Dynamisches Laden von Funktionen während der Laufzeit). Dynamisch ladbare Bibliotheken werden im Regelfall vom System zur Applikation dazugebunden (gelinkt), wenn die Applikation gestartet wird. Daneben gibt es auch die Variante, dass eine Applikation selbst eine Bibliothek lädt (siehe Bild Dynamisches Laden von Funktionen während der Laufzeit). Dieses bietet für einige Anwendungen den Vorteil der möglichen Modularisierung. #include <stdio.h> #include <dlfcn.h> int main(int argc, char **argv) { void *handle; int (*function)(int); char *error; handle = dlopen ("myshlib.so", RTLD_LAZY); if( (error=dlerror()) ) { fprintf(stderr,"%s\n", error ); return( -1 ); function = dlsym(handle, "DoubleMinusOne"); printf ("%d\n", function(5) ); dlclose(handle); Abbildung Dynamisches Laden von Funktionen während der Laufzeit Während früher beim Linken statischer Libraries die komplette Bibliothek hinzugebunden wurde, werden heute aus einer Bibliothek nur die Funktionen extrahiert, die ein Programm auch wirklich einsetzt. Damit wird ebenfalls verhindert, dass der Programmcode auf der einen Seite zu stark anwächst oder auf der anderen Seite Funktionen auf mehrere Libraries verteilt werden. (Die Verwendung vieler kleiner Libraries anstelle einer großen Library spart Speicherplatz.) Insbesondere in eingebetteten Systemen bzw. wenn einfache Laufzeitsysteme verwendet werden, entfällt die Notwendigkeit von Bibliotheksfunktionen Zeitaspekte Interrupt- und Task-Latenzzeiten Echtzeitbetriebssysteme werden durch die Hersteller meist über zwei Werte charakterisiert: Der Interruptund der Task-Latenzzeit. 58

64 Kapitel 3. Realzeitbetriebssysteme Die Interruptlatenzzeit t L ist die Zeit, die zwischen dem Auftreten des Interrupts (t A ) und dem Starten der zugehörigen Interruptserviceroutine maximal vergeht. Ereignis tritt ein Task Latency Interrupt Latency Hardware Latency Preemption Latency (IR disabled) ISR ISR2 Higher prior IR Latency IR Execution Latency Scheduler Context Switch IR ausgelöst System Call Delay Abbildung Verzögerungszeiten Die Interruptlatenzzeit setzt sich aus den folgenden Zeiten zusammen [TimMon97]: 1. Hardware-Latency: Zeit, die die Hardware benötigt, um das Ereignis in Form eines Interrupts dem Prozessor zu melden (einige wenige Gatterlaufzeiten). 2. Preemption-Delay: Verzögerungszeit, die entsteht, wenn sich das Betriebssystem in einem kritischen Abschnitt befindet, der nicht unterbrochen werden darf (Interrupts sind gesperrt). 3. Verarbeitungszeit einer höherprioren ISR. Die Tasklatenzzeit t L ist die Zeit, die zwischen dem Auftreten des Ereignisses (Interrupt, t A ) un dem Start des zugehörigen Rechenprozesses maximal vergeht. Die Tasklatenzzeit setzt sich aus den folgenden Zeiten zusammen: 1. Interruptlatenzzeit. 2. Verarbeitungszeit der zum Ereignis zugehörigen ISR. 3. System Call: Tritt der Interrupt gerade zu einem Zeitpunkt auf, an dem ein Systemcall bearbeitet wurde, muss zunächst dieser Systemcall zuende bearbeitet (oder abgebrochen) werden. 4. Scheduling: Zeit, die vergeht, um den nächsten lauffähigen Prozess auszuwählen (der Scheduler wird im Regelfall mit jedem Interrupt aufgerufen). 5. Context-Switch: Zeit, die benötigt wird, um die (neue) Task starten zu können (also die Zeit, um die Register der CPU mit den Daten der zu bearbeitenden Task zu laden). 6. Hat die Task, für die das Ereignis gilt, nicht höchste Priorität, werden erst die höher prioren Tasks bearbeitet. Die Zeiten treten weder in der angegebenen Reihenfolge auf, noch müssen sie grundsätzlich alle berücksichtigt werden. Hinzu kommt, dass die Zeiten auch mehrfach auftreten, wenn beispielsweise mehr als zwei unterschiedliche Interrupts auftreten. Die Hardwarelatenzzeit muss in jedem Fall berücksichtigt werden, fällt aber bei ihrer Größenordnung (ns) im Vergleich zu den anderen Zeiten kaum ins Gewicht. Diese Zeit fällt maximal einmal an. Da Interrupts die höchste Priorität haben, fallen abgesehen von dem Fall, dass Interrupts gesperrt sind die Interrupt-Latenzzeiten an. Interrupt-Latenzzeiten treten für jeden Interrupt genau einmal auf, bei unterschiedlicher Priorisierung der Interrupts möglicherweise auch mehrfach. Die Zeiten, die durch Abarbeitung von Interrupt Service Threads anfallen, werden hier als den Interrupts zugehörig betrachtet. 59

65 Kapitel 3. Realzeitbetriebssysteme Nach den Interrupts bearbeitet der Prozessor zunächst einen aktiven Systemcall zuende. Danach folgt der Scheduler und schließlich der Kontextswitch. Diese Zeiten fallen durchaus mehrfach an, insbesondere dann, wenn es noch höherpriore Tasks im System gibt. Diese Verzögerungszeiten kennzeichnen die Realzeiteigenschaften eines Betriebssystems. Echtzeitbetriebssysteme weisen auf PC-Plattformen (Pentium) beispielsweise Wartezeiten in der Größenordnung von 15 bis 300µs auf. Bei der Wahl eines Echtzeitbetriebssystems werden häufig die Interrupt-Latenzzeiten verglichen. Die Interrupt-Latenzzeit wird aber im wesentlichen durch die ISR s der Treiber bestimmt. Der Hersteller eines Betriebssystem kennt aber nur die Treiber, die integrativer Bestandteil sind. Beim Vergleich ist daher auf die eigentliche Systemkonfiguration zu achten. Sperrt ein einzelner Untersuchungen haben gezeigt, dass Latenzzeiten nicht unbedingt konstant sein müssen. Vielmehr gibt es auch den Fall, dass die Zeiten von der sogenannten Uptime des Systems abhängen (z.b. dadurch, dass intern Listen verarbeitet werden, die it längerer Laufzeit des Systems ebenfalls länger werden) [Mächtel]. Merke: Um Verzögerungszeiten (Latency-Times) so gering wie möglich zu halten, ist es beim Systemdesign wichtig, Interrupts nicht länger als notwendig zu sperren und critical sections kurz zu halten Unterbrechungsmodell Das Unterbrechungsmodell ist verantwortlich für den Schutz kritischer Abschnitte und damit auch für Latenzzeiten im System. Der Systemarchitekt muss es kennen, da die Ebenen unterschiedlich priorisiert sind und er durch Zuordnung von Funktionen auf Ebenen das Echtzeitverhalten generell beeinflusst. Der Programmierer muss es kennen, da er nur so kritische Abschnitte identifizieren und effizient schützen kann. Grundsätzlich muss er hierfür die quasi-parallelen von der real-parallelen Verarbeitung unterschieden. Erstere entsteht durch die Unterbrechbarkeit: Auf einer Einkernmaschine wird ein einzelner Job A unterbrochen (preempted), ein anderer Job B wird lauffähig und abgearbeitet und nach einiger Zeit wird auch dieser wieder preempted und Job A fortgesetzt. Damit laufen, wenn auch ineinander geschachtelt, Job A und Job B (quasi-) parallel. Auf einer Mehrkernmaschine dagegen kann Job A auf einem Prozessorkern und Job B auf einem anderen Prozessorkern zeitgleich, real-parallel ablaufen. Diese Parallelverarbeitung ist im Unterbrechungsmodell nicht direkt sichtbar. Im Linux-Kernel gibt es das in Abbildung Unterbrechungsmodell dargestellte Unterbrechungsmodell, das aus vier Ebenen besteht. 1. Die Applikations-Ebene. 2. Die Kernel-Ebene. 3. Die Soft-IRQ-Ebene. 4. Die Interrupt-Ebene. Auf der Applikations-Ebene laufen die Userprogramme, die beispielsweise über Prioritäten gescheduled werden. Kritische Abschnitte, beispielsweise die Zugriffe auf globale Variablen, werden über Semaphore oder Spinlocks geschützt. 60

66 Kapitel 3. Realzeitbetriebssysteme Rufen Applikationen Systemcalls auf (beispielsweise open(), read() oder nanosleep()) werden diese auf der Kernel-Ebene im User-Kontext abgearbeitet. Der Systemcall hat dabei die Priorität des aufrufenden Jobs. Auf gleicher Ebene laufen Kernel-Threads ab, die sich dadurch von User-Threads unterscheiden, indem sie keine Ressourcen (z.b. Speicher) im Userland belegen und damit im Kernel-Kontext arbeiten. Kernel-Threads haben eine Priorität und konkurrieren mit User-Threads. Anders als User-Threads werden die Kernel-Threads jedoch nicht unterbrochen (preempted). Höchste Priorität in der Abarbeitungsreihenfolge haben Interrupt-Service-Routinen. Sobald ein Interrupt auftritt und Interrupts freigegeben sind, werden sie auf dem lokalen Kern gesperrt und die zugehörige ISR wird abgearbeitet. Eine quasi-parallele Verarbeitung findet damit nicht statt. Auf einer Mehrkernmaschine können real jedoch mehrere ISRs parallel verarbeitet werden, ein und dieselbe typischerweise jedoch nicht. Eine Datenstruktur, die von nur einer ISR verwendet wird, muss daher nicht gesondert geschützt werden. Sind anstehende ISRs abgearbeitet, gibt der Kernel Interrupts frei und startet Soft-IRQs, die ebenfalls im Interruptkontext abgearbeitet werden. Soft-IRQs sind nicht unterbrechbar, können aber auf einer Mehrkernmaschine real parallel abgearbeitet werden. Doch auch hier gilt die Einschränkung, dass ein und derselbe Soft-IRQ nicht mehrfach gleichzeitig abläuft. Application Level USER vertical preemptible Kernel Level Soft IRQ Level KERNEL horizontal preemption preemptible Interrupt Level not preemptible Abbildung Unterbrechungsmodell Da Funktionen, die im Interrupt-Kontext ablaufen, nicht schlafen gelegt werden können, kann ein kritischer Abschnitt auf dieser Ebene niemals per Semaphor geschützt werden. Hier wird auf dem lokalen Kern eine Interruptsperre eingesetzt und für die reale Parallelität ein Spinlock. Der Linux-Kernel 2.6.xx bietet die Möglichkeit von so genannten Threaded-Interrupts. Hierbei laufen Interrupts als Kernel-Threads, also im Kernel-Kontext ab. Vorteil: Threads können durch den Systemarchitekten untereinander und sogar bezüglich sonstiger User-Tasks priorisiert werden Timing (Zeitverwaltung) Zeiten spielen in einem Echtzeitsytem eine wesentliche Rolle. Ein Zeitgefühl wird für unterschiedliche Aufgaben benötigt: Zeitmessung. Das Messen von Zeiten ist eine oft vorkommende Aufgabe in der Automatisierungstechnik. Geschwindigkeiten lassen sich beispielsweise über eine Differenzzeitmessung berechnen. 61

67 Kapitel 3. Realzeitbetriebssysteme Zeitsteuerung für Dienste. Spezifische Aufgaben in einem System müssen in regelmäßigen Abständen durchgeführt werden. Zu diesen Aufgaben gehören Backups ebenso, wie Aufgaben, die der User dem System überträgt (z.b. zu bestimmten Zeitpunkten Meßwerte erfassen). Watchdog (Zeitüberwachung). Neben der Zeitmessung spielt die Zeitüberwachung eine sicherheitsrelevante Rolle in Echtzeitsystemen. Zum einen werden einfache Dienste (z.b. die Ausgabe von Daten an eine Prozessperipherie) zeitüberwacht, zum anderen aber auch das gesamte System (Watchdog). Dazu muss das System im regelmäßigen Abstand einen Rückwärtszähler (Timer) zurücksetzen. Ist das System in einem undefinierten Zustand und kommt nicht mehr dazu, den Zähler zurückzusetzen, zählt dieser bis Null und bringt das System in den sicheren Zustand (löst beispielsweise an der CPU ein Reset aus). Das für Betriebssystem und Anwendung benötigte Zeitgefühl wird über Hardware- oder Software-Zähler realisiert, die periodisch getaktet incrementieren oder decrementieren. Die Auflösung der Zähler ergibt sich aus der Taktfrequenz, die Genauigkeit aus der Stabilität des Taktgebers. Grundsätzlich gilt: je höher die Taktfrequenz, desto genauer die Zeitmessung. Die Bitbreite des Zählers entscheidet über den (Zeit-) Meßbereich. Die Software-Zähler werden typischerweise im Rahmen einer Interrupt-Service-Routine incrementiert beziehungsweise decrementiert. Klassischerweise wird dafür ein periodischer Interrupt eingesetzt (Timer-Interrupt), der beispielsweise alle 10ms, also mit einer Rate von 100Hz auftritt. Innerhalb der durch den Interrupt aktivierten Interrupt-Service-Routine wird nicht nur der Zähler incrementiert, sondern auch überprüft, ob Rechenprozesse in den Zustand lauffähig zu versetzen sind, die auf das Ablaufen einer Zeit geschlafen haben. Danach läuft der Scheduling-Algorithmus und schließlich der Kontext-Switch ab. Gemäß dieser Architektur sind Zeitaufträge mit einer Auflösung von maximal dem so genannten Timer- Tick (Zeitabstand zwischen zwei Timer-Interrupts) möglich. Möchte beispielsweise ein Job für wenige Mikrosekunden schlafen wird er trotzdem erst mit dem nächsten Timertick geweckt. Im ungünstigsten Fall ist das bei 100 Hz nach knapp 10ms. Um die Genauigkeit von Zeitoperationen zu verbessern, muss die Rate der Timerinterrupts erhöht werden. Das geht allerdings zu Lasten der Effizienz, was sich insbesondere bei leistungsschwachen Systemen bemerkbar machen kann. Moderne Echtzeitsysteme sind tickless. Tickless bedeutet jedoch mitnichten, dass es gar keinen Timer- Interrupt gibt, sondern nur, dass der Timer-Interrupt nicht periodisch, sondern bedrafsgemäß auftritt. Dazu wird mit jedem Timer-Interrupt der nächste Zeitpunkt berechnet, an dem der Interrupt erneut ausgelöst werden soll. Entsprechend wird der Interrupt-Controller programmiert. Das hat gleich mehrere Vorteile: Die Systemlast wird reduziert, da nur dann Interrupts auftreten, wenn diese auch gebraucht werden. Die Effizienz wird gesteigert. Energie wird eingespart, da während der inaktiven Zeiten das System schlafen gelegt werden kann. Die Genauigkeit von Zeitaufträgen wird erhäht, da nicht mehr auf den nächsten Interrupt gewartet werden muss. Vielmehr wird der Interrupt exakt zum benötigten Zeitpunkt ausgelöst. Nachteilig ist, dass mit jedem Interrupt eine Berechnung des nächsten Auslösezeitpunkts und die Umprogrammierung des Timerbausteins erfolgen muss. Außerdem müssen die Softwaretimer entsprechend der real vergangenen Zeit aktualisiert werden. Zwei Ausprägungen von Zeitgebern lassen sich unterscheiden: Absolutzeitgeber. Zeiten werden grundsätzlich relativ zu einem Ereignis (beispielsweise die Geburt Christi) angegeben. Falls dieses Ereignis aus Sicht eines Rechnersystems außerhalb und vor allem vor der Aktivierung des Rechnersystems liegt, spricht man von einer absoulten Zeit (Uhren). Relativzeitgeber. Steht das Ereignis in direkter Beziehung zum Rechnersystem, beispielsweise das Ereignis Start des Rechners spricht man von einer relativen Zeit. Der zugehörige Zeitgeber ist ein Relativzeitgeber. Die Relativ-Zeitgeber gibt es als Vorwärts- und als Rückwärtszähler (Timer). Bieten Prozessoren einen so genannten Timestamp Counter (TSC) an, einen Zähler, der mit der Taktfrequenz der CPU getaktet wird, nutzt das Betriebssystem diesen häufig, um damit das Timekeeping auf eine 62

68 Kapitel 3. Realzeitbetriebssysteme sehr genaue Basis (Nanosekunden) zu stellen. Bei einem Interrupt wird die real vergangene Zeit durch Auslesen des TSC bestimmt und die internen (Software-) Zeitzähler entsprechend angepasst. Dabei gibt es allerdings das Problem der variablen Taktfrequenzen. Um Energie zu sparen werden moderne Prozessoren mit möglichst niedriger Frequenz getaktet. Erst wenn mehr Leistung benötigt wird, wird auch die Taktfrequenz erhöht. Der Taktfrequenzwechsel muss vom Timekeeping natürlich berücksichtigt werden. Da Applikationen im Userland ebenfalls Zugang zum Timestamp Counter haben, müssen sich diese über Taktfrequenzänderungen informieren lassen und einen Wechsel entsprechend berücksichtigen. Beim Umgang mit Zeiten gibt es im Wesentlichen zwei Problemfelder: Zählerüberläufe. Die verwendeten Hard- und Softwarezähler haben eine endliche Breite und können damit abhängig von der gewählten Auflösung auch nur einen endlichen Zeitraum messen. Danach gibt es einen Zählerüberlauf. Der Zählerüberlauf ist dann kritisch, wenn Komponenten Zeitvergleiche durchführen. Bei einem Zählerüberlauf ist der jüngere Zeitstempel kleiner als der ältere und führt damit zu einem falschen Vergleichsergebnis infolge dessen es zu einem unvorhersehbarem Verhalten kommen kann. Der Linux- Kernel 2.4 beispielsweise kann mit seinem 32-Bit Zähler und einer Rate von 100Hz einen Zählerüberlauf nach knapp 497 Tage. Die Zähler, die die so genannte Unixzeit repräsentieren, laufen am 19. Januar 2038 um 03:14:08 über. Zur Lösung dieses Problems sind zum einen ausreichend große Zähler zu verwenden. Linux beispielsweise setzt ab Kernel 2.6 auf 64-Bit breite Zähler. Das reicht aus, um in Milliarden von Jahren keinen Überlauf zu bekommen. Zum anderen sind Zeitvergleiche mit Vorsicht zu programmieren. Falls die zu vergleichenden Zeitstempel nicht weiter auseinanderliegen, als die Hälfte des Meßbereiches, können die in Abschnitt 4.3.2> vorgestellten Makros eingesetzt werden. Zeitsynchronisation. Rechner werden nur noch in seltenen Fällen als vollständig autarke Systeme eingesetzt. Vielmehr sind sie Teil eines Gesamtsystems, bei dem mehrere Komponenten Informationen austauschen. Hierbei spielt sehr häufig die Zeit eine wichtige Rolle, so dass die Systeme zeitlich synchronisiert werden. Ist dabei eine Zeitkorrektur notwendig, darf diese niemals sprunghaft vorgenommen werden! Wird die Zeit sprunghaft vorgestellt (zum Beispiel bei der Umstellung von Winterzeit auf Sommerzeit), kommen manche Zeitpunkte nicht vor (im Beispiel 2:30 Uhr). Steht zu einem ausgelassenen Zeitpunkt aber ein Zeitauftrag (Backup) an, wird dieser nicht durchgeführt und geht verloren. Wird die Zeit sprunghaft zurückgestellt, kommen demgegenüber Zeitpunkte gleich zweifach vor. Zeitaufträge werden dabei unter Umständen doppelt ausgeführt. Für eine funktionierende Zeitsynchronisation ist es zunächst notwendig, dass die Systemzeit auf der Zeitzone UTC (universal time zone) basiert. Damit wird das Rechnersystem unabhängig vom Ort und unabhängig von Sommer- und Winterzeiten. Die Anwendungssoftware kann auf Basis der Systemzeit und des aktuellen Rechnerstandpunktes die lokale Zeit ableiten. Desweiteren darf niemals eine sprunghafte Zeitkorrektur vorgenommen werden. Die Zeit muss vielmehr der Zielzeit angepasst werden. Dazu wird die Zeitbasis (das Auftreten des periodischen Interrupts) gefühlvoll verlangsamt oder beschleunigt. Gefühlvoll bedeutet in diesem Zusammenhang, dass bei großen zeitlichen Unterschieden zunächst eine stärkere Korrektur und je näher man der Zielzeit ist eine behutsamere Korrektur vorgenommen wird. Das Network Time Protocol (NTP) ist übrigens für diese Art der Zeitsynchronisation entwickelt worden. Der Systemcall adjtime parametriert die zeitliche Anpassung im Kernel. Beim Netowrk Time Protocol stellen Zeitserver die aktuelle Zeit zur Verfügung, wobei die Zeitserver klassifiziert werden. Der Zeitserver, der die Uhrzeit selbst bestimmt (z.b. durch eine DCF77 Funkuhr), ist ein sogenannter Stratus 1 Server. Der Server, der die Uhrzeit von einem Stratus 1 Server bezieht ist der Stratus 2 Server usw. Bei der Verteilung der Uhrzeit werden Berechnungen über die Laufzeit der Pakete zwischen den Rechnern durchgeführt, und die Uhrzeit wird entsprechend korrigiert. 63

69 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung 4.1. Taskmanagement Das Taskmanagement ist für die Erzeugung, das Beenden und die Parametrierung von Tasks zuständig. Zwischen den erzeugenden und den erzeugten Tasks gibt es ein Eltern-Kindverhältnis. Die Kindtasks erben dabei sämtliche Eigenschaften - man sagt das komplette Environment - des Elternjobs. Dazu gehören beispielsweise die Besitzverhältnisse, die Priorität oder auch offene Datei- oder Kommunikationshandels Tasks erzeugen Zur Erzeugung von Prozessen steht der Systemcall pid_t fork(void) zur Verfügung. Nach dem Aufruf gibt es eine Kopie des Prozesses, der fork() aufgerufen hat. Der neue Prozess unterscheidet sich nur in der neuen Prozessidentifikationsnummer (PID) und im Rückgabewert der Funktion. Der Prozess, der fork() aufgerufen habe bekommt die PID der Prozesskopie zurück, die Kopie aber bekommt 0 zurück. Anhand des Rückgabewertes kann der Programmierer also erkennen, ob es sich um den Eltern- oder um den Kindprozess handelt. if (fork()) { /* Parentprocess */... else { /* Childprocess */... Sehr häufig starten Elternprozesse Kindprozesse, damit diese selbständig eine Verarbeitung durchführen und ein Ergebnis liefern. Make beispielsweise startet per fork() den Compiler und wartet darauf, dass der Compiler mitteilt, ob er den Compilierungsvorgang erfolgreich beenden konnte oder beispielsweise wegen eines Syntaxfehlers abbrechen musste. Ist letzteres der Fall, wird auch Make seine Arbeit beendetn. Erfolg oder Mißerfolg teilt ein Progamm über seinen Exitcode mit. Der Exitcode entspricht entweder dem Rückgabewert der Funktion main oder dem Parameter der Funktion exit() selbst. Der Exitcode wird über die Funktion pid_t wait(int *status) oder pid_t waitpid(pid_t pid, int *status, int options) abgeholt. Erstere legt an der übergebenen Adresse status den Exitcode eines Kindprozesses ab. Welcher Kindprozess das ist - falls mehrere gestartet wurden - ist am Rückgabewert erkennbar. Um den Rückgabewert eines bestimmten Kindprozesses abzuholen dient waitpid(). Zur Auswertung des von wait() zurückgelieferten Werts dient ein Satz von Makros (siehe Manpage). childpid = fork(); if (childpid==0) { /* child */... return result; waitpid( childpid, &status, 0 ); printf("child returned 0x%x\n", status); if (WIFEXITED(status)) { printf("child status = %d\n", WEXITSTATUS(status)); Durch Aufruf der Funktion int execve(const char *filename, char *const argv[], char *const envp[]) überschreibt ein per fork() erzeugter Prozess sein Codesegment. Über diesen Mechanismus kann ein komplett anderer Code abgearbeitet werden. Beispiel Execve zeigt, wie parallel zum gerade laufenden 64

70 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Process das Programm cat /etc/issue aufgerufen werden kann. Soll im übrigen ein normales Kommando aufgerufen werden, kann auch alternativ die Funktion int system(const char *command) verwendet werden. Beispiel 4-1. Execve #include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main( int argc, char **argv, char **envp ) { int childpid, status; char *childargv[] = { "/bin/cat", "/etc/issue", NULL, NULL ; char *childenvp[] = { NULL ; childpid = fork(); if (childpid==(-1) ) { perror( "fork" ); return -1; if (childpid==0) { /* child */ execve("/bin/cat", childargv, childenvp); /* parent only */ printf("waiting for my child %d to die...\n", childpid); wait(&status); printf("child died...\n"); return 0; Threads werden über die Funktion int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) erzeugt. Intern verwendet pthread_create() den Systemcall clone(). Um die Funktion nutzen zu können, muss die Bibliothek libpthread zum Programm gebunden werden (Option -lpthread). Anders als bei fork() startet der neue Thread die Verarbeitung in einer separaten Funktion, deren Adresse über den Parameter start_routine übergeben wird. Dieser Funktion wird beim Aufruf das Argument arg übergeben. Der Parameter attr steuert die Erzeugung, thread enthält nach dem Aufruf die Kennung des neuen Threads. pthread_create() gibt im Erfolgsfall 0 zurück, ansonsten einen Fehlercode. Ein Thread beendet sich typischerweise, in dem er void pthread_exit(void *retval) aufruft. Der Elternthread verwendet int pthread_join(pthread_t thread, void **retval) um auf den Exitcode des Threads thread zu warten. Beispiel 4-2. Programmbeispiel Threads #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void *child( void *args ) { printf("the child thread has ID %d\n", getpid() ); // You can either call pthread_exit or "return". // pthread_exit( NULL ); return NULL; int main( int argc, char **argv, char **envp ) { pthread_t MyThread; 65

71 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung if( pthread_create( &MyThread, NULL, child, NULL )!= 0 ) { fprintf(stderr,"creation of thread failed\n"); return -1; /* Now the thread is running concurrent to the main task */ /* But it won t return to this point (aulthough it might */ /* execute a return statement at the end of the Child routine).*/ /* waiting for the termination of MyThread */ pthread_join( MyThread, NULL ); printf("end of main thread\n"); return 0; Tasks beenden Per int kill(pid_t pid, int sig) wird einem anderen Prozess oder einem anderen Thread mit der PID pid das Signal sig geschickt. Typischerweise führt dieses dazu, dass sich der Job beendet. Allerdings können die Signale durch die Programme - mit Ausnahme von SIGKILL (Signalnummer 9) - ignoriert oder abgefangen werden und das Beenden damit verhindert oder verzögert werden. Die eigene Prozess-Identifikationsnummer (PID) wird durch pid_t getpid(void) abgefragt, die Thread- Identifikationsnummer (TID) per pid_t gettid(void) Tasks parametrieren Neu erzeugte Tasks erben die Echtzeiteigenschaften der Elterntasks. Um diese anzupassen stehen eine Reihe von Funktionen zur Verfügung. Per int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param) wird für den Job mit der Priorität pid das Schedulingverfahren policy für die in der Datenstruktur param definierte Prioritätsebene eingestellt. Ist der Parameter pid 0, gelten die Einstellungen für den aufrufenden Prozess. Als Schedulingverfahren stehen die folgenden zur Verfügung: SCHED_RR: Round Robin SCHED_FIFO: First Come First Serve SCHED_OTHER: Default Verfahren für Jobs ohne (Echtzeit-) Priorität Die gerade aktiven Schedulingparameter werden durch Aufruf von int sched_getparam(pid_t pid, struct sched_param *param) ausgelesen. pid referenziert wieder den Job, an der mit param übergebenen Speicheradresse legt die Funktion später die Parameter (Priorität, Prioritätsbereich etc.) ab. Beispiel 4-3. Threads parametrieren #include <stdio.h> #include <stdlib.h> #include <sched.h> #include <time.h> #define PRIORITY_OF_THIS_TASK 15 char *Policies[] = { "SCHED_OTHER", "SCHED_FIFO", "SCHED_RR" ; 66

72 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung static void print_scheduling_parameter() { struct timespec rr_time; printf("priority-range SCHED_FF: %d - %d\n", sched_get_priority_min(sched_fifo), sched_get_priority_max( SCHED_FIFO ) ); printf("priority-range SCHED_RR: %d - %d\n", sched_get_priority_min(sched_rr), sched_get_priority_max( SCHED_RR)); printf("current Scheduling Policy: %s\n", Policies[sched_getscheduler(0)] ); sched_rr_get_interval( 0, &rr_time ); printf("intervall for Policy RR: %ld [s] %ld [nanosec]\n", rr_time.tv_sec, rr_time.tv_nsec ); int main( int argc, char **argv ) { struct sched_param scheduling_parameter; struct timespec t; int i,j,k,p; print_scheduling_parameter(); sched_getparam( 0, &scheduling_parameter ); printf("priority: %d\n", scheduling_parameter.sched_priority ); // only superuser: // change scheduling policy and priority to realtime priority scheduling_parameter.sched_priority = PRIORITY_OF_THIS_TASK; if( sched_setscheduler( 0, SCHED_RR, &scheduling_parameter )!= 0 ) { perror( "Set Scheduling Priority" ); exit( -1 ); sched_getparam( 0, &scheduling_parameter ); printf("priority: %d\n", scheduling_parameter.sched_priority ); t.tv_sec = 10; // sleep t.tv_nsec = ; nanosleep( &t, NULL ); print_scheduling_parameter(); return 0; Multicore-Architekturen bieten durch die CPU-Isolation und -Affinität interessante Möglichkeiten, Anforderungen an das Zeitverhalten zu erfüllen. Dazu werden einzelne Prozessor-Kerne für Echtzeitaufgaben reserviert und kritische Jobs auf diese Kerne fixiert. Zentral hierfür ist die Datenstruktur cpu_set_t, die die Prozessorkerne repräsentiert. Jerder Kern cpu ist dabei durch ein einzelnes Bit dargestellt. Auf diese Datenstruktur sind eine Reihe von Funktionen (Methoden) anwendbar, die das Bitfeld set löschen (void CPU_ZERO(cpu_set_t *set)) und einzelne Bits setzen (void CPU_SET(int cpu, cpu_set_t *set)) beziehungsweise rücksetzen (void CPU_CLR(int cpu, cpu_set_t *set)). Per int CPU_ISSET(int cpu, cpu_set_t *set) wird überprüft, ob die CPU cpu im Set set gesetzt ist oder nicht. Um einen Job auf spezifische CPU-Kerne zu fixieren verwenden Sie die Funktion int sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask). Beispiel Erzwungene Prozessmigration zeigt die programmtechnische Anwendung. Die Vorbereitung des Systems finden Sie in Abschnitt Mehrkernmaschine beschrieben. 67

73 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Beispiel 4-4. Erzwungene Prozessmigration #include <stdio.h> #include <stdlib.h> #define USE_GNU #include <sched.h> #include <errno.h> int main( int argc, char **argv ) { cpu_set_t mask; unsigned int cpuid, pid; if( argc!= 3 ) { fprintf(stderr,"usage: %s pid cpuid\n", argv[0]); return -1; pid = strtoul( argv[1], NULL, 0 ); cpuid = strtoul( argv[2], NULL, 0 ); CPU_CLR( cpuid, &mask ); // Aktuelle CPU verbieten. if( sched_setaffinity( pid, sizeof(mask), &mask ) ) { perror("sched_setaffinity"); // UP-System??? return -2; return 0; // vim: aw ic ts=4 sw=4: 4.2. Schutz kritischer Abschnitte Zur Lösung einer Echtzeitaufgabe werden im Normalfall mehrere Threads oder Prozesse verwendet, die (quasi-)parallel arbeiten. Man unterscheidet dabei diskunkte Prozesse, bei denen der Ablauf eines Prozesses (respektive Threads) unabhängig von den anderen Prozessen, zu denen er disjunkt ist, nicht disjunkte Prozesse, die auf gemeinsamen Variablen arbeiten. Diese lassen sich wiederum unterscheiden in: Konkurrierende Prozesse, die sich um den Zugriff auf Daten konkurrieren und Kooperierende Prozesse (meist verkettet), bei denen der eine Prozess Daten für den anderen Prozess liefert (Hersteller/Verbrauchermodell). Die Wirkung der gegenseitigen Beeinflussung nicht disjunkter paralleler Prozesse ist ohne Synchronisation nicht vorhersagbar und im Regelfall nicht reproduzierbar. Der Programmteil, in dem auf gemeinsame Betriebsmittel (z.b. Daten) zugegriffen wird, heißt kritischer Abschnitt (critical section). Greifen zwei oder mehr Codesequenzen (Threads, Prozesse oder auch Interrupt-Service-Routinen) auf dieselben Daten zu, kann es zu einer sogenannten race condition kommen. Bei einer race condition hängt das Ergebnis des Zugriffs vom Prozessfortschritt ab (siehe hierzu auch 68

74 Semaphor und Mutex Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Race conditions lassen sich vermeiden, indem nie mehr als ein Prozess in einen kritischen Abschnitt eintritt (mutual exclusion, gegenseitiger Ausschluß). Dieses läßt sich mithilfe von Semaphoren sicherstellen. Ein Semaphor wird zur Synchronisation, inbesondere bei gegenseitigem Ausschluß verwendet. Es handelt sich dabei um ein Integer Variable, die wie folgt verwendet wird: Der Wert des Semaphors wird auf einen Maximalwert N initialisiert. Bei einem Zugriff auf das Semaphor (P-Operation, das P stammt aus dem holländischen und steht für passeeren) wird dessen Wert um 1 erniedrigt und der Prozess schlafend gelegt, wenn der neue Semaphor-Wert negativ ist. Bei der Freigabe eines Semaphors (V-Operation, V leitet sich vom holländischen vrijgeven ab) wird dessen Wert um 1 erhöht und falls der neue Semaphor-Wert negativ oder gleich Null ist, ein auf das Semaphor wartender (schlafender) Prozess aufgeweckt. P-Operation s = s - 1; if (s < 0) { sleep_until_semaphore_is_free(); V-Operation s = s + 1; if (s <= 0) { wake_up_sleeping_process_with_highest_priority(); P- und V-Operationen sind selbst kritische Abschnitte, deren Ausführung nicht unterbrochen werden darf. Daher sind Semaphor-Operationen im Betriebssystem als System-Calls verankert, und während ihrer Ausführung (im Kernel) sind Interrupts gesperrt. Bei einfachen Mikroprozessoren bzw. Laufzeitsystemen werden Semaphoroperationen ansonsten durch Test-And-Set-Befehle des Mikroprozessors realisiert. Ein Mutex ist ein Semaphor mit Maximalwert N=1 (binärer Semaphor). POSIX-Funktionen für Mutexe: int pthread_mutex_destroy pthread_mutex_t * mutex Über diese Funktion wird ein Mutex (Semaphor, das genau einem Thread den Zugriff auf den kritischen Abschnitt ermöglicht) gelöscht und die damit zusammenhängenden Ressourcen freigegeben. Ein Mutex wird nur entfernt, wenn es frei (unlocked) ist. 69

75 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung int pthread_mutex_lock pthread_mutex_t * mutex Über diese Funktion wird ein kritischer Abschnitt betreten. Sollte der kritische Abschnitt frei sein, wird das Mutex gesperrt (locked) und dem aufrufenden Thread zugehörig erklärt. In diesem Falle returniert die Funktion direkt. Sollte der kritische Abschnitt gesperrt sein, wird der aufrufende Thread solange in den Zustand schlafend versetzt, bis das Mutex wieder freigegeben wird. Ist das Mutex durch denselben Thread gesperrt, der den Aufruf durchführt, kann es zu einem Deadlock kommen. Um dies zu verhindern läßt sich ein Mutex so konfigurieren, dass es entweder in diesem Fall den Fehlercode EDEADLK returniert (ein error checking mutex oder den Mehrfachzugriff erlaubt (ein recursive mutex ). int pthread_mutex_trylock pthread_mutex_t * mutex Diese Funktion verhält sich genauso wie die Funktion pthread_mutex_lock mit der Ausnahme, dass sie nicht blockiert, falls der kritische Abschnitt nicht betreten werden kann. In diesem Fall returniert die Funktion EBUSY. int pthread_mutex_init pthread_mutex_t * mutex const pthread_mutexattr_t * mutexattr Diese Funktion initalisiert ein Mutex mutex gemäß der Angaben im Parameter mutexattr. Ist mutexattr NULL werden default Werte zur Initialisierung verwendet. int pthread_mutex_unlock pthread_mutex_t * mutex Über diese Funktion wird ein kritischer Abschnitt wieder verlassen. Beispiel 4-5. Programmbeispiel Mutex #include <stdio.h> #include <unistd.h> #include <pthread.h> static pthread_mutex_t mutex; int main( int argc, char **argv, char **envp ) { pthread_mutex_init( &mutex, NULL ); pthread_mutex_lock( &mutex ); // enter critical section printf("%d in critical section...\n", getpid()); pthread_mutex_unlock( &mutex ); // leave critical section pthread_mutex_destroy( &mutex ); return 0; Ein Semaphor kann zur kooperativen Synchronisation zwischen Rechenprozessen verwendet werden, indem die zusammengehörigen P- und V-Operationen aufgesplittet werden (siehe auch den Abschnitt Condition-Variable (Event)). 70

76 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Task C B A P(X) P(X) t 1 t 2 t 3 t 4 t 5 t 1 t 2 t 3 t 4 t 5 Task A belegt die Ressource X Task B (mit höherer Priorität)wird rechenbereit Task C (mit der höchsten Priorität) wird rechenbereit Task C versucht die Ressource X zu belegen und wird schlafen gelegt Task B hat von den rechenbereiten Prozessen die höchste Priorität Abbildung 4-1. Prioritäten und Synchronisation Prioritätsinversion Unter Prioritätsinversion versteht man die Situation, dass eine niedrigpriore Task eine Ressource alloziert hat (über ein Semaphor), die von einer hochprioren Task benötigt wird. Dadurch wird die Bearbeitung der hochprioren Task solange verzögert, bis die niedrigpriore Task die Ressource freigibt. Das kann aber unzumutbar lang dauern, wenn im System eine Reihe Tasks lauffähig sind, die mittlere Priorität haben (siehe Abbildung Prioritäten und Synchronisation). Task C B A P(X) t 1 t 2 t 3 t 4 P(X) V(X) t 5 V(X) t 1 t 2 t 3 t 4 t 4 t 5 Task A belegt die Ressource X Task B (mit höherer Priorität)wird rechenbereit Task C (mit der höchsten Priorität) wird rechenbereit Task C versucht die Ressource X zu belegen und wird schlafen gelegt Task A erbt die Priorität von Task C und wird rechenbereit Task A gibt X wieder frei und bekommt die ursprüngliche Priorität Abbildung 4-2. Prioritätsinversion Das angesprochene Problem wird über die Methode der Prioritätsvererbung (Priority Inheritance) gelöst. Es gibt unterschiedliche Protokolle zur Prioritätsvererbung, zum Beispiel das 1. Priority Inheritance Protocol (PIP) oder das 2. Priority Ceiling Protocol (PCP). 71

77 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Priority Inheritance Protocol. Falls ein Job J R ein Mutex (oder auch ein Semaphor) anfordert, das gegenwärtig von einem Job J O mit niedrigerer Priorität gehalten wird, dann wird die Priorität von J O auf die Priorität von Job J R angehoben. Sobald der Job J O das Mutex wieder freigibt, bekommt er seine initiale Priorität zurück. Die Prioritätsvererbung ist transitiv. Falls der Job J O bereits die Priorität von J R geerbt hat und ein weiterer Job J S, dessen Priorität größer als die von J R ist, ankommt, erbt J O dir Priorität von J S. Gibt J O die Ressource wieder frei, erhält J O seine ursprüngliche Priorität zurück. Die Ressource selbst wird dem anfordernden Prozess mit der höchsten Priorität zugeteilt (im Beispiel also J S ). Mit Hilfe dieses Protokolls lassen sich sehr viele Probleme lösen, insbesondere wenn die Jobs nur um eine Ressource konkurrieren. Es gilt allerdings zu beachten, dass die Transitivität des Protokolls nicht bei allen Implementierungen gesichert ist, die Implementierungen können sich also bezüglich ihres Verhaltens im Detail unterscheiden! Priority Ceiling Protocol. Das Priority Ceiling Protocol ähnelt dem Priority Inheritance Protocol insofern, dass auch hier der blockierte Job dem Job, der die Ressource belegt hat, die Priorität vererbt. Unterschiede aber gibt es bei der Freigabe des Mutex. In diesem Fall bekommt der freigebende Job nicht seine ursprüngliche Priorität zurück, sondern erhält die höchste Priorität, die ein anderer Job besitzt, mit dem er ebenfalls ein anderes Mutex teilt. Damit das Priority Ceiling Protocol angewendet werden kann, muss also bereits vor dem Start bekannt sein, welche Ressourcen die Jobs benötigen. Das Priority Ceiling Protocol ist unter Umständen in der Lage, Deadlock-Situation zu verhindern. Gesichert ist dies allerdings nicht. Beispiel Prioritätsinversion aktivieren zeigt, wie bei der Initialisierung des Mutex Prioritätsinversion eingeschaltet wird. Allerdings ist diese nur dann wirksam, wenn der Rechenprozess eine Echtzeitpriorität besitzt, also der Schedulingklasse rt_sched_class zugeordnet ist (siehe Abschnitt Tasks parametrieren). Beispiel 4-6. Prioritätsinversion aktivieren #include <stdio.h> #include <unistd.h> #define USE_UNIX98 /* Needed for PTHREAD_PRIO_INHERIT */ #include <pthread.h> static pthread_mutex_t mutex; static pthread_mutexattr_t attr; int main( int argc, char **argv, char **envp ) { // don t forget: rt-priority is needed for priority inheritance pthread_mutexattr_init( &attr ); pthread_mutexattr_setprotocol( &attr, PTHREAD_PRIO_INHERIT ); pthread_mutex_init( &mutex, &attr ); pthread_mutex_lock( &mutex ); // enter critical section printf("%d in critical section...\n", getpid()); pthread_mutex_unlock( &mutex ); // leave critical section pthread_mutex_destroy( &mutex ); return 0; 72

78 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Deadlock Task A P(S1) Task B P(S2) P(S2) P(S1) Deadlock S1 ist durch Task A blockiert, S2 durch Task B V(S2) V(S1) V(S1) V(S2) Abbildung 4-3. Deadlock Durch critical sections (gegenseitigem Ausschluß) kann es leicht zu Verklemmungen, den sogenannten Deadlocks kommen. Muss eine Task beispielsweise zwei Datenstrukturen manipulieren, die durch zwei unabhängige Semaphore geschützt sind, und eine zweite Task muss das gleiche tun, nur dass sie die Semaphore in umgekehrter Reihenfolge allokiert, gibt es eine Verklemmung (Deadlock). In der Praxis kommen derartige Konstellationen häufiger vor. Dabei sind sie nur sehr schwer zu entdecken, da das Nehmen und Freigeben des Semaphors nicht selten in Funktionen gekapselt sind. Deadlocks lassen sich durch zwei Maßnahmen vermeiden: 1. entweder durch geeignete Systemauslegung und Programmierung oder dadurch, dass 2. nur dann Anforderungen an Betriebsmittel befriedigt werden, wenn sichergestellt ist, dass durch die Anforderung keine Deadlock-Situation entstehen kann. Zur Überprüfung, ob durch eine spezifische Anforderung eine Deadlock-Situation entstehen kann oder nicht, haben die Informatiker den Bankers-Algorithmus entwickelt. Der Einsatz desselben führt allerdings zu einem völlig verzehrten Zeitverhalten und soll hier daher nicht weiter betrachtet werden. Beim Software-Entwurf können Deadlocks unter Umständen durch die Modellierung und Analyse des zugehörigen Petrinetzes entdeckt werden. Die Erkennung, Vermeidung und Behandlung von Deadlocks gehört zu den schwierigsten Aufgaben bei der Programmierung moderner Anwendungen! Schreib-/Lese-Locks Wie hier vorgestellt wird ein Job blockiert, sobald er auf einen kritischen Abschnitt zugreifen möchte, der bereits durch einen anderen Job belegt ist. Falls der kritische Abschnitt aus dem Zugriff auf globale Daten besteht, kommt es nur dann zu einer Race- Condition, falls die zugreifenden Rechenprozesse die Daten modifizieren. Das parallele Lesen ist jedoch 73

79 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung unkritisch. Aus dieser Erkenntnis heraus bieten viele Systeme sogenannte Schreib-/Lese-Locks an. Das sind Semaphore, die parallele Lesezugriffe erlauben, aber einem schreibenden Job einen exklusiven Zugriff ermöglichen. Bei der Anforderung des Mutex muss der Rechenprozess mitteilen, ob er den kritischen Abschnitt nur zum Lesen, oder auch zum Schreiben betreten möchte. Folgende Fälle werden unterschieden: 1. Der kritische Abschnitt ist frei: Der Zugriff wird gewährt. 2. Der kritische Abschnitt ist von mindestens einem Rechenprozess belegt, der lesend zugreift und es gibt zur Zeit keinen Rechenprozess, der schreibend zugreifen möchte: Ein lesend wollender Prozess bekommt Zugriff. Ein Prozess, der schreiben möchte, wird blockiert. 3. Der kritische Abschnitt ist von mindestens einem Rechenprozess belegt, der lesend zugreift und es gibt zur Zeit mindestens einen Rechenprozess, der schreibend zugreifen möchte: Der anfragende Rechenprozess wird blockiert (unabhängig davon, ob er lesen oder schreiben möchte). 4. Der kritische Abschnitt ist von einem Prozess belegt, der schreibend zugreift: Der anfragende Rechenprozess wird blockiert (unabhängig davon, ob er lesen oder schreiben möchte). Auch einen lesen wollenden Prozess zu blockieren, sobald ein Rechenprozess zum Schreiben den kritischen Abschnitt betreten möchte ist notwendig, damit es nicht zum sterben des schreiben wollenden Jobs kommt Weitere Schutzmaßnahmen für kritische Abschnitte Ein Semaphor läßt sich dann zum Schutz kritischer Abschnitte einsetzen, wenn der Abschnitt durch zwei oder mehr Applikationen (User-Prozesse) betreten werden soll. Kritische Abschnitte aber, die beispielsweise innerhalb des Betriebssystemkerns, eines Treibers oder in Interrupt-Service-Routinen vorkommen, können damit nicht gesichert werden. Hier werden andere Techniken benötigt: Unterbrechungssperren und Spinlocks. Unterbrechungssperre. Hierbei werden die Interrupts auf einem System für die Zeit des Zugriffs auf den kritischen Abschnitt gesperrt. Um die Latenzzeiten kurz zu halten, darf der Zugriff selbst nur kurz dauern. Bei modernen Betriebssystemen ist diese Methode nur für Zugriffe innerhalb des Betriebssystemkerns, also beispielsweise bei der Realisierung von Treibern, interessant, denn nur hier lassen sich Interrupts sperren. Spinlocks. Da bei Multiprozessorsysteme (SMP=Symmetric Multiprocessing) zwei oder mehr Interruptserviceroutinen real parallel bearbeitet werden können, hilft eine lokale Unterbrechungssperre nicht weiter. Hier arbeitet man im Regelfall mit sogenannten Spinlocks. Bei Spinlocks entscheidet - wie schon beim Semaphor - eine Variable darüber, ob ein kritischer Abschnitt betreten werden darf oder nicht. Ist der kritische Abschnitt bereits besetzt, wartet die zugreifende Einheit aktiv solange, bis der kritische Abschnitt wieder freigegeben worden ist. Damit ergeben sich für die vorgestellten drei Methoden zum gegenseitigen Ausschluß bei einem kritischen Abschnitt unterschiedliche Einsatzfelder. Die Unterbrechungssperre steht normalen Applikationen nicht zur Verfügung. Bei Einprozessorsystemen (UP=Uni-Processor) wird selbige im Betriebssystemkerns eingesetzt. Bei SMP lassen sich zwar die Interrupts auf allen Prozessoren sperren, bevor aber auf den kritischen Abschnitt zugegriffen werden kann ist sicherzustellen, dass nicht zufällig zwei Prozessoren die ISR bearbeiten. 74

80 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Spinlocks, die ein aktives Warten realisieren, lassen sich auf Applikations- und auf Mehrprozessormaschienen auch auf Kernelebene einsetzen. Das Semaphor schließlich ist für den gegenseitigen Ausschluß auf Applikationsebene gedacht. Es ist sowohl für UP- als auch für SMP-Umgebungen verwendbar. Der Einsatz im Betriebssystemkern ist möglich, aber nur dann, wenn der kritische Abschnitt nicht von einer ISR betreten werden soll. In diesem Fall müsste man nämlich die ISR schlafen legen, was nicht möglich ist Programmtechnischer Umgang mit Zeiten year = ORIGINYEAR; /* = 1980 */ while (days > 365) { if (IsLeapYear(year)) { if (days > 366) { days -= 366; year += 1; else { days -= 365; year += 1; Abbildung 4-4. Weil der Programmierer des Treibers für die Echtzeituhr das Schaltjahr nicht richtig programmierte, blieben am 31. Dezember 2008 viele MP3-Player der Firma Microsoft (Zune-Player) hängen. Betriebssysteme stellen Echtzeitapplikationen Zeitgeber mit unterschiedlichen Eigenschaften zur Verfügung, über die das Zeitverhalten kontrolliert wird. Diese sind gekennzeichnet durch ihre Genauigkeit, die Zuverlässigkeit, den Bezugspunkt, die Darstellung und den maximalen Zeitbereich. Das Betriebssystem repräsentiert Zeiten unter anderem mit den folgenden Datentypen (Darstellung): clock_t: Timerticks. struct timeval: Zeit in Mikrosekunden-Auflösung. struct timespec: Zeit in Nanosekunden-Auflösung. struct tm: absolute Zeitangabe. struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ ; struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ ; struct tm { int tm_sec; /* seconds */ 75

81 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung ; int tm_min; /* minutes */ int tm_hour; /* hours */ int tm_mday; /* day of the month */ int tm_mon; /* month */ int tm_year; /* year */ int tm_wday; /* day of the week */ int tm_yday; /* day in the year */ int tm_isdst; /* daylight saving time */ Die Strukturen struct timeval und struct timespec bestehen aus jeweils zwei Variablen, die einmal den Sekundenanteil und einmal den Mikro- beziehungsweise den Nanosekundenanteil repräsentieren. Die Darstellung erfolgt jeweils normiert. Das bedeutet, dass der Mikro- oder Nanosekundenanteil immer kleiner als eine volle Sekunde bleibt. Ein Zeitstempel von beispielsweise 1 Sekunde, Mikrosekunden ist gültig, 1 Sekunde, Mikrosekunden ist ungültig. In normierter Darstellung ergäbe sich 2 Sekunden, Mikrosekunden. Die Darstellungs- beziehungsweise Repräsentationsform reflektiert auch den darstellbaren Wertebereich. Da bei den dargestellten Datenstrukturen für den Typ time_t ein long eingesetzt wird, lassen sich auf einer 32-Bit Maschine rund 4 Milliarden Sekunden zählen, auf einer 64-Bit Maschine 2 64 (mehr als 500 Milliarden Jahre). Als Bezugspunkte haben sich die folgenden eingebürgert: Start des Systems, Start eines Jobs und Start einer Epoche, beispielsweise "Christi Geburt" oder der (Unix-Epoche). Dieser Bezugspunkt weist zudem noch eine örtliche Komponente auf: Der Zeitpunkt 19:00 Uhr in Europa entspricht beispielsweise einem anderen Zeitpunkt in den USA (minus sechs Stunden zur Ostküste). Die Genauigkeit wird beeinflußt durch, die Taktung des Zeitgebers, deren Schwankungen und durch Zeitsprünge. Das Attribut Zuverlässigkeit eines Zeitgebers beschreibt dessen Verhalten bei (bewußten) Schwankungen der Taktung und bei Zeitsprüngen: Ein Zeitgeber kann beispielsweise der Systemuhr folgen (CLOCK_REALTIME) oder unabhängig von jeglicher Modifikation an der Systemzeit einfach weiterzählen (CLOCK_MONOTONIC). Die Posix-Realzeiterweiterung beziehungsweise Linux-Systeme definieren hierzu folgende Clocks [Manpage: clock_gettime]: CLOCK_REALTIME. Dieser Zeitgeber repräsentiert die systemweite, aktuelle Zeit. Er reagiert auf Zeitsprünge, sowohl vorwärts als auch rückwärts, die beispielsweise beim Aufwachen (Resume) nach einem Suspend (Schlafzustand des gesamten Systems) ausgelöst werden. Er reagiert ebenfalls auf unterschiedliche Taktungen, die beispielsweise durch NTP erfolgen. Dieser Zeitgeber liefert die Sekunden und Nanosekunden seit dem UTC (Unixzeit) zurück. CLOCK_MONOTONIC. Dieser Zeitgeber läuft entsprechend seiner Auflösung stets vorwärts, ohne dabei Zeitsprünge zu vollziehen. Er ist also unabhängig von der mit Superuserprivilegien zu verändernden Systemuhr. Allerdings reagiert dieser Zeitgeber auf Modifikationen der Taktung, die beispielsweise durch NTP erfolgen. CLOCK_MONOTONIC_RAW. Dieser Zeitgeber ist linuxspezifisch. Er reagiert weder auf Zeitsprünge noch auf in Betrieb geänderte Taktungen (NTP). CLOCK_PROCESS_CPUTIME_ID. Dieser Zeitgeber erfasst die Verarbeitungszeit (Execution Time) des zugehörigen Prozesses. Das funktioniert aber nur zuverlässig auf Single-Core-Systemen beziehungsweise wenn sichergestellt werden kann, dass keine Prozessmigration stattfindet. 76

82 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung CLOCK_THREAD_COMPUTE_ID. Dieser Zeitgeber erfasst die Verarbeitungszeit (Execution Time) des zugehörigen Threads. Das funktioniert aber nur zuverlässig auf Single-Core-Systemen beziehungsweise wenn sichergestellt werden kann, dass keine Prozessmigration stattfindet. Der maximale Zeitbereich schließlich ergibt sich durch die Auflösung des Zeitgebers und die Bitbreite der Variablen: zeitbereich = auflösung * 2 bitbreite Zeit lesen Es gibt unterschiedliche Systemfunktionen, mit denen die aktuelle Zeit gelesen werden kann. Favorisiert ist die Funktion int clock_gettime(clockid_t clk_id, struct timespec *tp), die die Zeit seit dem (Unixzeit) als Universal Time (Zeitzone UTC) zurückliefert (struct timespec). Konnte die aktuelle Zeit gelesen werden, gibt die Funktion Null, ansonsten einen Fehlercode zurück. Allerdings ist das Auslesen auf 32-Bit Systemen in so fern problematisch, da der 32-Bit Zähler am 19. Januar 2038 überläuft. Vor allem eingebettete Systeme, bei denen mit einer jahrzentelangen Standzeit geplant wird, könnten von diesem Zählerüberlauf betroffen sein. Wichtig ist, dass Sie in diesem Fall entsprechend programmiert sind (siehe Der Zeitvergleich). struct timepspec timestamp;... if (clock_gettime(clock_monotonic,&timestamp)) { perror("timestamp"); return -1; printf("seconds: %ld, nanoseconds: %ld\n", timestamp.tv_sec, timestamp.tv_nsec); Durch die Wahl der Clock CLOCK_PROCESS_CPUTIME_ID beziehungsweise CLOCK_THREAD_CPUTIME_ID kann auch die Verarbeitungszeit ausgemessen werden (Profiling). Die Genauigkeit der zurückgelieferten Zeit kann mit Hilfe der Funktion clock_getres(clockid_t clk_id, struct timespec *res) ausgelesen werden. Die Funktion clock_gettime() ist nicht in der Standard-C-Bibliothek zu finden, sondern in der Realzeit-Bibliothek librt. Daher ist bei der Programmgenerierung diese Bibliothek hinzuzulinken (Parameter -lrt). Steht nur die Standard-C-Bibliothek zur Verfügung, kann time_t time(time_t *t) oder auch int gettimeofday(struct timeval *tv, struct timezone *tz) eingesetzt werden. time() gibt die Sekunden zurück, die seit dem (UTC) vergangen sind. time_t now;... now = time(null); gettimeofday() schreibt an die per tv übergebene Speicheradresse die Sekunden und Mikrosekunden seit dem Das Argument tz wird typischerweise mit NULL angegeben. Liegen die Sekunden seit dem vor (timestamp.tv_sec), können diese mit Hilfe der Funktionen struct tm *localtime_r(const time_t *timep, struct tm *result); time_t *timep, struct tm *result); in die Struktur struct tm konvertiert werden. struct tm absolute_time; if (localtime_r( timestamp.tv_sec, &absolute_time )==NULL) { perror( "localtime_r" ); return -1; printf("year: %d\n", absolute_time.tm_year); oder struct tm *gmtime_r(const 77

83 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Die Funktion time_t mktime(struct tm *tm) konvertiert eine über die Struktur struct tm gegebene Zeit in Sekunden seit dem (time_t). Mit Hilfe der Funktion clock_t times(struct tms *buf) lässt sich sowohl die aktuelle Zeit zu einem letztlich nicht genau definierten Bezugspunkt, als auch die Verarbeitungszeit (Execution-Time) des aufrufenden Prozesses bestimmen. Die Zeiten werden als Timerticks (clock_t) zurückgeliefert. Die zurückgelieferte Verarbeitungszeit ist aufgeschlüsselt in die Anteile, die im Userland und die Anteile, die im Kernel verbraucht wurden. Außerdem werden diese Anteile auch für Kindprozesse gelistet. #include <stdio.h> #include <sys/times.h> #include <unistd.h> #include <time.h> int main( int argc, char **argv, char **envp ) { struct tms exec_time; clock_t act_time; long ticks_per_second; long tickduration_in_ms; ticks_per_second = sysconf(_sc_clk_tck); tickduration_in_ms = 1000/ticks_per_second; act_time = times( &exec_time ); printf("actual time (in ms): %ld\n", act_time*tickduration_in_ms); printf("execution time (in ms): %ld\n", (exec_time.tms_utime+exec_time.tms_stime)*tickduration_in_ms); return 0; Sehr genaue Zeiten lassen sich erfassen, falls der eingesetzte Prozessor einen Zähler besitzt, der mit der Taktfrequenz des Systems getaktet wird. Bei einer x86-architektur (PC) heißt dieser Zähler Time Stamp Counter (TSC). Der TSC kann auch von einer normalen Applikation ausgelesen werden, allerdings muss sichergestellt sein, dass sich die Taktfrequenz zwischen zwei Messungen ändert. Alternativ kann man sich vom Betriebssystem über die Taktänderung informieren lassen Der Zeitvergleich Zwei Absolutzeiten (struct tm) werden am einfachsten über deren Repräsentation in Sekunden verglichen. Die Umwandlung erfolgt über die Funktion (time_t mktime(struct tm *tm)). Allerdings ist dabei zu beachten, dass es auf einem 32-Bit System am 19. Januar 2038 zu einem Überlauf kommt. Wird einer der beiden Zeitstempel vor dem 19. Januar 2038 genommen, der andere danach, kommt es zu einem falschen Ergebnis, wenn nur die beiden Werte per "<" beziehungsweise ">" verglichen werden. Das ist ein generelles Problem und kann dann gelöst werden, wenn sichergestellt ist, dass die zu vergleichenden Zeiten nicht weiter als die Hälfte des gesamten Zeitbereiches auseinanderliegen. In diesem Fall lassen sich die Makros einsetzen, die im Linux-Kernel für den Vergleich zweier Zeiten eingesetzt werden. Das Makro time_after(a,b) liefert true zurück, falls es sich bei a um eine spätere Zeit als b handelt. Das Makro time_after_eq(a,b) liefert true zurück, falls es sich bei a um eine spätere Zeit oder um die gleiche Zeit handelt, wie b handelt. Die Zeitstempel a und b müssen beide vom Typ unsigned long sein. Natürlich können die Makros auch auf andere Datentypen angepasst werden [HEADERDATEI linux/jiffies.h]. #define time_after(a,b) \ (typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \ ((long)(b) - (long)(a) < 0)) #define time_before(a,b) time_after(b,a) 78

84 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung #define time_after_eq(a,b) \ (typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \ ((long)(a) - (long)(b) >= 0)) #define time_before_eq(a,b) time_after_eq(b,a) Differenzzeitmessung In Echtzeitapplikationen ist es häufig notwendig eine Zeitdauer zu messen, Zeitpunkte zu erfassen oder eine definierte Zeit verstreichen zu lassen. Dabei sind folgende Aspekte zu beachten: Die Genauigkeit der eingesetzten Zeitgeber, die maximalen Zeitdifferenzen, Schwankungen der Zeitbasis, beispielsweise durch Schlafzustände, Modifikationen an der Zeitbasis des eingesetzten Rechnersystems (Zeitsprünge) und die Ortsabhängigkeit absoluter Zeitpunkte. Zur Bestimmung einer Zeitdauer verwendet man häufig eine Differenzzeitmessung. Dabei wird vor und nach der auszumessenden Aktion jeweils ein Zeitstempel genommen. Die Zeitdauer ergibt sich aus der Differenz dieser beiden Zeitstempel. Dies ist allerdings nicht immer ganz einfach. Liegt der Zeitstempel beispielsweise in Form der Datenstruktur struct timeval (als Ergebnis der Funktion gettimeofday()) vor, müssen die Sekunden zunächst getrennt von den Mikrosekunden subtrahiert werden. Ist der Mikrosekundenanteil negativ, muss der Sekundenanteil um eins erniedrigt und der Mikrosekundenanteil korrigiert werden. Dazu wird auf die Anzahl der Mikrosekunden pro Sekunde (also eine Million) der negative Mikrosekundenanteil addiert. #define MICROSECONDS_PER_SECOND struct timespec * diff_time( struct timeval before, struct timeval after, struct timeval *result ) { if (result==null) return NULL; result->tv_sec = after.tv_sec - before.tv_sec; result->tv_usec= after.tv_usec- before.tv_usec; if (result->tv_usec<0) { result->tv_sec--; /* result->tv_usec is negative, therefore we use "+" */ result->tv_usec = MICROSECONDS_PER_SECOND+result->tv_usec; return result; Für Zeitstempel vom Typ struct timeval gilt entsprechendes. Anstelle der MICROSECONDS_PER_SECOND sind NANOSECONDS_PER_SECOND einzusetzen. Sind die Zeitstempel vorzeichenlos, sieht die Rechnung für den Sekundenanteil etwas komplizierter aus. Das soll hier aber nicht weiter vertieft werden. Etwas einfacher ist die Differenzbildung, wenn aus der Datenstruktur eine einzelne Variable mit der gewünschten Auflösung, beispielsweise Mikrosekunden, generiert wird. Im Fall von struct timeval wird dazu der Sekundenanteil mit einer Million multipliziert und der Mikrosekundenanteil aufaddiert. Bei der Multiplikation können natürlich Informationen verloren gehen, allerdings geht der gleiche Informationsgehalt auch beim zweiten Zeitstempel verloren: Für die Differenzbildung ist das damit nicht relevant, solange der zu messende zeitliche Abstand kleiner als 1000 Sekunden ist und es während der Messung keinen Überlauf beim Sekundenanteil gibt. time_in_usec=((nachher.tv_sec* )+nachher.tv_usec)- 79

85 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung ((vorher.tv_sec* )+vorher.tv_usec); Schlafen Threads legen sich für eine Zeitspanne oder aber bis zu einem Punkt, an dem sie aufgeweckt werden, schlafen (absolut oder relativ schlafen). Innerhalb von Echtzeitapplikationen kann zusätzlich die Zeitquelle (CLOCK_MONOTONIC oder CLOCK_REALTIME) eingestellt werden. Ob relativ oder absolut geschlafen wird, hat durchaus Relevanz: Wird das Schlafen unterbrochen und danach mit der Restzeit neu aufgesetzt, kommt es durch den zusätzlichen Aufruf zu einer Verzögerung. Bei der Verwendung einer absoulten Weckzeit fallen zusätzliche Aufrufe nicht ins Gewicht. Ein Thread kann sich durch Aufruf der Funktion int nanosleep(const struct timespec *req, struct timespec *rem); für die über req definierte Zeitspanne schlafen legen. Hierbei wird zwar offiziell die Zeitquelle CLOCK_REALTIME verwendet, eine Änderung der Schlafenszeit wird aber nachgeführt. Defacto basiert nanosleep() daher auf CLOCK_MONOTONIC. Der schlafende Thread wird aufgeweckt, wenn entweder die angegebene Relativzeit abgelaufen ist, oder der Thread ein Signal gesendet bekommen hat. Ist letzteres der Fall gewesen und hat der Aufrufer den Parameter rem mit einer gültigen Hauptspeicheradresse versehen, legt das Betriebssystem in diesem Speicher die noch übrig gebliebene Schlafenszeit ab. Genauer kann das Verhalten beim Schlafenlegen über die Funktion int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *request, struct timespec *remain); eingestellt werden. Über den Parameter clock_id wird die Zeitquelle (CLOCK_REALTIME, CLOCK_MONOTONIC) eingestellt. Der Parameter flag erlaubt die Angabe, ob die Zeitangabe request relativ (flag==0) oder absolut (flag==timer_abstime) zu interpretieren ist. Um die Restzeit bei einem durch ein Signal provozierten vorzeitigen Abbruch aufzunehmen, kann per remain eine Speicheradresse dafür übergeben werden. Beispiel 4-7. Schlafenlegen einer Task #include <stdio.h> #include <time.h> #include <unistd.h> #include <errno.h> #include <signal.h> void sigint_handler(int signum) { printf("sigint (%d)\n", signum); int main( int argc, char **argv, char **envp ) { struct timespec sleeptime; struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = sigint_handler; if (sigaction(sigint, &sa, NULL) == -1) { perror( "sigaction" ); return -1; printf("%d going to sleep for 10 seconds...\n", getpid()); clock_gettime( CLOCK_MONOTONIC, &sleeptime ); sleeptime.tv_sec += 10; sleeptime.tv_nsec += 0; while (clock_nanosleep(clock_monotonic,timer_abstime, &sleeptime,null)==eintr) { printf("interrupted...\n"); 80

86 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung printf("woke up...\n"); return 0; Die Funktion clock_nanosleep() steht nur über die Realzeitbibliothek librt zur Verfügung. Beim Linken ist daher die Option -lrt mit anzugeben. Neben nanosleep() finden sich in der Standard-C-Bibliothek noch weitere Funktionen, mit denen Threads schlafen gelegt werden können: int usleep(useconds_t usec), unsigned int sleep(unsigned int seconds) und int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout). Die Funktion sleep() bekommt die Schlafenszeit als eine Anzahl von Sekunden übergeben, bei usleep() sind es Mikrosekunden. select() schließlich ist eigentlich nicht primär dazu gedacht, Jobs schlafen zu legen. Diese Eigenschaft wurde insbesondere früher genutzt, um eine portierbare Funktion zum Schlafenlegen bei einer Auflösung im Mikrosekundenbereich zu haben Weckrufe per Timer Das Betriebssystem kann periodisch Funktionen, so genannte Timer aufrufen. Diese Funktionen werden typischerweise als Signal Handler oder im Rahmen eines Threads aktiviert. Dazu müssen zwei Datenstrukturen vorbereitet werden. struct sigevent speichert die Daten, die mit dem Timer selbst Funktion und der aufzurufenden Funktion zusammenhängen: struct sigevent { int sigev_notify; int sigev_signo; union sigval sigev_value; void (*sigev_notify_function) (union sigval); void *sigev_notify_attributes; pid_t sigev_notify_thread_id; ; Das Element sigev_notify legt fest, ob die Timerfunktion als Signal Handler oder im Rahmen eines Threads aktiviert wird. Interessant sind die Werte SIGEV_SIGNAL und SIGEV_THREAD. Im Fall von SIGEV_SIGNAL legt sigev_signo die Nummer des Signals fest, welches nach Ablauf des Auslöseintervalls dem Job gesendet wird. Das Feld sigev_value nimmt einen Parameter auf, der entweder dem Signal Handler oder der im Thread abgearbeiteten Funktion übergeben wird. Der Signal Handler selbst wird mit der Funktion sigaction() (siehe Signals) etabliert. Unter Linux kann auch noch der Thread spezifiziert werden, dem das Signal zuzustellen ist. Dazu ist der Parameter sigev_notify_thread_id mit der Thread-ID zu besetzen und anstelle von SIGEV_SIGNAL ist SIGEV_THREAD_ID für sigev_notify auszuwählen. Ist für sigev_notify SIGEV_THREAD ausgewählt, nimmt sigev_notify_function die Adresse der Funktion auf, die im Kontext eines Threads abgearbeitet wird. Per sigev_notify_attributes lässt sich die Threaderzeugung konfigurieren. Meistens reicht es aus, hier NULL zu übergeben. Die Datenstruktur struct sigevent wird per int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid) dem Kernel übergeben. clockid spezifiziert den Zeitgeber (CLOCK_REALTIME, CLOCK_MONOTONIC), evp die initialisierte struct sigevent und in timerid findet sich nach dem Aufruf die Kennung des erzeugten, aber deaktivierten Timers. Der beziehungsweise die Zeitpunkte, zu denen die Timerfunktion aufgerufen werden soll, wird über struct itimerspec konfiguriert: struct itimerspec { struct timespec it_interval; /* Timer interval */ struct timespec it_value; /* Initial expiration */ 81

87 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung ; it_value gibt in Sekunden und Nanosekunden die Zeit an, zu der erstmalig die Funktion oder der Signal Handler aufgerufen werden soll, it_interval spezifiziert in Sekunden und Nanosekunden die Periode. Ob die Zeiten absolut oder relativ gesehen werden, wird beim Aufruf der Funktion int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value, struct itimerspec * old_value) über den Parameter flags (0 oder TIMER_ABSTIME festgelegt. new_value übernimmt die zeitliche Parametrierung. Falls old_value ungleich NULL ist, findet sich in dieser Datenstruktur nach dem Aufruf das vorhergehende Intervall. Die Funktionen sind nur nutzbar, wenn die Realzeitbibliothek librt zur Applikation gebunden wird. Dazu ist beim Aufruf des Compilers die Option -lrt mit anzugeben. Beispiel 4-8. Periodische Taskaktivierung #include <stdio.h> #include <time.h> #include <signal.h> #include <unistd.h> void timer_function( union sigval parameter ) { printf("timer with id %d active\n", getpid()); return; int main( int argc, char **argv, char **envp ) { timer_t itimer; struct sigevent sev; struct itimerspec interval; printf("start process.\n"); sev.sigev_notify = SIGEV_THREAD; sev.sigev_notify_function = timer_function; sev.sigev_value.sival_int = 99; sev.sigev_notify_attributes = NULL; if (timer_create(clock_monotonic, &sev, &itimer ) == -1) { perror("timer_create"); return -1; interval.it_interval.tv_sec = 1; interval.it_interval.tv_nsec= 0; interval.it_value.tv_sec = 1; interval.it_value.tv_nsec = 0; timer_settime( itimer, 0, &interval, NULL ); /* activate timer */ sleep( 5 ); printf("end process.\n"); return 0; 4.4. Inter-Prozess-Kommunikation Die klassische Inter-Prozess-Kommunikation (Datenaustausch) bietet unter anderem die folgenden Methoden an: Pipes/Mailboxes/Messages Shared-Memory 82

88 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Sockets Pipes Zum Datenaustausch bieten Betriebssysteme einen Mailbox-Mechanismus (send/receive Interface) an. Dabei werden - im Regelfall unidirektional - Daten von Task 1 zu Task 2 transportiert und gequeued (im Gegensatz zu gepuffert, Nachrichten gehen also nicht verloren, da sie nicht überschrieben werden). In den meisten Realzeitbetriebssystemen und in Unix-Systemen ist ein Mailbox-Mechanismus über Pipes (FIFO) und über die sogenannten Messages implementiert. Messages gehören zur Gruppe der klassischen System-V-IPC. Da allerdings die Verwaltung der benötigten Message-Queues, also das Anlegen, die Verteilung von Zugriffsberechtigungen und das spätere Freigeben sehr kompliziert und vor allem fehleranfällig ist, sollten Messages in Realzeitapplikationen nicht verwendet werden und werden daher auch nicht weiter beschrieben. Werden demgegenüber bei der Kommunikation zwischen Tasks (unnamed) Pipes eingesetzt, ist ein solches Management nicht notwendig. Über den Systemcall int pipe(int piped[2]) werden zwei Deskriptoren reserviert. Über piped[1] können per write() Daten geschrieben, über piped[0] per read() gelesen werden. Typischerweise reserviert eine Task zunächst per pipe() die Pipe-Deskriptoren um danach per fork() oder pthread_create() eine neue Task zu starten, die die beiden Deskriptoren erbt. Da die Kommunikation über Pipes unidirektional ist, gibt jede der Task den Deskriptor wieder frei, der nicht benötigt wird. Beispiel 4-9. Interprozess-Kommunikation über Pipes #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main(int argc, char **argv, char **envp) { int pd[2]; /* pipe descriptor */ pid_t cpid; char c; if (pipe(pd) == -1) { perror("pipe"); return -1; if ((cpid=fork()) == -1) { perror("fork"); return -1; if (cpid == 0) { /* child reads from pipe */ close(pd[1]); /* close unused write end */ while (read(pd[0], &c, 1) > 0) write(stdout_fileno, &c, 1); write(stdout_fileno, "\n", 1); close(pd[0]); return 0; else { /* Parent writes argv[1] to pipe */ close(pd[0]); /* Close unused read end */ write(pd[1], "hello world", strlen("hello world")); close(pd[1]); /* Reader will see EOF */ wait(null); /* Wait for child */ return 0; 83

89 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Shared-Memory Bei einem Shared-Memory handelt es sich um einen gemeinsamen Speicherbereich einer oder mehrerer Tasks innerhalb eines Rechners. Die Realisierung eines gemeinsamen Speicherbereichs im Falle von Threads auf Basis globaler Variablen ist trivial. Wollen mehrere Rechenprozesse jedoch einen gemeinsamen Speicher nutzen, müssen sie diesen vom Betriebssystem anfordern. Ähnlich wie beim Dual-Port-RAM, bei dem allerdings unterschiedliche Prozessoren zugreifen, kann die Speicheradresse des gemeinsamen Speicherbereichs von Task zu Task unterschiedlich sein. Deshalb ist es auch hier wichtig, Pointer innerhalb von Datenstrukturen relativ zum Beginn (oder Ende) des Speicherbereichs anzugeben. Aus den gleichen Gründen wie sie für die Messages gelten, ist auch das Shared-Memory zwischen Rechenprozessen zu vermeiden. Die Verwaltung mit Anlegen der Ressource, Identifikation der Ressource, Zugriffsberechtigungen verwalten und schließlich der Freigabe ist im Detail ausgesprochen komplex. Außerdem ist unter normalen Umständen nur eine Kommunikation lokal (innerhalb eines Rechnersystems) möglich. Ein Shared-Memory auf Basis von globalen Variablen zwischen Threads demgegenüber ist jedoch eine sehr einfache Möglichkeit, Daten zwischen zwei Threads auszutauschen. Natürlich stellt jede globale Variable einen potenziellen kritischen Abschnitt dar und ist gegebenenfalls zu schützen! Sockets sd = socket(pf_inet, SOCK_STREAM, 0 ); bind( sd,... ); listen( sd, Anzahl möglicher paralleler Verbindungen ); while( 1 ) Newsd = accept( sd,... ); Zugriff auf die Verbindung über read und write read( Newsd, buffer, sizeof(buffer) ); Abbildung 4-5. Basisstruktur einer Socket-Serverapplikation Die wichtigste Schnittstelle für Inter-Prozess-Kommunikation stellt die Socket-Schnittstelle dar. Mittels Sockets können Daten zwischen Prozessen ausgetauscht werden, die auf unterschiedlichen Rechnern lokalisiert sind (verteiltes System). Die Socketschnittstelle bietet Zugriff zur tcp/ip- und zur udp-kommunikation. Ist dabei einmal eine Verbindung zwischen zwei Prozessen hergestellt worden, können die Daten mit den bekannten Systemcalls (read(), write()...) ausgetauscht werden. Auf tcp/ip-ebene handelt es sich um eine Server/Client-Kommunikation. Ein Server alloziert per socket- Funktion einen beliebigen Socket (Ressource) im System, wobei der Socket über eine Nummer identifiziert wird. Damit ein Client auf einen Socket zugreifen kann, muss er die Socketnummer (Socketadresse) kennen. Für Standarddienste, wie beispielsweise http, sind diese Nummern reserviert, man spricht von sogenannten well known sockets. Hat der Server einen Socket alloziert, muss er ihm eine bekannte Socketnummer zuweisen (z.b. 80 bei http). Dies geschieht mit der Funktion bind() (siehe Abbildung 84

90 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Basisstruktur einer Socket-Serverapplikation). Über einen Socket kann der Server gleichzeitig mehrere Clients bedienen. Über die Funktion listen() parametriert er dabei, wieviele derartige Clients er maximal bedienen möchte (Beispiel Socket-Serverprogramm). Ein einzelner Port auf einem Rechner kann aus dem Grund mehrere Verbindungen (Clients) bedienen (und unterscheiden), weil eine Verbindung durch Ziel-IP-Adresse, Ziel-Port, Remote-IP-Adresse und Remote-Port charakterisiert ist. Ist der Socket geöffnet und ist ihm eine bekannte Socketadresse zugewiesen worden, kann der Server auf Verbindungswünsche warten. Dieses Warten wird über den accept() Aufruf realisiert. Accept liefert, wenn auf dem bekannten Socket ein Verbindungswunsch kommt, einen neuen Socket zurück. Der ursprüngliche, gebundene Socket (bind socket) ist damit wieder frei, um auf weitere Verbindungswünsche zu warten. Klassische Serverprogramme, wie beispielsweise der http-server, erzeugen für jeden Verbindungswunsch (also nach dem accept()) mittels fork() einen eigenen Prozess. Der zurückgegebene Socket (im Bild ConnectionSocket) wird vom Server wie ein Filedeskriptor verwendet. Er kann also auf den Filedeskriptor/Socketdeskriptor schreiben und von ihm lesen (bidirektionale Verbindung). Beispiel Socket-Serverprogramm #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> static int bind_socket, connection_socket; static void emergency_close( int Signal ) { close( bind_socket ); close( connection_socket ); exit( -1 ); int main( int argc, char **argv, char **envp ) { unsigned int count; struct sockaddr_in my_port, remote_socket; char buffer[512]; signal( SIGINT, emergency_close ); bind_socket = socket( AF_INET, SOCK_STREAM, 0 ); if (bind_socket <= 0) { perror( "socket" ); return -1; bzero( &my_port, sizeof(my_port) ); my_port.sin_port = htons( ); if (bind(bind_socket,(struct sockaddr *)&my_port,sizeof(my_port))<0) { perror( "bind" ); return -1; listen( bind_socket, 3 ); while (1) { count = sizeof( remote_socket ); connection_socket = accept( bind_socket, 85

91 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung (struct sockaddr *)&remote_socket, &count ); if (connection_socket >= 0) { printf("connection established...\n"); while ((count=read(connection_socket,buffer, sizeof(buffer)))) { if (strncmp(buffer,"end",strlen("end")) == 0) { break; else { write( connection_socket,buffer,count ); close( connection_socket ); printf( "connection released...\n"); close( bind_socket ); return 0; sd=socket(pf_inet, SOCK_STREAM, 0 ); inet_aton( " ", &destination.sin_addr ); //Zieladresse spez. destination.sin_port = htons( Portnummer ); //Zielport spezifizieren destination.sin_family = PF_INET; connect( sd, &destination, sizeof(destination) ); Zugriff auf die Verbindung über read und write write( sd, Nachricht, strlen(nachricht)+1 );... Abbildung 4-6. Basisstruktur einer Socket-Clientapplikation Der Verbindungsaufbau über Sockets auf der Client-Seite ist nicht so kompliziert (siehe Bild Basisstruktur einer Socket-Clientapplikation). Der Client alloziert sich - wie der Server auch - per socket() Funktion die Socket-Ressource. Da der Client ja keine Verbindung entgegennehmen möchte, muss dieser Socket auch nicht an eine bestimmte Nummer gebunden werden. Der Client tätigt nur noch einen connect() Aufruf, um sich mit dem Remote-Rechner zu verbinden. Er verwendet den vom Socket-Aufruf returnierten Socketdeskriptor/Filedeskriptor direkt (Beispiel Socket-Clientprogramm). Beispiel Socket-Clientprogramm #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define TEXT "Hello, here is a client ;-)" static int sd; static void emergency_close( int signal ) { close( sd ); int main( int argc, char **argv, char **envp ) { 86

92 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung int count; struct sockaddr_in destination; char buffer[512]; signal( SIGINT, emergency_close ); sd = socket( PF_INET, SOCK_STREAM, 0 ); if (sd <= 0) { perror( "socket" ); return -1; bzero( &destination, sizeof(destination) ); //inet_aton( " ", &destination.sin_addr ); //inet_aton( " ", &destination.sin_addr ); inet_aton( " ", &destination.sin_addr ); destination.sin_port = htons( ); destination.sin_family = PF_INET; if (connect(sd,(struct sockaddr *)&destination,sizeof(destination))<0) { perror( "connect" ); return -1; write( sd, TEXT, strlen(text)+1 ); if ((count=read( sd, buffer, sizeof(buffer))) <=0) { perror( "read" ); close( sd ); return -1; printf( "%d: %s\n", count, buffer ); write( sd, "end", 4 ); close( sd ); return 0; Die Kommunikation über Sockets ist - wie in den Abbildungen ersichtlich - relativ unproblematisch. Reale Serverprogramme werden aber aufgrund notwendiger Flexibilität deutlich komplexer. So programmiert man im Regelfall weder die IP-Adresse noch die Portadresse fest in die Applikation ein. Stattdessen werden symbolische Namen spezifiziert, die in entsprechenden Konfigurationsdateien abgelegt sind (z.b. /etc/services) oder über andere Serverdienste (z.b. DNS für die Auflösung von Hostnamen zu IP-Adressen) geholt werden. Für diese Aktionen existieren eine ganze Reihe weiterer Bibliotheksfunktionen. Die Schnittstelle unterstützt nicht nur die Kommunikation über tcp/ip, sondern auch über udp. Bei udp handelt es sich um einen verbindungslosen Dienst. Der Server muss also kein accept() aufrufen, sondern kann direkt vom Socket (Bind-Socket) lesen. Um hier Informationen über den Absender zu erhalten, gibt es eigene Systemaufrufe, bei denen IP- und Portadresse des Absenders übernommen werden. Auch die über IP angebotenen Multicast- und Broadcast-Dienste werden über die Socket-Schnittstelle bedient und sind letztlich Attribute (Parameter) des Sockets. Die im IP-Protokoll festgelegten Datenstrukturen gehen von einem big endian -Ablageformat der Variablen (z.b. Integer) aus. Das bedeutet, dass eine Applikation, die auf einem Rechner abläuft, der ein little endian -Datenablageformat verwendet, die Inhalte der Datenstrukturen erst konvertieren muss. Dieser Vorgang wird durch die Funktioen ntohx (net to host) und htonx (host to net) unterstützt (X steht hier für short oder long). Auf einem Rechner mit big endian -Ablageformat sind die Funktionen (Makros) leer, auf einem Rechner mit little endian -Ablageformat wird dagegen eine Konvertierung durchgeführt. Schreibt man also eine verteilte Applikation, die unabhängig vom Datenablageformat ist, müssen alle Datenstrukturen in ein einheitliches Format gewandelt werden. Die Daten müssen zum Verschicken konvertiert und beim Empfang wieder rückkonvertiert werden. Folgende Technologien unterstützen den Programmierer bei dieser Arbeit: Remote Procedure Call (RPC) Remote Message Invocation (RMI, für Java) 87

93 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung OLE (Microsoft) Corba (Unix) RPC, RMI, OLE und Corba kümmern sich dabei nicht nur um die Konvertierung, sondern auch um den eigentlichen Datentransport Condition-Variable (Event) Während über ein Semaphor der Zugriff auf ein gemeinsam benutztes Betriebsmittel synchronisiert wurde (Synchronisation konkurrierender Tasks), werden zwei oder mehrere kooperierende Tasks über so genannte Condition Variablen oder Events bezüglich ihres Programmablaufs synchronisiert. Dabei schläft eine Task auf eine Condition Variable (Ereignis), welche durch eine andere Task gesetzt wird. Eine Condition Variable ist ein Synchronisationselement, welches Rechenprozessen erlaubt solange den Prozessor frei zu geben, bis eine bestimmte Bedingung erfüllt ist. Die Basisoperationen auf Condition Variablen sind: schlafen auf die Condition Variable und setzen der Condition Variable. Das Setzen der Condition Variable, wenn kein anderer Job darauf schläft, bleibt wirkungslos; das Ereignis wird nicht zwischengespeichert. Um die sich dadurch ergebende Deadlocksituation zu vermeiden, kombiniert man die Condition Variable mit einem Mutex. Um innerhalb von Echtzeitapplikationen, die auf ein Posix-Interface aufsetzen, Condition Variable nutzen zu können, gibt es einen Satz von Funktionen. Die Condition Variable selbst wird innerhalb der Applikation definiert. Die Initialisierung kann statisch (Compiler) durch Zuweisung mit dem Makro PTHREAD_COND_INITIALIZER erfolgen, oder dynamisch durch Aufruf der Funktion int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);. Wird für attr Null übergeben, werden die Default-Attribute eingesetzt. Um auf die Signalisierung der Condition Variable zu warten, dienen die Funktionen int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) und int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime). Vor Aufruf einer der beiden Funktionen muss das zugehörige Mutex mutex zwingend reserviert (gelockt) sein. Die Funktionen geben atomar (ohne dass sie dabei unterbrochen werden) das Mutex frei und legen den aufrufenden Thread schlafen, bis die Signalisierung erfolgt und gleichzeitig das Mutex wieder gelockt werden kann. Nach dem Aufruf einer der beiden Funktionen ist das Mutex mutex also wieder gesperrt. Zur Signalisierung dienen die Funktionen int pthread_cond_signal(pthread_cond_t *cond) und int pthread_cond_broadcast(pthread_cond_t *cond). Erstere weckt genau einen Job auf, letztere alle auf die Condition Variable cond schlafende Jobs. Um die Condition Variable cond wieder zu deinitialisieren, dient die Funktion int pthread_cond_destroy(pthread_cond_t *cond). Allerdings ist diese Funktion nicht zwingend erforderlich, da die Variable ja ohnehin durch die Applikation selbst definiert wird.. Condition Variablen lassen sich gut zur Lösung so genannter Producer-/Consumer-Probleme einsetzen. Bei derartigen Problemen erzeugt eine Task Producer eine Information und eine Task Consumer verwertet diese. Condition-Variable zur Lösung des Producer-/Consumer-Problems zeigt den Einsatz zweier Condition Variablen zur Lösung eines solchen Problems. 88

94 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Beispiel Condition-Variable zur Lösung des Producer-/Consumer-Problems #include <stdio.h> #include <unistd.h> #include <pthread.h> static pthread_mutex_t mutex; static pthread_cond_t cond_consumed = PTHREAD_COND_INITIALIZER; static pthread_cond_t cond_produced = PTHREAD_COND_INITIALIZER; static void *producer( void *arg ) { int count=0; pthread_mutex_lock( &mutex ); while( 1 ) { printf("producer: PRODUCE %d...\n", count++); pthread_cond_signal( &cond_produced ); pthread_cond_wait( &cond_consumed, &mutex ); return NULL; static void *consumer( void *arg ) { int count=0; sleep( 1 ); pthread_mutex_lock( &mutex ); while( 1 ) { printf("consumer: CONSUME %d...\n", count++); pthread_cond_signal( &cond_consumed ); pthread_cond_wait( &cond_produced, &mutex ); return NULL; int main( int argc, char **argv, char **envp ) { pthread_t p1, p2; if (pthread_mutex_init( &mutex, NULL )) { perror("pthread_mutex_init"); return -1; pthread_create( &p2, NULL, consumer, NULL ); pthread_create( &p1, NULL, producer, NULL ); pthread_join( p1, NULL ); pthread_join( p2, NULL ); pthread_mutex_destroy( &mutex ); return 0; 4.6. Signals Bei Signals handelt es sich um Software-Interrupts auf Applikationsebene, das heißt: Ein Signal führt zu einer Unterbrechung des Programmablaufs innerhalb der Applikation. Daraufhin wird das Programm entweder abgebrochen oder reagiert mit einem vom Programm zur Verfügung gestellten Signal-Handler (ähnlich einer Interrupt-Service-Routine). 89

95 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Realisiert werden Signals als Systemcalls des Betriebssystems. Signals können zum einen durch eine Applikation ausgelöst werden, zum anderen aber auch durch Ereignisse innerhalb des Betriebssystem selbst. So führt zum Beispiel der Zugriff auf einen nicht vorhanden Speicherbereich (z.b. der Zugriff auf Adresse Null) innerhalb des Betriebssystems dazu, dem Prozess ein Segmentation-Fault-Signal zu schicken, welches der Prozess abfangen könnte. In der zugehörigen Segmentation-Fault-ISR würden alle notwendigen Daten noch gespeichert. Erst danach würde die Applikation beendet werden. Die Unterschiede zwischen einer Condition Variable (Event) und Signals sind in Tabelle Unterschiede zwischen einem Signal und einer Condition Variablen dargestellt. Tabelle 4-1. Unterschiede zwischen einem Signal und einer Condition Variablen Signals Die Signalisierung kommt asynchron zum Programmablauf und wird asynchron verarbeitet. Charakter einer Interrupt-Service-Routine (Software-Interrupt). Condition Variable Die Signalisierung kommt synchron zum Programmablauf und wird synchron verarbeitet. Rendezvous-Charakter Warnung Ein Signal führt - wenn nicht anders konfiguriert - zum sofortigen Abbruch eines gerade aktiven Systemcalls. Werden im Rahmen einer Applikation Signals verwendet bzw. abgefangen, muss jeder Systemcall daraufhin überprüft werden, ob selbiger durch ein Signal unterbrochen wurde und, falls dieses zutrifft, muss der Systemcall neu aufgesetzt werden! Unixartige Betriebssysteme unterstützen Signals über verschiedene Interfaces. Die größte Kontrolle gibt dabei die Funktion int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact). Mit dieser Funktion kann über den Parameter oldact die aktuelle Konfiguration ausgelesen und alternativ oder gleichzeitig mit act eine neue Konfiguration für das Signal signum gesetzt werden. Die Konfiguration selbst findet sich in der Datenstruktur struct sigaction. Der Programmierer hat die Möglichkeit, die Adresse einer Signalhandler-Funktion (Typ sa_sighandler anzugeben, oder alternativ die Adresse eines Signalhandlers (sa_sigaction), der drei Parameter übergeben bekommt und dem damit vielfältige Information auch über den Prozess zur Verfügung stehen, der das Signal geschickt hat. In das Bitfeld sa_mask können die Signalnummern aktiviert werden, die während der Abarbeitung des Signalhandlers geblockt werden sollen. Über das Datenstrukturelement sa_flags wird unter anderem ausgewählt, ob der einfache oder der alternative Signalhandler verwendet werden soll. Beispiel Programmierbeispiel Signal #include <stdio.h> #include <unistd.h> #include <signal.h> #include <string.h> #define message "SIGINT caught\n" void signal_handler(int value) { write( 1, message, strlen(message) ); // printf is not signalsafe int main(int argc, char **argv, char **envp ) { struct sigaction new_action; int time_to_sleep; 90

96 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung new_action.sa_handler = signal_handler; sigemptyset( &new_action.sa_mask ); new_action.sa_flags = 0; sigaction( SIGINT, &new_action, NULL ); printf("pid: %d\n", getpid() ); time_to_sleep = 20; while( time_to_sleep ) time_to_sleep = sleep( time_to_sleep ); return 0; Applikationen schicken Signals durch Aufruf der Funktion int kill(pid_t pid, int sig):. pid spezifiziert dabei den Job, dem das Signal num zugestellt werden soll Peripheriezugriff Der applikationsseitige Zugriff auf Peripherie erfolt in mehreren Schritten. Im ersten Schritt teilt die Applikation dem Betriebssystem mit, auf welche Peripherie sie in welcher Art (zum Beispiel lesend oder schreibend) zugreifen möchte. Das Betriebssystem prüft, ob der Zugriff möglich und erlaubt ist. Ist dies der Fall, bekommt die Applikation die Zugriffsberechtigung in Form eines Handles beziehungsweise Deskriptors mitgeteilt. Im zweiten Schritt greift die Applikation mit Hilfe des Deskriptors auf die Peripherie so oft und so lange wie notwendig zu. Erst wenn keine Zugriffe mehr notwendig sind, wird das Handle beziehungsweise der Deskriptor wieder freigegeben (Schritt 3). Für den ersten Schritt steht in unxibasierten Systemen die von normalen Dateizugriffen her bekannte Funktion int open(const char *pathname, int flags) zur Verfügung. Da hier das Konzept alles ist eine Datei praktiziert wird, wird die Peripherie über einen Dateinamen (pathname), den Gerätedateinamen identifiziert. Die Art des Zugriffs (lesend, schreibend, nicht blockierend) wird über die in der Headerdatei <fcntl.h> definierten flags spezifiziert: O_RDONLY: Lesender Zugriff O_WRONLY: Schreibender Zugriff O_RDWR: Lesender und Schreibender Zugriff O_NONBLOCK: Nichtblockierender Zugriff Ein negativer Rückgabewert der Funktion open bedeutet, dass der Zugriff nicht möglich ist. Anhand der (thread-) globalen Variablen errno kann die Applikation die Ursache abfragen. Ein positiver Rückgabewert repräsentiert das Handle beziehungsweise den Deskriptor. ssize_t und size_t Viele Standardfunktionen verwenden den Datentyp size_t (size type, vorzeichenlos) oder ssize_t (signed size type, vorzeichenbehaftet). Typischerweise repräsentiert er den orginären Typ unsigned long beziehungsweise long. Applikationen greifen auf die Peripherie dann über die Funktionen ssize_t read(int fd, void *buf, size_t count) und ssize_t write(int fd, const void *buf, size_t count) zu. Der Parameter fd ist der Filedeskriptor (Handle), der von open zurückgegeben wird. Die Adresse buf enthält den Speicherbereich, in den read die Daten ablegt und count gibt an, welche Größe dieser Speicherbereich hat. read kopiert von der Peripherie mindestens ein Byte und maximal count Bytes an die Speicheradresse buf und gibt - solange kein Fehler aufgetreten ist - die Anzahl der kopierten Bytes zurück. Gibt es zum Zeitpunkt 91

97 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung des Aufrufes der Funktion read keine Daten, die gelesen werden können, legt die Funktion im so genannten blockierenden Mode den aufrufenden Thread schlafen und weckt ihn wieder auf, wenn mindestens ein Byte in den Speicherbereich buf abgelegt wurde. Im nicht blockierenden Mode (O_NONBLOCK) quittiert die Funktion den Umstand, dass keine Daten zur Verfügung stehen, mit einem negativen Rückgabewert. Die (thread-) globale Variable errno hat dann den in der Headerdatei <errno.h> definierten Wert EAGAIN. Auch im Fehlerfall, wenn beispielsweise der Aufruf durch ein Signal unterbrochen wurde, gibt read einen negativen Wert zurück und errno enthält den zugehörigen Fehlercode. Die Funktion write arbeitet identisch. Die Funktion schreibt mindestens ein Byte maximal aber count Bytes auf das über den Filedeskriptor fd spezifizierte Gerät. Falls nicht geschrieben werden kann, weil beispielsweise das Sendefifo einer seriellen Schnittstelle bereits voll ist, wird der zugreifende Thread in den Zustand schlafend versetzt, bis der Zugriff möglich ist. Beim nicht blockierenden Zugriff gibt write in diesem Fall - schreiben ist zur Zeit nicht möglich - einen negativen Wert zurück und errno enthält wieder den Wert EAGAIN. Direct-IO versus Buffered-IO Wenn Ein- und Ausgabebefehle wie die Systemcalls read() und write() direkt, ohne Verzögerung umgesetzt werden, spricht man von Direct-IO. Funktionen wie beispielsweise fprintf(), fread(), fwrite() oder fscanf() gehören zur so genannten Buffered-IO. Bei dieser puffert das Betriebssystem (genauer die Bibliothek) die Daten aus Gründen der Effizienz zwischen. Erst wenn es sinnvoll erscheint, werden die zwischengespeicherten Daten per Systemcall read() oder write() transferiert. Dies ist beispielsweise der Fall, wenn ein \n ausgegeben wird oder wenn 512 Byte Daten gepuffert sind. Da nur die Direct-IO Funktionen die volle Kontrolle über die Ein- und Ausgabe geben, werden in Echtzeitapplikationen nur diese für den Datentransfer mit der Peripherie eingesetzt. Beispiel Programmbeispiel Zugriff auf Peripherie #include <stdio.h> #include <fcntl.h> #include <string.h> #include <errno.h> #include <unistd.h> static char *hello = "Hello World"; int main( int argc, char **argv, char **envp ) { int dd; /* device descriptor */ ssize_t bytes_written, bytes_to_write; char *ptr; dd = open( "/dev/ttys0", O_RDWR ); /* blocking mode */ if (dd<0) { perror( "/dev/ttys0" ); return -1; bytes_to_write = strlen( hello ); ptr = hello; while (bytes_to_write) { bytes_written = write( dd, hello, bytes_to_write ); if (bytes_written<0) { perror( "write" ); close( dd ); return -1; 92

98 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung bytes_to_write -= bytes_written; ptr += bytes_written; close( dd ); return 0; Der Zugriffsmode (blockierend oder nicht blockierend) kann im übrigen per int fcntl(int fd, int cmd,... /* arg */) auch nach dem Öffnen noch umgeschaltet werden. Dazu werden zunächst mit dem Kommando (cmd) F_GETFL die aktuellen Flags gelesen und dann wird entweder per Oder-Verknüpfung der nicht blockierende Modus gesetzt beziehungsweise per XOR-Verknüpfung der blockierende Modus wieder aktiviert. int fd, fd_flags, ret;... fd_flags = fcntl( fd, F_GETFL ); if (fd_flags<0) { return -1; /* Fehler */ fd_flags = O_NONBLOCK; /* nicht blockierenden Modus einschalten */ if (fcntl( fd, F_SETFL, (long)fd_flags )<0 ) { return -1; /* Fehler */... fd_flags = fcntl( fd, F_GETFL ); if (fd_flags<0) { return -1; /* Fehler */ fd_flags~= O_NONBLOCK; /* nicht blockierenden Modus einschalten */ if (fcntl( fd, F_SETFL, (long)fd_flags )<0 ) { return -1; /* Fehler */... Werden regelmäßig (im nicht blockierenden Modus) Peripheriegeräte daraufhin abgeprüft, ob Daten zum Lesen vorliegen oder ob Daten geschrieben werden können, spricht man von Polling. Polling ist insofern ungünstig, da ein Rechner auch dann aktiv wird, wenn eigentlich nichts zu tun ist. Besser ist der ereignisgesteuerte Zugriff (blockierender Modus), bei dem die Applikation nur dann aktiv wird, wenn Daten gelesen oder geschrieben werden können. Allerdings hat der blockierende Modus in unixartigen Systemen den Nachteil, dass per read oder write immer nur eine Quelle abgefragt werden kann. Oftmals sind aber mehrere Datenquellen oder Datensenken abzufragen. Dieses Problem kann auf zwei Arten gelöst werden. Die klassische Methode basiert auf der Funktion int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout), die auch eine portable Möglichkeit ist, einen Thread mit einer Genauigkeit von Mikrosekunden schlafen zu legen. Select werden Sets von Deskriptoren übergeben, die auf Aktivitäten überwacht werden. Sollten an einem der in readfds eingetragenen Deskriptoren Daten gelesen werden können, ohne dass ein (blockierend) zugreifender Thread schlafen gelegt wird, returniert die Funktion. Ebenso ist das mit writefds: select returniert, falls an einem der überwachten Deskriptoren Daten ohne zu blockieren ausgegeben werden können. Das dritte Set, exceptfds, wird nur im Kontext von Netzwerkverbindungen verwendet. Sollten an einem der im Set spezifizierten Socketdeskriptoren so genannte Out of Band Daten vorliegen, kehrt die Funktion zurück. Der letzte Parameter der Funktion (timeout) dient der Zeitüberwachung. Sollten an keinem der in den Sets definierten Deskriptoren Daten ohne zu blockieren transferiert werden können, schläft select maximal für die in timeout angegebene Zeitspanne. Ist timeout Null, schläft die Funktion ohne Zeitüberwachung. Aus Gründen der Effizienz muss im Parameter nfds beim Aufruf noch der höchste in den Sets spezifizierte Deskriptor plus eins übergeben werden. Die übergebenen Sets von Deskriptoren (Datentyp fd_set) sind implementierungstechnisch betrachtet meistens als Bitfelder realisiert. Jedes Bit steht für einen Deskriptor. Ist das Bit gesetzt, soll der entsprechende Deskriptor überwacht werden. Um ein Set von Deskriptoren zu initialisieren, steht das 93

99 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Makro void FD_ZERO(fd_set *set) zur Verfügung. Es stellt sicher, dass alle Bits zu Null gesetzt sind. Mit der Funktion void FD_SET(int fd, fd_set *set) wird dem Set ein Deskriptor hinzugefügt (Bit setzen). Ein Deskriptor wird über void FD_CLR(int fd, fd_set *set) wieder aus dem Set entfernt (ein einzelnes Bit wird gelöscht). Und um nach dem Aufruf von select festzustellen, über welchen Deskriptor Daten gelesen oder geschrieben werden können, verwenden Sie das Makro int FD_ISSET(int fd, fd_set *set) (siehe Programmbeispiels select). Beispiel Programmbeispiels select #include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #define max(x,y) ((x) > (y)? (x) : (y)) int main( int argc, char **argv, char **envp ) { fd_set rds; struct timeval tv; int retval, nds=0; int dd = 0; /* watch stdin to see when it has input */ FD_ZERO(&rds); /* initialize the deskriptor set */ /* for every deskriptor you want to add... */ FD_SET(dd, &rds); /* add the deskriptor you want to watch to the set */ nds = max(nds, dd); tv.tv_sec = 5; /* wait up to five seconds. */ tv.tv_usec = 0; retval = select(nds, &rds, NULL, NULL, &tv); if (retval) { if ( FD_ISSET(dd, &rds) ) printf("data is available now.\n"); else { printf("no data within five seconds.\n"); return 0; Die Funktion select ist problematisch. Per Definitionem soll ein dem select folgender read oder write Aufruf nicht blockieren. Dieses Verhalten müsste letztlich ein Treiber sicherstellen, was aber nur in Ausnahmefällen gewährleistet ist. Falls also mehrere Threads auf die gleiche Peripherie zugreifen, könnte select dem einen Thread signalisieren, dass das Lesen der Daten möglich ist, der andere Thread aber in dem Moment diese Daten bereits abholen, so dass der erste Thread, wenn er das read aufruft doch schlafen gelegt wird. Die modernere Variante, mehrere Ein- oder Ausgabekanäle zu überwachen, basiert auf der Threadprogrammierung. Für jeden Kanal (Filedeskriptor, Handle) wird ein eigener Thread aufgezogen, der dann blockierend per read oder write zugreift. Neben der einfacheren und übersichtlicheren Programmierung hat das auch den Vorteil, dass damit direkt das nebenläufige Programmieren (Stichwort Multicore-Architektur) unterstützt wird. Sind keine Zugriffe auf die Peripherie über den Deskriptor mehr notwendig, kann dieser per int close(int fd) wieder freigegeben werden. In vielen Anwendungen fehlt jedoch zu einem open das korrespondierende close. Das ist insofern nicht dramatisch, da das Betriebssystem beim Ende einer Applikation alle noch offenen Deskriptoren von sich aus wieder freigibt. 94

100 Kapitel 4. Aspekte der nebenläufigen Echtzeit-Programmierung Manche Peripheriegeräte, wie beispielsweise eine Grafikkarte, transferieren nicht nur einzelne Bits oder Bytes, sondern umfangreiche Speicherbereiche. Diese Daten über einzelne read und write Aufrufe zu verschieben, wäre sehr ineffizient: Mit jedem Zugriff ist ein Kontextwechsel notwendig und außerdem wird mindestens in der Applikation ein Speicherbereich benötigt, in den beziehungsweise von dem die Daten zwischen Applikation und Hardware transferiert werden. Daher bieten Betriebssysteme und die zu den Geräten gehörenden Treiber oft die Möglichkeit, Speicherbereiche der Hardware (oder aber auch des Kernels) in den Adressraum einer Applikation einzublenden. Die Applikation greift dann direkt auf diese Speicherbereiche zu. Dazu öffnet die Applikation als erstes die Gerätedatei (Funktion open), die den Zugriff auf die gewünschte Peripherie ermöglicht. Der zurückgelieferte Gerätedeskriptor dd (Device Deskriptor) wird dann der Funktion void *mmap(void *addr, size_t length, int prot, int flags, int dd, off_t offset) übergeben. Ist der Parameter addr mit einer Adresse vorbelegt, versucht das Betriebssystem den Speicher an diese Adresse einzublenden. Typischerweise ist addr aber auf Null gesetzt und das System selbst sucht eine günstige, freie Adresse aus. Der Parameter length gibt die Länge des Speicherbereiches an, der aus Sicht der Peripherie ab dem Offset offset eingeblendet werden soll, prot spezifiziert den gewünschten Speicherschutz und ist entweder PROT_NONE, PROT_EXEC, PROT_READ oder PROT_WRITE (bitweise verknüpft). Das Argument flags legt fest, ob Änderungen an dem eingeblendeten Speicherbereich für andere Threads sichtbar sind, die den Bereich ebenfalls eingeblendet haben. Hierzu stehen die vordefinierten Werte MAP_SHARED und MAP_PRIVATE zur Verfügung. Beispiel Speicherbereiche in den Adressraum einblenden #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> int main( int argc, char **argv, char **envp ) { int dd; void *pageaddr; int *ptr; dd = open( "/dev/mmap_dev", O_RDWR ); if (dd<0) { perror( "/dev/mmap_dev" ); return -1; pageaddr = mmap( NULL, 4096, PROT_READ PROT_WRITE, MAP_SHARED, dd, 0 ); printf("pageaddr mmap: %p\n", pageaddr ); if (pageaddr) { ptr = (int *)pageaddr; printf("*pageaddr: %d\n", *ptr ); *ptr = 55; /* access to the mapped area */ munmap( pageaddr, 4096 ); return 0; Um einen eingeblendeten Speicherbereich wieder freizugeben, ruft die Applikation int munmap(void *addr, size_t length) auf. 95

101 Kapitel 5. Echtzeitarchitekturen Um ein Standardbetriebssystem für eine Aufgabe einzusetzen, ei der harte Zeitanforderungen eingehalten werden müssen, gibt es drei Basistechnologien: 1. Teile der Applikation in den Kernel verlagern. 2. Mehrkernansatz: Einsatz einer Mehrkernmaschine. 3. Mehrkernelansatz: Neben dem Standardbetriebssystem wird ein Echtzeitbetriebssystem eingesetzt Applikationen im Kernel Wenn die zeitlichen Anforderung auf Applikationsebene nicht eingehalten werden können, kann der Systemarchitekt überlegen, die zeitkritischen Teile der Applikation in den Kernel zu verlagern. Das hat die folgende Vorteile: Im Kernel gibt es generell kürzere Latenzzeiten. Der Overhead ist geringer, da weniger Kontextwechsel stattfinden müssen. Um kürzere Latenzzeiten zu erhalten, verlagert der Architekt die Teile, die Anforderungen von wenigen Mikrosekunden haben, direkt in die ISR. Hierbei ist natürlich zu beachten, dass die Abarbeitung der ISR dadruch nicht zu lang wird. Das würde sich generell negativ auf das Zeitverhalten des Gesamtsystems auswirken, da ja für alle anderen Prozesse eine zusätzliche Latenzzeit auftritt. Ansonsten sollten die Applikationsteile in Treiberfunktionen ausgelagert werden, die entweder auf der Kernel-Ebene oder auf der Soft-IRQ-Ebene (siehe Unterbrechungsmodell) abgearbeitet werden. Der Vorteil dieses Ansatzes: Der Anwender hat trotz der Verlagerung weiterhin sein Standardbetriebssystem mit all seinen Vorteilen. Nachteilig ist zweifelsohne, dass sich die Lösung der Echtzeitaufgabe auf unterschiedliche Komponenten verteilt und auf das Betriebssystem angepasst werden muss. Es besteht damit auch keine saubere Trennung zwischen Applikation und Betriebssystemkern mehr, die Gefahr von Systemabstürzen durch Programmierfehler, die sich im Kernel ungeschützt bemerkbar machen können, besteht. Außerdem gibt es bei der Programmierung von Funktionen im Kernel einige Einschränkungen. So können im Kernel beispielsweise keine Floating-Point-Operationen durchgeführt werden. Trotz der manigfaltigen Nachteile ist diese Lösung zumindest dem Mehrkernel-Ansatz vorzuziehen Mehrkernmaschine Eine weitere Möglichkeit, Echtzeitanforderungen einzuhalten, bieten Mehrprozessor- beziehungsweise Mehrkernmaschinen. Auf diesen Rechnern wird eine CPU allein für die Echtzeitaufgaben reserviert. Bereits ein moderner Linux-Kernel (zum Beispiel Kernel ) bietet diese Möglichkeit an. Kommerziell gibt es solche Systeme, die Realzeitprozesse auf einer abgeriegelten (shielded) CPU abarbeiten lassen (zum Beispiel Redhawk-Linux der Firma Concurrent). Aufgabe des Systemarchitekten ist es zunächst, die abzuarbeitenden Rechenporzesse in Realzeit- und Nichtrealzeitprozesse zu klassifizieren. Dabei müssen die zeitlichen Parameter der Prozesse und die sich 96

102 Kapitel 5. Echtzeitarchitekturen ergebende Gesamtauslastung bestimmt werden. Die Gesamtauslastung muss unterhalb von»anzahl-cpu * 100 Prozent«liegen. Typischerweise wird die Boot-CPU (die CPU mit der Bezeichnung»0«als die CPU eingeplant, die nicht Realzeitprozesse abarbeitet. Das hat oft hardwaretechnische Gründe, denn nicht bei jeder Hardware lassen sich Interrupts beliebig auf unterschiedliche Prozessorkerne verteilen. n Bit cpumask_t cpu_possible_map cpu_present_map cpu_online_map cpus_allowed Globale CPU Sets: 1 = CPU könnte es geben 1 = CPU ist eingebaut 1 = CPU ist verfügbar pro Thread 1x vorhanden: 1 = CPU darf genutzt werden Abbildung 5-1. Datenstruktur zur Verwaltung von mehreren Prozessorkernen. Jedem Rechenpozress kann übrigens typischerweise eine Affinität mitgegeben werden. HIerbei handelt es sich um die Kennung, auf welchen Prozessoren ein Prozess abgearbeitet werden dasrf (siehe Abbildung Datenstruktur zur Verwaltung von mehreren Prozessorkernen.). Die Affinität lässt sich über die Funktion sched_setaffinity() programmtechnisch realisieren. Beim weiteren Vorgehen gibt es zwei Möglichkeiten: Alle Nicht-Realzeitprozesse werden auf CPU 0 verlagert. Die Gesamtauslastung ρ ges für diese CPU muss dabei natürlich unterhalb von 100 Prozent liegen. Die Realzeitprozesse werden auf den übrigen Prozessoren abgearbeitet, wobei der Scheduler selbst die Verteilung auf die einzelnen Prozessoren vornimmt. Alternativ dazu kann der Systemarchitekt die Rechenprozesse direkt den einzelnen Rechnerkernen zuordnen. In jedem Fall muss nach der Verteilung auf die Prozessoren für jeden Prozessor getrennt ein Echtzeitnachweis geführt werden. In Linux gibt es außerdem für die Zuordnung von Rechenprozesse auf Prozessoren das»cgroup«-framework (Container- oder Control-Group) [KuQu08]. Control Croups stellen ein neues Framework zur hierarchischen Organisation von Rechenprozessen (die spezifische Eigenschaften besitzen) und deren zukünftigen Kindprozessen dar. Mit diesem Mittel gruppiert der Admin über ein virtuelles Filesystem (type cpuset) sowohl Prozessoren als auch Speicherressourcen (»memory nodes«) und verteilt auf die eingerichteten Gruppen die Rechenprozesse. In Abbildung Hierarchische Prozessor-Organisation. ist zu sehen, dass diese Verteilung hierarchisch aufgebaut ist. Die oberste Gruppe enthält sämtliche Prozessoren und Memory-Nodes. In einer Ebene darunter lassen sich die Ressourcen auf mehrere Untergruppen vertreilen. Per Flag»cpu-exclusive«und»memory-exclusive«lässt sich noch festlegen, ob eine Ressource von mehreren Gruppen aus verwendet werden darf (überlappend), oder eben exklusiv der einen Gruppe zur Verfügung steht. Die Verlagerung eines Rechenprozesses in eine Gruppe führt dazu, dass das Attribut cpus_allowed des verschobenen Prozesses mit den gruppenspezifischen Einstellungen (Affinity-Maske der Gruppe) überschrieben wird. Auf diese Weise lassen sich recht einfach (und dauerhaft) die zeitkritischen Prozesse von den weniger zeitkritischen trennen. Im einfachsten Fall legen Sie zwei Gruppen (»rt«und»non-rt«) an, weisen diesen die Speicherressourcen und die Prozessorkerne zu und verschieben sämtliche Prozesse in die 97

103 Kapitel 5. Echtzeitarchitekturen»non-rt«-Gruppe. Anschließend picken Sie sich die zeitkritischen Rechenprozesse heraus und migrieren diese in die»rt«-gruppe: mkdir /dev/cpuset mount -t cpuset -ocpuset cpuset /dev/cpuset mkdir /dev/cpuset/rt mkdir /dev/cpuset/non-rt echo 0 > /dev/cpuset/rt/mems root@lab01:~# echo 0 > /dev/cpuset/non-rt/mems root@lab01:~# echo 2 >/dev/cpuset/rt/cpus root@lab01:~# echo 1 >/dev/cpuset/rt/cpu_exclusive root@lab01:~# echo 0-1 >/dev/cpuset/non-rt/cpus root@lab01:~# echo 1 >/dev/cpuset/rt/cpu_exclusive root@lab01:~# for pid in $(cat /dev/cpuset/tasks); \ > do /bin/echo $pid > /dev/cpuset/non-rt/tasks;\ > done root@lab01:~# cd /dev/cpuset/rt root@lab01:~# /bin/echo $$ >tasks root@lab01:~# starte_rt_task... cgroup /dev/cpuset Jobs, die auf allen Kernen der cgroup abgearbeitet werden. Lastverteilung zwischen den Kernen non rt rt h rt normales Scheduling Rechenprozesse Ohne RT Anforderungen Mit RT Anforderungen Abbildung 5-2. Hierarchische Prozessor-Organisation. Wenn Sie die Verteilung einmal vorgenommen haben, sollten Sie davon absehen, nachträglich die Affinity- Maske einer Gruppe zu ändern. Das hat nämlich keine Auswirkung mehr auf den innerhalb der Gruppe befindlichen Prozess. Um eine veränderte Parametrierung wirksam werden zu lassen, müsste der Rechenprozess der entsprechenden Gruppe neu zugeordnet werden. Das Aus- oder Einbrechen aus dem Container- Gefängnis (cgroup) über die Funktion sched_setaffinity() ist aber dennoch nicht möglich. Nur wenn ein Prozessorkern zur Gruppe gehört, kann der Prozess dorthin migrieren. Durch das Verschieben der Prozesse in die non-rt-gruppe wird der für Realzeitaufgaben vorgesehene Prozessor freigeschaufelt. Das lässt sich einfacher per CPU-Hotplugging bewerkstelligen. Eigentlich heißt Hotplugging, dass während des Betriebs Hardware zu oder abgesteckt wird; in diesem Fall also einer von mehreren Prozessoren. Das sollten Sie falls Sie zu den glücklichen Besitzern einer Mehrprozessormaschine gehören jetzt nicht wörtlich nehmen und während des Betriebs den Rechner aufschrauben. Hardware-CPU-Hotplugging unterstützen nämlich nur wenige Maschinen. Unabhängig davon können Sie aber softwaretechnisch (logisch) einzelne Prozessoren (und damit auch Prozessorkerne) deaktivieren und wieder aktivieren. Beim Deaktivieren werden dann die auf dem Prozessor ablaufenden Rechenprozesse auf die anderen Prozessoren migriert. Das Feature wird auch sinnvoll eingesetzt, wenn der Verdacht besteht, dass ein einzelner CPU-Kern defekt ist. Die CPU-0 spielt als Boot-Prozessor eine 98

104 Kapitel 5. Echtzeitarchitekturen Sonderrolle und lässt sich auf vielen Systemen nicht deaktivieren. Das hängt damit zusammen, dass bei diesen Architekturen einige Interrupts fest an den ersten Prozessor-Kern gekoppelt sind. Abbildung Deaktivieren von Rechnerkernen. zeigt, wie Sie durch Zugriffe auf das Sys-Filesystem eine CPU aktivieren und deaktivieren. Sichtbar am Inhalt der Datei /proc/interrupts sind zunächst vier Kerne online. An der Prozesstabelle lassen sich die auf dieser CPU lokalisierten Kernel-Threads entdecken. Wird die CPU jetzt deaktiviert (Kommando echo "0" >online), taucht die CPU in der Datei /proc/interrupts nicht mehr auf. Auch die cpu-spezifischen Kernel-Threads sind verschwunden. Wenn Sie bei einer CPU die Datei online nicht vorfinden bedeutet das übrigens nur, dass sich diese CPU auch nicht deaktivieren lässt. Abbildung 5-3. Deaktivieren von Rechnerkernen. In Abgrenzung zu den CPU-Sets setzt die CPU-Isolation bereits beim Booten ein. Per Boot-Parameter isolcpus= lassen sich einzelne Rechnerkerne (mit Ausnahme von CPU 0) direkt deaktivieren und vom Load-Balancing (Verteilen der Last zwischen den vorhandenen Prozessoren) ausnehmen. Hierbei handelt es sich um eine sehr rudimentäre Methode, die als wesentlichen Vorteil die kürzere Bootzeit mit sich bringt. Ansonsten spricht wohl noch die einfache Handhabung für den Bootparameter. Sollen auf einer Mehrkernmaschine mehrere Prozessoren für die Abarbeitung zeitkritischer Rechenprozesse eingesetzt werden, kann ander als bei den cgroups auf Basis von isolcpus keine Lastverteilung innerhalb der isolierten Kerne aktiviert werden. Außerdem werden auch auf den auf diese Weise isolierten Rechnerkernen ISRs, Tasklets und Timer abgearbeitet; etwas, das sich allenfalls durch Verwendung des Bootparameters max_cpus= verhindern lässt. Verständlich, dass bei den wenigen Alleinstellungsmerkmalen die Entwickler diskutieren, dieses Feature aus dem Kernel zu entfernen. Ähnlich wie Rechenprozesse besitzen auch Interrupts im Kernel ein ihnen zugewiesenes Attribut, welches die Prozessoren in Form der Bitmaske auf die Rechnerkerne festlegt, auf die die zugehörige Interrupt- Service-Routine abgearbeitet werden darf. Dieses lautet im Kernel relativ einfach nur mask. Wenn es um Hardware-Interrupts geht, kann der Kernelprogrammierer zum einen beim Einhängen der Interrupt-Service- Routine festlegen, dass die ISR nicht "balanced", also auf unterschiedlichen Rechnerkernen abgearbeitet wird. Vom Userland aus kann das Attribut mask wieder einmal über das Proc-Filesystem manipuliert werden. Im folgenden Beispiel wird auf einer Vierkern-Maschine (smp_affinity=0x0f) die CPU-0 von der Verarbeitung des Interrupts 1 ausgenommen (smp_affinity=0x0e): 99

Fachbericht zum Thema: Anforderungen an ein Datenbanksystem

Fachbericht zum Thema: Anforderungen an ein Datenbanksystem Fachbericht zum Thema: Anforderungen an ein Datenbanksystem von André Franken 1 Inhaltsverzeichnis 1 Inhaltsverzeichnis 1 2 Einführung 2 2.1 Gründe für den Einsatz von DB-Systemen 2 2.2 Definition: Datenbank

Mehr

Grundlagen verteilter Systeme

Grundlagen verteilter Systeme Universität Augsburg Insitut für Informatik Prof. Dr. Bernhard Bauer Wolf Fischer Christian Saad Wintersemester 08/09 Übungsblatt 3 12.11.08 Grundlagen verteilter Systeme Lösungsvorschlag Aufgabe 1: a)

Mehr

Vorbereitung zur Prüfung Echtzeitbetriebssysteme

Vorbereitung zur Prüfung Echtzeitbetriebssysteme Vorbereitung zur Prüfung Echtzeitbetriebssysteme Zugelassene Hilfsmittel: Taschenrechner Bitte verwenden Sie keinen roten Farbstift! 1. Echtzeitbetriebssysteme - Allgemein (15 Punkte) 1.1. Warum setzen

Mehr

Datensicherung. Beschreibung der Datensicherung

Datensicherung. Beschreibung der Datensicherung Datensicherung Mit dem Datensicherungsprogramm können Sie Ihre persönlichen Daten problemlos Sichern. Es ist möglich eine komplette Datensicherung durchzuführen, aber auch nur die neuen und geänderten

Mehr

50. Mathematik-Olympiade 2. Stufe (Regionalrunde) Klasse 11 13. 501322 Lösung 10 Punkte

50. Mathematik-Olympiade 2. Stufe (Regionalrunde) Klasse 11 13. 501322 Lösung 10 Punkte 50. Mathematik-Olympiade. Stufe (Regionalrunde) Klasse 3 Lösungen c 00 Aufgabenausschuss des Mathematik-Olympiaden e.v. www.mathematik-olympiaden.de. Alle Rechte vorbehalten. 503 Lösung 0 Punkte Es seien

Mehr

Architektur Verteilter Systeme Teil 2: Prozesse und Threads

Architektur Verteilter Systeme Teil 2: Prozesse und Threads Architektur Verteilter Systeme Teil 2: Prozesse und Threads 21.10.15 1 Übersicht Prozess Thread Scheduler Time Sharing 2 Begriff Prozess und Thread I Prozess = Sequentiell ablaufendes Programm Thread =

Mehr

geben. Die Wahrscheinlichkeit von 100% ist hier demnach nur der Gehen wir einmal davon aus, dass die von uns angenommenen

geben. Die Wahrscheinlichkeit von 100% ist hier demnach nur der Gehen wir einmal davon aus, dass die von uns angenommenen geben. Die Wahrscheinlichkeit von 100% ist hier demnach nur der Vollständigkeit halber aufgeführt. Gehen wir einmal davon aus, dass die von uns angenommenen 70% im Beispiel exakt berechnet sind. Was würde

Mehr

Systeme 1. Kapitel 6. Nebenläufigkeit und wechselseitiger Ausschluss

Systeme 1. Kapitel 6. Nebenläufigkeit und wechselseitiger Ausschluss Systeme 1 Kapitel 6 Nebenläufigkeit und wechselseitiger Ausschluss Threads Die Adressräume verschiedener Prozesse sind getrennt und geschützt gegen den Zugriff anderer Prozesse. Threads sind leichtgewichtige

Mehr

Outlook-Daten komplett sichern

Outlook-Daten komplett sichern Outlook-Daten komplett sichern Komplettsicherung beinhaltet alle Daten wie auch Kontakte und Kalender eines Benutzers. Zu diesem Zweck öffnen wir OUTLOOK und wählen Datei -> Optionen und weiter geht es

Mehr

icloud nicht neu, aber doch irgendwie anders

icloud nicht neu, aber doch irgendwie anders Kapitel 6 In diesem Kapitel zeigen wir Ihnen, welche Dienste die icloud beim Abgleich von Dateien und Informationen anbietet. Sie lernen icloud Drive kennen, den Fotostream, den icloud-schlüsselbund und

Mehr

Handbuch ECDL 2003 Basic Modul 5: Datenbank Grundlagen von relationalen Datenbanken

Handbuch ECDL 2003 Basic Modul 5: Datenbank Grundlagen von relationalen Datenbanken Handbuch ECDL 2003 Basic Modul 5: Datenbank Grundlagen von relationalen Datenbanken Dateiname: ecdl5_01_00_documentation_standard.doc Speicherdatum: 14.02.2005 ECDL 2003 Basic Modul 5 Datenbank - Grundlagen

Mehr

3.14 Die Programmieroberfläche Programmierung

3.14 Die Programmieroberfläche Programmierung 121 3.14 Die Programmieroberfläche Programmierung Besonderheiten Die Oberflächen der einzelnen Quellen (3S, KW-Software, Siemens-TIA-Portal, logi.cad 3, PAS4000) sind in sich unterschiedlich. Aber auch

Mehr

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren Lineargleichungssysteme: Additions-/ Subtraktionsverfahren W. Kippels 22. Februar 2014 Inhaltsverzeichnis 1 Einleitung 2 2 Lineargleichungssysteme zweiten Grades 2 3 Lineargleichungssysteme höheren als

Mehr

Bitte verwenden Sie nur dokumentenechtes Schreibmaterial!

Bitte verwenden Sie nur dokumentenechtes Schreibmaterial! VO 182.711 Prüfung Betriebssysteme 8. November 2013 KNr. MNr. Zuname, Vorname Ges.)(100) 1.)(35) 2.)(20) 3.)(45) Zusatzblätter: Bitte verwenden Sie nur dokumentenechtes Schreibmaterial! 1 Synchronisation

Mehr

Grundlagen der Technischen Informatik. Sequenzielle Netzwerke. Institut für Kommunikationsnetze und Rechnersysteme. Paul J. Kühn, Matthias Meyer

Grundlagen der Technischen Informatik. Sequenzielle Netzwerke. Institut für Kommunikationsnetze und Rechnersysteme. Paul J. Kühn, Matthias Meyer Institut für Kommunikationsnetze und Rechnersysteme Grundlagen der Technischen Informatik Paul J. Kühn, Matthias Meyer Übung 2 Sequenzielle Netzwerke Inhaltsübersicht Aufgabe 2.1 Aufgabe 2.2 Prioritäts-Multiplexer

Mehr

Primzahlen und RSA-Verschlüsselung

Primzahlen und RSA-Verschlüsselung Primzahlen und RSA-Verschlüsselung Michael Fütterer und Jonathan Zachhuber 1 Einiges zu Primzahlen Ein paar Definitionen: Wir bezeichnen mit Z die Menge der positiven und negativen ganzen Zahlen, also

Mehr

4D Server v12 64-bit Version BETA VERSION

4D Server v12 64-bit Version BETA VERSION 4D Server v12 64-bit Version BETA VERSION 4D Server v12 unterstützt jetzt das Windows 64-bit Betriebssystem. Hauptvorteil der 64-bit Technologie ist die rundum verbesserte Performance der Anwendungen und

Mehr

Lizenzierung von System Center 2012

Lizenzierung von System Center 2012 Lizenzierung von System Center 2012 Mit den Microsoft System Center-Produkten lassen sich Endgeräte wie Server, Clients und mobile Geräte mit unterschiedlichen Betriebssystemen verwalten. Verwalten im

Mehr

Leitfaden zur ersten Nutzung der R FOM Portable-Version für Windows (Version 1.0)

Leitfaden zur ersten Nutzung der R FOM Portable-Version für Windows (Version 1.0) Leitfaden zur ersten Nutzung der R FOM Portable-Version für Windows (Version 1.0) Peter Koos 03. Dezember 2015 0 Inhaltsverzeichnis 1 Voraussetzung... 3 2 Hintergrundinformationen... 3 2.1 Installationsarten...

Mehr

Task A Zündung. Task B Einspritzung. Task C Erfassung Pedalwert. J. Schäuffele, Th. Zurawka: Automotive Software Engineering, Vieweg, 2003

Task A Zündung. Task B Einspritzung. Task C Erfassung Pedalwert. J. Schäuffele, Th. Zurawka: Automotive Software Engineering, Vieweg, 2003 Task! evt. parallel zu bearbeitende Ausführungseinheit! Beispiel: Task A Zündung Task B Einspritzung Task C Erfassung Pedalwert Zeit t J. Schäuffele, Th. Zurawka:, Vieweg, 2003 Echtzeitbetriebssysteme

Mehr

In 12 Schritten zum mobilen PC mit Paragon Drive Copy 11 und Microsoft Windows Virtual PC

In 12 Schritten zum mobilen PC mit Paragon Drive Copy 11 und Microsoft Windows Virtual PC PARAGON Technologie GmbH, Systemprogrammierung Heinrich-von-Stephan-Str. 5c 79100 Freiburg, Germany Tel. +49 (0) 761 59018201 Fax +49 (0) 761 59018130 Internet www.paragon-software.com Email sales@paragon-software.com

Mehr

Partitionieren in Vista und Windows 7/8

Partitionieren in Vista und Windows 7/8 Partitionieren in Vista und Windows 7/8 Windows Vista und Windows 7 können von Haus aus Festplatten partitionieren. Doch die Funktion ist etwas schwer zu entdecken, denn sie heißt "Volume verkleinern".

Mehr

1 Mathematische Grundlagen

1 Mathematische Grundlagen Mathematische Grundlagen - 1-1 Mathematische Grundlagen Der Begriff der Menge ist einer der grundlegenden Begriffe in der Mathematik. Mengen dienen dazu, Dinge oder Objekte zu einer Einheit zusammenzufassen.

Mehr

EasyWk DAS Schwimmwettkampfprogramm

EasyWk DAS Schwimmwettkampfprogramm EasyWk DAS Schwimmwettkampfprogramm Arbeiten mit OMEGA ARES 21 EasyWk - DAS Schwimmwettkampfprogramm 1 Einleitung Diese Präsentation dient zur Darstellung der Zusammenarbeit zwischen EasyWk und der Zeitmessanlage

Mehr

Anwenderdokumentation AccountPlus GWUPSTAT.EXE

Anwenderdokumentation AccountPlus GWUPSTAT.EXE AccountPlus Inhaltsverzeichnis Inhaltsverzeichnis Anwenderdokumentation AccountPlus GWUPSTAT.EXE (vorläufig) ab Version 6.01 INHALTSVERZEICHNIS...1 1 ALLGEMEINES...2 2 INSTALLATION UND PROGRAMMAUFRUF...2

Mehr

Zeichen bei Zahlen entschlüsseln

Zeichen bei Zahlen entschlüsseln Zeichen bei Zahlen entschlüsseln In diesem Kapitel... Verwendung des Zahlenstrahls Absolut richtige Bestimmung von absoluten Werten Operationen bei Zahlen mit Vorzeichen: Addieren, Subtrahieren, Multiplizieren

Mehr

Er musste so eingerichtet werden, dass das D-Laufwerk auf das E-Laufwerk gespiegelt

Er musste so eingerichtet werden, dass das D-Laufwerk auf das E-Laufwerk gespiegelt Inhaltsverzeichnis Aufgabe... 1 Allgemein... 1 Active Directory... 1 Konfiguration... 2 Benutzer erstellen... 3 Eigenes Verzeichnis erstellen... 3 Benutzerkonto erstellen... 3 Profil einrichten... 5 Berechtigungen

Mehr

Echtzeitscheduling (1)

Echtzeitscheduling (1) Echtzeitscheduling (1) Scheduling in Betriebssystemen Ressourcenausteilung (CPU, Speicher, Kommunikation) Faire Ressourcenvergabe, insbesondere CPU Hohe Interaktivität / kurze Reaktionszeit für interaktive

Mehr

Beschreibung und Bedienungsanleitung. Inhaltsverzeichnis: Abbildungsverzeichnis: Werkzeug für verschlüsselte bpks. Dipl.-Ing.

Beschreibung und Bedienungsanleitung. Inhaltsverzeichnis: Abbildungsverzeichnis: Werkzeug für verschlüsselte bpks. Dipl.-Ing. www.egiz.gv.at E-Mail: post@egiz.gv.at Telefon: ++43 (316) 873 5514 Fax: ++43 (316) 873 5520 Inffeldgasse 16a / 8010 Graz / Austria Beschreibung und Bedienungsanleitung Werkzeug für verschlüsselte bpks

Mehr

In 15 einfachen Schritten zum mobilen PC mit Paragon Drive Copy 10 und Microsoft Windows Virtual PC

In 15 einfachen Schritten zum mobilen PC mit Paragon Drive Copy 10 und Microsoft Windows Virtual PC PARAGON Technologie GmbH, Systemprogrammierung Heinrich-von-Stephan-Str. 5c 79100 Freiburg, Germany Tel. +49 (0) 761 59018201 Fax +49 (0) 761 59018130 Internet www.paragon-software.com Email sales@paragon-software.com

Mehr

1 Informationelle Systeme begriffliche Abgrenzung

1 Informationelle Systeme begriffliche Abgrenzung 1 Informationelle Systeme begriffliche Abgrenzung Im Titel dieses Buches wurde das Wort Softwaresystem an den Anfang gestellt. Dies ist kein Zufall, denn es soll einen Hinweis darauf geben, dass dieser

Mehr

20. Algorithmus der Woche Online-Algorithmen: Was ist es wert, die Zukunft zu kennen? Das Ski-Problem

20. Algorithmus der Woche Online-Algorithmen: Was ist es wert, die Zukunft zu kennen? Das Ski-Problem 20. Algorithmus der Woche Online-Algorithmen: Was ist es wert, die Zukunft zu kennen? Das Ski-Problem Autor Susanne Albers, Universität Freiburg Swen Schmelzer, Universität Freiburg In diesem Jahr möchte

Mehr

Einrichtung des Cisco VPN Clients (IPSEC) in Windows7

Einrichtung des Cisco VPN Clients (IPSEC) in Windows7 Einrichtung des Cisco VPN Clients (IPSEC) in Windows7 Diese Verbindung muss einmalig eingerichtet werden und wird benötigt, um den Zugriff vom privaten Rechner oder der Workstation im Home Office über

Mehr

Datentechnik. => Das Rechenergebnis ist nur dann sinnvoll, wenn es rechtzeitig vorliegt. Die Zeit muß daher beim Programmdesign berücksichtigt werden.

Datentechnik. => Das Rechenergebnis ist nur dann sinnvoll, wenn es rechtzeitig vorliegt. Die Zeit muß daher beim Programmdesign berücksichtigt werden. 5. Steuerung technischer Prozesse 5.1 Echtzeit (real time) Im Gegensatz zu Aufgabenstellungen aus der Büroumgebung, wo der Anwender mehr oder weniger geduldig wartet, bis der Computer ein Ergebnis liefert

Mehr

Anleitung zur Daten zur Datensicherung und Datenrücksicherung. Datensicherung

Anleitung zur Daten zur Datensicherung und Datenrücksicherung. Datensicherung Anleitung zur Daten zur Datensicherung und Datenrücksicherung Datensicherung Es gibt drei Möglichkeiten der Datensicherung. Zwei davon sind in Ges eingebaut, die dritte ist eine manuelle Möglichkeit. In

Mehr

infach Geld FBV Ihr Weg zum finanzellen Erfolg Florian Mock

infach Geld FBV Ihr Weg zum finanzellen Erfolg Florian Mock infach Ihr Weg zum finanzellen Erfolg Geld Florian Mock FBV Die Grundlagen für finanziellen Erfolg Denn Sie müssten anschließend wieder vom Gehaltskonto Rückzahlungen in Höhe der Entnahmen vornehmen, um

Mehr

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes.

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes. Binäre Bäume Definition: Ein binärer Baum T besteht aus einer Menge von Knoten, die durch eine Vater-Kind-Beziehung wie folgt strukturiert ist: 1. Es gibt genau einen hervorgehobenen Knoten r T, die Wurzel

Mehr

Übersicht. Nebenläufige Programmierung. Praxis und Semantik. Einleitung. Sequentielle und nebenläufige Programmierung. Warum ist. interessant?

Übersicht. Nebenläufige Programmierung. Praxis und Semantik. Einleitung. Sequentielle und nebenläufige Programmierung. Warum ist. interessant? Übersicht Aktuelle Themen zu Informatik der Systeme: Nebenläufige Programmierung: Praxis und Semantik Einleitung 1 2 der nebenläufigen Programmierung WS 2011/12 Stand der Folien: 18. Oktober 2011 1 TIDS

Mehr

Speicher in der Cloud

Speicher in der Cloud Speicher in der Cloud Kostenbremse, Sicherheitsrisiko oder Basis für die unternehmensweite Kollaboration? von Cornelius Höchel-Winter 2013 ComConsult Research GmbH, Aachen 3 SYNCHRONISATION TEUFELSZEUG

Mehr

Bilder zum Upload verkleinern

Bilder zum Upload verkleinern Seite 1 von 9 Bilder zum Upload verkleinern Teil 1: Maße der Bilder verändern Um Bilder in ihren Abmessungen zu verkleinern benutze ich die Freeware Irfan View. Die Software biete zwar noch einiges mehr

Mehr

Objektorientierte Programmierung für Anfänger am Beispiel PHP

Objektorientierte Programmierung für Anfänger am Beispiel PHP Objektorientierte Programmierung für Anfänger am Beispiel PHP Johannes Mittendorfer http://jmittendorfer.hostingsociety.com 19. August 2012 Abstract Dieses Dokument soll die Vorteile der objektorientierten

Mehr

Albert HAYR Linux, IT and Open Source Expert and Solution Architect. Open Source professionell einsetzen

Albert HAYR Linux, IT and Open Source Expert and Solution Architect. Open Source professionell einsetzen Open Source professionell einsetzen 1 Mein Background Ich bin überzeugt von Open Source. Ich verwende fast nur Open Source privat und beruflich. Ich arbeite seit mehr als 10 Jahren mit Linux und Open Source.

Mehr

Die Erstellung eigener Strukturprofile

Die Erstellung eigener Strukturprofile Die Erstellung eigener Strukturprofile Manchmal ist es nötig, eigene Profile zu Erstellen, die man dann mittels Gestellgenerator verbaut. Diese Strukturprofile werden in einer Benutzerbezogenen Bibliothek

Mehr

QM: Prüfen -1- KN16.08.2010

QM: Prüfen -1- KN16.08.2010 QM: Prüfen -1- KN16.08.2010 2.4 Prüfen 2.4.1 Begriffe, Definitionen Ein wesentlicher Bestandteil der Qualitätssicherung ist das Prüfen. Sie wird aber nicht wie früher nach der Fertigung durch einen Prüfer,

Mehr

Mean Time Between Failures (MTBF)

Mean Time Between Failures (MTBF) Mean Time Between Failures (MTBF) Hintergrundinformation zur MTBF Was steht hier? Die Mean Time Between Failure (MTBF) ist ein statistischer Mittelwert für den störungsfreien Betrieb eines elektronischen

Mehr

Agile Vorgehensmodelle in der Softwareentwicklung: Scrum

Agile Vorgehensmodelle in der Softwareentwicklung: Scrum C A R L V O N O S S I E T Z K Y Agile Vorgehensmodelle in der Softwareentwicklung: Scrum Johannes Diemke Vortrag im Rahmen der Projektgruppe Oldenburger Robot Soccer Team im Wintersemester 2009/2010 Was

Mehr

Zunächst empfehlen wir Ihnen die bestehenden Daten Ihres Gerätes auf USB oder im internen Speicher des Gerätes zu sichern.

Zunächst empfehlen wir Ihnen die bestehenden Daten Ihres Gerätes auf USB oder im internen Speicher des Gerätes zu sichern. Anleitung zum Softwareupdate Eycos S 75.15 HD+ Eine falsche Vorgehensweise während des Updates kann schwere Folgen haben. Sie sollten auf jeden Fall vermeiden, während des laufenden Updates die Stromversorgung

Mehr

Anbindung LMS an Siemens S7. Information

Anbindung LMS an Siemens S7. Information Datum: 18.09.2003 Status: Autor: Datei: Lieferzustand Rödenbeck Dokument1 Versio n Änderung Name Datum 1.0 Erstellt TC 18.09.03 Seite 1 von 1 Inhalt 1 Allgemein...3 2 Komponenten...3 3 Visualisierung...4

Mehr

Outlook 2000 Thema - Archivierung

Outlook 2000 Thema - Archivierung interne Schulungsunterlagen Outlook 2000 Thema - Inhaltsverzeichnis 1. Allgemein... 3 2. Grundeinstellungen für die Auto in Outlook... 3 3. Auto für die Postfach-Ordner einstellen... 4 4. Manuelles Archivieren

Mehr

Verwendung des Terminalservers der MUG

Verwendung des Terminalservers der MUG Verwendung des Terminalservers der MUG Inhalt Allgemeines... 1 Installation des ICA-Client... 1 An- und Abmeldung... 4 Datentransfer vom/zum Terminalserver... 5 Allgemeines Die Medizinische Universität

Mehr

Beschreibung E-Mail Regeln z.b. Abwesenheitsmeldung und Weiterleitung

Beschreibung E-Mail Regeln z.b. Abwesenheitsmeldung und Weiterleitung Outlook Weiterleitungen & Abwesenheitsmeldungen Seite 1 von 6 Beschreibung E-Mail Regeln z.b. Abwesenheitsmeldung und Weiterleitung Erstellt: Quelle: 3.12.09/MM \\rsiag-s3aad\install\vnc\email Weiterleitung

Mehr

Ist Fernsehen schädlich für die eigene Meinung oder fördert es unabhängig zu denken?

Ist Fernsehen schädlich für die eigene Meinung oder fördert es unabhängig zu denken? UErörterung zu dem Thema Ist Fernsehen schädlich für die eigene Meinung oder fördert es unabhängig zu denken? 2000 by christoph hoffmann Seite I Gliederung 1. In zu großen Mengen ist alles schädlich. 2.

Mehr

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3 Handbuch Fischertechnik-Einzelteiltabelle V3.7.3 von Markus Mack Stand: Samstag, 17. April 2004 Inhaltsverzeichnis 1. Systemvorraussetzungen...3 2. Installation und Start...3 3. Anpassen der Tabelle...3

Mehr

Modul 2: Automatisierung des Posteingangs - Regel- und Abwesenheits-Assistent

Modul 2: Automatisierung des Posteingangs - Regel- und Abwesenheits-Assistent Outlook 2003 - Aufbaukurs 19 Modul 2: Automatisierung des Posteingangs - Regel- und Abwesenheits-Assistent Wie kann ich die Bearbeitung von Nachrichten automatisieren? Wie kann ich Nachrichten automatisch

Mehr

Einrichten einer Festplatte mit FDISK unter Windows 95/98/98SE/Me

Einrichten einer Festplatte mit FDISK unter Windows 95/98/98SE/Me Einrichten einer Festplatte mit FDISK unter Windows 95/98/98SE/Me Bevor Sie die Platte zum ersten Mal benutzen können, muss sie noch partitioniert und formatiert werden! Vorher zeigt sich die Festplatte

Mehr

SICHERN DER FAVORITEN

SICHERN DER FAVORITEN Seite 1 von 7 SICHERN DER FAVORITEN Eine Anleitung zum Sichern der eigenen Favoriten zur Verfügung gestellt durch: ZID Dezentrale Systeme März 2010 Seite 2 von 7 Für die Datensicherheit ist bekanntlich

Mehr

I N F O R M A T I O N V I R T U A L I S I E R U N G. Wir schützen Ihre Unternehmenswerte

I N F O R M A T I O N V I R T U A L I S I E R U N G. Wir schützen Ihre Unternehmenswerte I N F O R M A T I O N V I R T U A L I S I E R U N G Wir schützen Ihre Unternehmenswerte Wir schützen Ihre Unternehmenswerte Ausfallsicherheit durch Virtualisierung Die heutigen Anforderungen an IT-Infrastrukturen

Mehr

NetStream Helpdesk-Online. Verwalten und erstellen Sie Ihre eigenen Tickets

NetStream Helpdesk-Online. Verwalten und erstellen Sie Ihre eigenen Tickets Verwalten und erstellen Sie Ihre eigenen Tickets NetStream GmbH 2014 Was ist NetStream Helpdesk-Online? NetStream Helpdesk-Online ist ein professionelles Support-Tool, mit dem Sie alle Ihre Support-Anfragen

Mehr

1. Man schreibe die folgenden Aussagen jeweils in einen normalen Satz um. Zum Beispiel kann man die Aussage:

1. Man schreibe die folgenden Aussagen jeweils in einen normalen Satz um. Zum Beispiel kann man die Aussage: Zählen und Zahlbereiche Übungsblatt 1 1. Man schreibe die folgenden Aussagen jeweils in einen normalen Satz um. Zum Beispiel kann man die Aussage: Für alle m, n N gilt m + n = n + m. in den Satz umschreiben:

Mehr

1 Einleitung. Lernziele. Symbolleiste für den Schnellzugriff anpassen. Notizenseiten drucken. eine Präsentation abwärtskompatibel speichern

1 Einleitung. Lernziele. Symbolleiste für den Schnellzugriff anpassen. Notizenseiten drucken. eine Präsentation abwärtskompatibel speichern 1 Einleitung Lernziele Symbolleiste für den Schnellzugriff anpassen Notizenseiten drucken eine Präsentation abwärtskompatibel speichern eine Präsentation auf CD oder USB-Stick speichern Lerndauer 4 Minuten

Mehr

Dokumentation IBIS Monitor

Dokumentation IBIS Monitor Dokumentation IBIS Monitor Seite 1 von 16 11.01.06 Inhaltsverzeichnis 1. Allgemein 2. Installation und Programm starten 3. Programmkonfiguration 4. Aufzeichnung 4.1 Aufzeichnung mitschneiden 4.1.1 Inhalt

Mehr

2004, Thomas Barmetler Automatisierungstechnik - Einstieg. Das EVA-Prinzip

2004, Thomas Barmetler Automatisierungstechnik - Einstieg. Das EVA-Prinzip Das EVA-Prinzip 1 Steuerungsarten Steuerungen lassen sich im Wesentlichen nach folgenden Merkmalen unterscheiden: Unterscheidung nach Art der Informationsdarstellung Diese Unterscheidung bezieht sich auf

Mehr

Das große ElterngeldPlus 1x1. Alles über das ElterngeldPlus. Wer kann ElterngeldPlus beantragen? ElterngeldPlus verstehen ein paar einleitende Fakten

Das große ElterngeldPlus 1x1. Alles über das ElterngeldPlus. Wer kann ElterngeldPlus beantragen? ElterngeldPlus verstehen ein paar einleitende Fakten Das große x -4 Alles über das Wer kann beantragen? Generell kann jeder beantragen! Eltern (Mütter UND Väter), die schon während ihrer Elternzeit wieder in Teilzeit arbeiten möchten. Eltern, die während

Mehr

Tutorial - www.root13.de

Tutorial - www.root13.de Tutorial - www.root13.de Netzwerk unter Linux einrichten (SuSE 7.0 oder höher) Inhaltsverzeichnis: - Netzwerk einrichten - Apache einrichten - einfaches FTP einrichten - GRUB einrichten Seite 1 Netzwerk

Mehr

Rheinische Fachhochschule Köln

Rheinische Fachhochschule Köln Inhaltsverzeichnis: 1.3 Schwerpunkte und Begriffe der MSR-Technik 2 1.3.1 Steuern, Regeln, Leiten 2 1.3.1.1 Steuern 2 1.3.1.2 Regeln 4 1.3.1.3 Leiten 6 1 von 8 1.3 Schwerpunkte und Begriffe der MSR-Technik

Mehr

Projektmanagement. Einleitung. Beginn. Was ist Projektmanagement? In dieser Dokumentation erfahren Sie Folgendes:

Projektmanagement. Einleitung. Beginn. Was ist Projektmanagement? In dieser Dokumentation erfahren Sie Folgendes: Projektmanagement Link http://promana.edulearning.at/projektleitung.html Einleitung Was ist Projektmanagement? In dieser Dokumentation erfahren Sie Folgendes: Definition des Begriffs Projekt" Kriterien

Mehr

Erklärung zum Internet-Bestellschein

Erklärung zum Internet-Bestellschein Erklärung zum Internet-Bestellschein Herzlich Willkommen bei Modellbahnbau Reinhardt. Auf den nächsten Seiten wird Ihnen mit hilfreichen Bildern erklärt, wie Sie den Internet-Bestellschein ausfüllen und

Mehr

Verwendung des IDS Backup Systems unter Windows 2000

Verwendung des IDS Backup Systems unter Windows 2000 Verwendung des IDS Backup Systems unter Windows 2000 1. Download der Software Netbackup2000 Unter der Adresse http://www.ids-mannheim.de/zdv/lokal/dienste/backup finden Sie die Software Netbackup2000.

Mehr

Echtzeitfähige Ereignisgetriebene Scheduling-Strategien

Echtzeitfähige Ereignisgetriebene Scheduling-Strategien Friedrich-Alexander-Universität Erlangen-Nürnberg Ausgewählte Kapitel eingebetteter Systeme Echtzeitfähige Ereignisgetriebene Scheduling-Strategien Sven Kerschbaum 1. Einführung Bei einem eingebetteten

Mehr

Anleitung über den Umgang mit Schildern

Anleitung über den Umgang mit Schildern Anleitung über den Umgang mit Schildern -Vorwort -Wo bekommt man Schilder? -Wo und wie speichert man die Schilder? -Wie füge ich die Schilder in meinen Track ein? -Welche Bauteile kann man noch für Schilder

Mehr

Informationsblatt Induktionsbeweis

Informationsblatt Induktionsbeweis Sommer 015 Informationsblatt Induktionsbeweis 31. März 015 Motivation Die vollständige Induktion ist ein wichtiges Beweisverfahren in der Informatik. Sie wird häufig dazu gebraucht, um mathematische Formeln

Mehr

Übung: Verwendung von Java-Threads

Übung: Verwendung von Java-Threads Übung: Verwendung von Java-Threads Ziel der Übung: Diese Übung dient dazu, den Umgang mit Threads in der Programmiersprache Java kennenzulernen. Ein einfaches Java-Programm, das Threads nutzt, soll zum

Mehr

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster Es gibt in Excel unter anderem die so genannten Suchfunktionen / Matrixfunktionen Damit können Sie Werte innerhalb eines bestimmten Bereichs suchen. Als Beispiel möchte ich die Funktion Sverweis zeigen.

Mehr

Novell Client. Anleitung. zur Verfügung gestellt durch: ZID Dezentrale Systeme. Februar 2015. ZID Dezentrale Systeme

Novell Client. Anleitung. zur Verfügung gestellt durch: ZID Dezentrale Systeme. Februar 2015. ZID Dezentrale Systeme Novell Client Anleitung zur Verfügung gestellt durch: ZID Dezentrale Systeme Februar 2015 Seite 2 von 8 Mit der Einführung von Windows 7 hat sich die Novell-Anmeldung sehr stark verändert. Der Novell Client

Mehr

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang sysplus.ch outlook - mail-grundlagen Seite 1/8 Outlook Mail-Grundlagen Posteingang Es gibt verschiedene Möglichkeiten, um zum Posteingang zu gelangen. Man kann links im Outlook-Fenster auf die Schaltfläche

Mehr

INTERNETZUGANG WLAN-ROUTER ANLEITUNG FIRMWARE-UPDATE SIEMENS

INTERNETZUGANG WLAN-ROUTER ANLEITUNG FIRMWARE-UPDATE SIEMENS Wichtige Hinweise: Das Firmware-Update geschieht auf eigene Gefahr! NetCologne übernimmt keine Verantwortung für mögliche Schäden an Ihrem WLAN-Router, die in Zusammenhang mit dem Firmware-Update oder

Mehr

Dokumentation zum Spielserver der Software Challenge

Dokumentation zum Spielserver der Software Challenge Dokumentation zum Spielserver der Software Challenge 10.08.2011 Inhaltsverzeichnis: Programmoberfläche... 2 Ein neues Spiel erstellen... 2 Spielfeldoberfläche... 4 Spielwiederholung laden... 5 Testdurchläufe...

Mehr

Kurzanleitung. MEYTON Aufbau einer Internetverbindung. 1 Von 11

Kurzanleitung. MEYTON Aufbau einer Internetverbindung. 1 Von 11 Kurzanleitung MEYTON Aufbau einer Internetverbindung 1 Von 11 Inhaltsverzeichnis Installation eines Internetzugangs...3 Ist mein Router bereits im MEYTON Netzwerk?...3 Start des YAST Programms...4 Auswahl

Mehr

Urlaubsregel in David

Urlaubsregel in David Urlaubsregel in David Inhaltsverzeichnis KlickDown Beitrag von Tobit...3 Präambel...3 Benachrichtigung externer Absender...3 Erstellen oder Anpassen des Anworttextes...3 Erstellen oder Anpassen der Auto-Reply-Regel...5

Mehr

How-to: Webserver NAT. Securepoint Security System Version 2007nx

How-to: Webserver NAT. Securepoint Security System Version 2007nx Securepoint Security System Inhaltsverzeichnis Webserver NAT... 3 1 Konfiguration einer Webserver NAT... 4 1.1 Einrichten von Netzwerkobjekten... 4 1.2 Erstellen von Firewall-Regeln... 6 Seite 2 Webserver

Mehr

Inhalt. 1 Einleitung AUTOMATISCHE DATENSICHERUNG AUF EINEN CLOUDSPEICHER

Inhalt. 1 Einleitung AUTOMATISCHE DATENSICHERUNG AUF EINEN CLOUDSPEICHER AUTOMATISCHE DATENSICHERUNG AUF EINEN CLOUDSPEICHER Inhalt 1 Einleitung... 1 2 Einrichtung der Aufgabe für die automatische Sicherung... 2 2.1 Die Aufgabenplanung... 2 2.2 Der erste Testlauf... 9 3 Problembehebung...

Mehr

Leichte-Sprache-Bilder

Leichte-Sprache-Bilder Leichte-Sprache-Bilder Reinhild Kassing Information - So geht es 1. Bilder gucken 2. anmelden für Probe-Bilder 3. Bilder bestellen 4. Rechnung bezahlen 5. Bilder runterladen 6. neue Bilder vorschlagen

Mehr

Erstellen einer digitalen Signatur für Adobe-Formulare

Erstellen einer digitalen Signatur für Adobe-Formulare Erstellen einer digitalen Signatur für Adobe-Formulare (Hubert Straub 24.07.13) Die beiden Probleme beim Versenden digitaler Dokumente sind einmal die Prüfung der Authentizität des Absenders (was meist

Mehr

Eigene Dokumente, Fotos, Bilder etc. sichern

Eigene Dokumente, Fotos, Bilder etc. sichern Eigene Dokumente, Fotos, Bilder etc. sichern Solange alles am PC rund läuft, macht man sich keine Gedanken darüber, dass bei einem Computer auch mal ein technischer Defekt auftreten könnte. Aber Grundsätzliches

Mehr

Lizenzierung von Windows Server 2012

Lizenzierung von Windows Server 2012 Lizenzierung von Windows Server 2012 Das Lizenzmodell von Windows Server 2012 Datacenter und Standard besteht aus zwei Komponenten: Prozessorlizenzen zur Lizenzierung der Serversoftware und CALs zur Lizenzierung

Mehr

schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG mitp/bhv

schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG mitp/bhv Roboter programmieren mit NXC für Lego Mindstorms NXT 1. Auflage Roboter programmieren mit NXC für Lego Mindstorms NXT schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG mitp/bhv Verlag

Mehr

HTBVIEWER INBETRIEBNAHME

HTBVIEWER INBETRIEBNAHME HTBVIEWER INBETRIEBNAHME Vorbereitungen und Systemvoraussetzungen... 1 Systemvoraussetzungen... 1 Betriebssystem... 1 Vorbereitungen... 1 Installation und Inbetriebnahme... 1 Installation... 1 Assistenten

Mehr

Professionelle Seminare im Bereich MS-Office

Professionelle Seminare im Bereich MS-Office Der Name BEREICH.VERSCHIEBEN() ist etwas unglücklich gewählt. Man kann mit der Funktion Bereiche zwar verschieben, man kann Bereiche aber auch verkleinern oder vergrößern. Besser wäre es, die Funktion

Mehr

Dokumentation Schedulingverfahren

Dokumentation Schedulingverfahren Dokumentation Schedulingverfahren von Norbert Galuschek Gordian Maugg Alexander Hahn Rebekka Weissinger June 23, 2011 1 Contents 1 Aufgabe 3 2 Vorgehensweise 4 2.1 Warum Android.......................

Mehr

Vermeiden Sie es sich bei einer deutlich erfahreneren Person "dranzuhängen", Sie sind persönlich verantwortlich für Ihren Lernerfolg.

Vermeiden Sie es sich bei einer deutlich erfahreneren Person dranzuhängen, Sie sind persönlich verantwortlich für Ihren Lernerfolg. 1 2 3 4 Vermeiden Sie es sich bei einer deutlich erfahreneren Person "dranzuhängen", Sie sind persönlich verantwortlich für Ihren Lernerfolg. Gerade beim Einstig in der Programmierung muss kontinuierlich

Mehr

Scheduling in Echtzeitbetriebssystemen. Prof. Dr. Margarita Esponda Freie Universität Berlin

Scheduling in Echtzeitbetriebssystemen. Prof. Dr. Margarita Esponda Freie Universität Berlin Scheduling in Echtzeitbetriebssystemen Prof. Dr. Margarita Esponda Freie Universität Berlin Echtzeitsysteme Korrekte Ergebnisse zum richtigen Zeitpunkt Hart Echtzeitsysteme Eine verspätete Antwort ist

Mehr

Step by Step Webserver unter Windows Server 2003. von Christian Bartl

Step by Step Webserver unter Windows Server 2003. von Christian Bartl Step by Step Webserver unter Windows Server 2003 von Webserver unter Windows Server 2003 Um den WWW-Server-Dienst IIS (Internet Information Service) zu nutzen muss dieser zunächst installiert werden (wird

Mehr

ICS-Addin. Benutzerhandbuch. Version: 1.0

ICS-Addin. Benutzerhandbuch. Version: 1.0 ICS-Addin Benutzerhandbuch Version: 1.0 SecureGUARD GmbH, 2011 Inhalt: 1. Was ist ICS?... 3 2. ICS-Addin im Dashboard... 3 3. ICS einrichten... 4 4. ICS deaktivieren... 5 5. Adapter-Details am Server speichern...

Mehr

LPT1 Anschluss mit PCMCIA Karte

LPT1 Anschluss mit PCMCIA Karte 1. Allgemeines LPT1 Anschluss mit PCMCIA Karte verwendete Hardware: Lenze PC Systembusadapter EMF 2173-V003 PCMCIA Karte Firma QUATECH Typ SPP-100 Auf die Installation der PCMCIA Karte wird hier nicht

Mehr

Name:... Matrikel-Nr.:... 3 Aufgabe Handyklingeln in der Vorlesung (9 Punkte) Angenommen, ein Student führt ein Handy mit sich, das mit einer Wahrscheinlichkeit von p während einer Vorlesung zumindest

Mehr

pro4controlling - Whitepaper [DEU] Whitepaper zur CfMD-Lösung pro4controlling Seite 1 von 9

pro4controlling - Whitepaper [DEU] Whitepaper zur CfMD-Lösung pro4controlling Seite 1 von 9 Whitepaper zur CfMD-Lösung pro4controlling Seite 1 von 9 1 Allgemeine Beschreibung "Was war geplant, wo stehen Sie jetzt und wie könnte es noch werden?" Das sind die typischen Fragen, mit denen viele Unternehmer

Mehr

Mind Mapping am PC. für Präsentationen, Vorträge, Selbstmanagement. von Isolde Kommer, Helmut Reinke. 1. Auflage. Hanser München 1999

Mind Mapping am PC. für Präsentationen, Vorträge, Selbstmanagement. von Isolde Kommer, Helmut Reinke. 1. Auflage. Hanser München 1999 Mind Mapping am PC für Präsentationen, Vorträge, Selbstmanagement von Isolde Kommer, Helmut Reinke 1. Auflage Hanser München 1999 Verlag C.H. Beck im Internet: www.beck.de ISBN 978 3 446 21222 0 schnell

Mehr

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen Binäre Bäume 1. Allgemeines Binäre Bäume werden grundsätzlich verwendet, um Zahlen der Größe nach, oder Wörter dem Alphabet nach zu sortieren. Dem einfacheren Verständnis zu Liebe werde ich mich hier besonders

Mehr