Die Reihe Xpert.press vermittelt Professionals in den Bereichen Softwareentwicklung, Internettechnologie und IT-Management aktuell und kompetent relevantes Fachwissen über Technologien und Produkte zur Entwicklung und Anwendung moderner Informationstechnologien.
Joachim Wietzke · Manh Tien Tran
Automotive Embedded Systeme Effizientes Framework – Vom Design zur Implementierung Mit 84 Abbildungen
123
Joachim Wietzke Fachhochschule Darmstadt, Fachbereich Technische Informatik und Grundlagen der Informatik Haardtring 100 64295 Darmstadt
[email protected] Manh Tien Tran Fachhochschule Kaiserslautern, Fachbereich IMST Amerikastr. 1 66482 Zweibrücken
[email protected] Bibliografische Information der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.
ISSN 1439-5428 ISBN-10 3-540-24339-9 Springer Berlin Heidelberg New York ISBN-13 978-3-540-24339-7 Springer Berlin Heidelberg New York Dieses Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere die der Übersetzung, des Nachdrucks, des Vortrags, der Entnahme von Abbildungen und Tabellen, der Funksendung, der Mikroverfilmung oder der Vervielfältigung auf anderen Wegen und der Speicherung in Datenverarbeitungsanlagen bleiben, auch bei nur auszugsweiser Verwertung, vorbehalten. Eine Vervielfältigung dieses Werkes oder von Teilen dieses Werkes ist auch im Einzelfall nur in den Grenzen der gesetzlichen Bestimmungen des Urheberrechtsgesetzes der Bundesrepublik Deutschland vom 9. September 1965 in der jeweils geltenden Fassung zulässig. Sie ist grundsätzlich vergütungspflichtig. Zuwiderhandlungen unterliegen den Strafbestimmungen des Urheberrechtsgesetzes. Springer ist ein Unternehmen von Springer Science+Business Media springer.de © Springer-Verlag Berlin Heidelberg 2005 Printed in The Netherlands Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutzgesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Text und Abbildungen wurden mit größter Sorgfalt erarbeitet. Verlag und Autor können jedoch für eventuell verbliebene fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Satz: Druckfertige Daten der Autoren Herstellung: LE-TeX Jelonek, Schmidt & Vöckler GbR, Leipzig Umschlaggestaltung: KünkelLopka Werbeagentur, Heidelberg Gedruckt auf säurefreiem Papier 33/3142/YL - 5 4 3 2 1 0
Vorwort
Perfektion ist nicht erreicht, wenn nichts mehr hinzugef¨ ugt werden kann, sondern wenn nichts mehr u ¨brig ist, was man weglassen kann. Antoine de Saint-Exup´ery
Wer hat, als Entwickler in einem Team oder als Manager eines Teams, noch nicht erlebt, dass mit zunehmendem Wachstum der Gruppe die Effizienz immer schlechter wurde? Eine Verdopplung der Gruppe etwa f¨ uhrte zwar ann¨ahernd zu einer Verdopplung der Fehler, nicht aber zur Verdopplung des produzierten Codes. Potenziert wurde das Problem, als dann nicht mehr alle Teammitglieder in einen Raum passten. Einige Meter Abstand waren dann schon so schlecht wie ein ganz anderer Standort. Die u ¨bliche Abhilfemaßnahme war in der folgenden Zeit der Versuch, mehr Objektorientierung in die Implementierung zu bringen, mit dem Ergebnis, dass die Fehler komplizierter waren und langwieriger zu finden, und dass die Implementierungen wie ein Flickenteppich aus verschiedenen Programmiersprachen und Paradigmen erschienen. Außerdem wurde der Speicher schnell knapp und die Performance schlecht. Ger¨ate, die vorher keine Schwierigkeiten machten, zeigten nach der OOPUmstellung bei gleichem Feature-Umfang erh¨ ohte und inakzeptable Startzeiten im zweistelligen Sekundenbereich und vielfache Speichergr¨oßen. Als N¨achstes kamen dann Vorschl¨ age f¨ ur einheitliche Design-Regeln, Testumgebungen und vorgegebene Software-L¨ osungen, die von den wenigen Kollegen, die Objektorientierung studiert hatten, geschrieben werden sollten.
VI
Vorwort
Schließlich setzte sich, oft zu sp¨ at, die Erkenntnis durch, dass es wohl einige Architekturvorgaben geben m¨ usse, die so implementiert waren, dass sie nicht leicht umgangen werden k¨ onnten. Banal war dann noch die Erkenntnis, dass die Kollegen, die das am besten konnten, daf¨ ur freigestellt werden m¨ ussten und nicht die, die daf¨ ur Zeit h¨atten und es gerne mal probieren wollten. Gerade die Mitarbeiter wurden aber gebraucht, die daf¨ ur nie Zeit hatten, weil es ja die besten waren und deshalb u ¨berall alte Br¨ande l¨oschen mussten. Seltsamerweise waren es auch die mit dem besten Dom¨anenwissen, nicht die Gurus der Objektorientierung. Bis sich all diese Erkenntnisse durchgesetzt hatten, war dann das Projekt um Monate u undet und versandet oder das ¨berzogen, einige Task-Forces gegr¨ Projekt war beim Kunden bereits verloren. Im Nachhinein wuchs dann auch die Erkenntnis, dass es hier eigentlich um die Entwicklung eines Frameworks ging, nicht nur um die Implementierung eines Projekts. Beim n¨ achsten Mal w¨ urde man deshalb zun¨achst ein Framework entwickeln und sp¨ ater ein neues Projekt daraus ableiten. Die Effizienz des Frameworks w¨ urde messbar sein und gemessen werden. Die Projektmitarbeiter w¨ urden nur noch Kundenvarianten programmieren, alle wesentlichen Fundamente w¨aren schon im Framework festgelegt. F¨ ur dieses Framework wurde dann eine große Mannschaft u ¨ber mehrere Standorte hinweg berufen, damit auch alles Expertenwissen einfließen konnte. Schließlich wurde ein Steering-Committee dar¨ uber gestellt, dem strategische Entscheidungen vorgelegt werden sollten. Das Schicksal dieses Frameworks kennen Sie schon, es war zu groß, zu langsam und wurde nicht fertig. Es konnte auch nicht angewendet werden oder vielleicht nur f¨ ur ein einziges unvermeidliches Projekt. Wenn sich dann schließlich beim n¨ achsten Versuch hoffentlich vier bis f¨ unf Kollegen finden, ausgestattet mit Motivation und Expertenwissen ihrer Dom¨ane, Gummib¨ archen, Cola, besten Arbeitsmitteln und ohne politisches Gremium dar¨ uber, dann gibt es Hoffnung auf eine gute L¨osung. Wenn diese Pioniere dann ein kleines Nachschlagewerk brauchen, aus dem sie manche Erfahrungen oder neue Ideen sch¨ opfen k¨ onnen, dann nutzt Ihnen, meine Damen und Herren, hoffentlich dieses Buch. Viel Spaß bei der Arbeit! Manh Tien Tran, Joachim Wietzke
Inhaltsverzeichnis
1
Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
Teil I Bausteine eines Embedded Frameworks 2
Ein Infotainment-System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1 Die Headunit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.2 Ger¨ ate/Devices im System . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.3 Begriffskl¨ arung: Was ist ein Framework . . . . . . . . . . . . . . . . 10 2.4 Fokus unserer Framework-Betrachtungen . . . . . . . . . . . . . . 11
3
Anforderungen an ein Embedded Automotive Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 Verteilte Entwicklung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 MVC, Organisationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Speicheranforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4 Startzeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5 Men¨ uwechselzeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6 Konfiguration versus Dynamik . . . . . . . . . . . . . . . . . . . . . . . . 3.7 Datenfluss/Kontrollfluss/Zustandssteuerung . . . . . . . . . . . .
13 13 13 15 15 16 16 16
Betriebssysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1 Prozesse, Tasks, Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Der Scheduler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Pr¨ aemptive Priorit¨ atssteuerung . . . . . . . . . . . . . . . . . 4.2.2 Round-Robin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.3 Priorit¨ ats-Inversion, Priorit¨ats-Vererbung . . . . . . . . 4.2.4 Task-Switch, Kontextwechsel . . . . . . . . . . . . . . . . . . . 4.3 Zusammenspiel mit der MMU . . . . . . . . . . . . . . . . . . . . . . . .
17 18 19 19 19 19 20 20
4
VIII
Inhaltsverzeichnis
5
Design-Rules und Konventionen . . . . . . . . . . . . . . . . . . . . . . . . 5.1 Namenskonventionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Shared-Verzeichnisse, Libraries . . . . . . . . . . . . . . . . . . . . . . . 5.3 Eigene Definitionen im Shared . . . . . . . . . . . . . . . . . . . . . . . . 5.3.1 Standard-Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.2 Alignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.3 Little-Endian, Big-Endian . . . . . . . . . . . . . . . . . . . . . 5.3.4 Zahlengrenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.5 Const Definitionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.6 Eigene Assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.7 Exceptions und Signale . . . . . . . . . . . . . . . . . . . . . . . . 5.3.8 Eigene Ausgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21 22 22 22 22 23 24 25 25 26 27 27
6
Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1 Statische Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Dynamische Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . 6.3 Start-Up-Strategien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4 Speicherbedarf und kleine Pittfalls . . . . . . . . . . . . . . . . . . . . 6.4.1 Welche Variable wo? . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4.2 Alternative Strategie 1: Sp¨ate Konstruktion . . . . . . 6.4.3 Alternative Strategie 2: Sp¨ate Initialisierung . . . . . 6.4.4 Alternative Strategie 3: Lokale statische Variablen 6.4.5 Speicherort von Konstanten . . . . . . . . . . . . . . . . . . . . 6.4.6 Speicherbedarf eines einfachen Objekts . . . . . . . . . . 6.4.7 Speicherbedarf eines einfachen Objekts mit virtueller Methode . . . . . . . . . . . . . . . . . . . . . . . . 6.4.8 Speicherbedarf eines Objekts einer abgeleiteten einfachen Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4.9 Speicherbedarf eines Objekts einer abgeleiteten Klasse mit virtueller Methode . . . . . . . . . . . . . . 6.4.10 Speicherbedarf eines Objekts einer abgeleiteten Klasse mit virtueller Basis-Methode . . . . . . . . . 6.4.11 Mehrfachvererbung und Pittfall . . . . . . . . . . . . . . . . . 6.4.12 Speicherbedarf dynamischer Variablen . . . . . . . . . . . 6.4.13 Schlechte Parameter¨ ubergabe . . . . . . . . . . . . . . . . . . 6.4.14 Probleme mit dem Kopierkonstruktor . . . . . . . . . . . 6.4.15 Probleme mit dem Zuweisungsoperator . . . . . . . . . . 6.4.16 Schlechte R¨ uckgaben . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4.17 Padding-Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4.18 Alignment-Probleme, Endian-Probleme . . . . . . . . . . 6.4.19 Speicherfragmentierung . . . . . . . . . . . . . . . . . . . . . . . . 6.4.20 Vektoren, STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29 31 31 33 33 33 36 38 39 40 43 44 46 48 50 52 55 56 57 57 57 59 60 60 61
Inhaltsverzeichnis
IX
7
Embedded Speicherkonzepte, spezielle Muster . . . . . . . . . . 7.1 Statische Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.1 Reihenfolge der Konstruktoren . . . . . . . . . . . . . . . . . 7.2 Quasi-statische Allokation aus festem Pufferspeicher . . . . . 7.2.1 Anlegen eines statischen Puffers . . . . . . . . . . . . . . . . 7.2.2 Placement-new . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.3 Makros zur Allokation . . . . . . . . . . . . . . . . . . . . . . . . . 7.3 Allokator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3.1 Statische Allokation . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3.2 Dynamische Allokation mit tempor¨arem Heap . . . . 7.3.3 Object-Pool-Allokation . . . . . . . . . . . . . . . . . . . . . . . . 7.4 Datencontainer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5 Gemeinsam benutzte Objekte (Shared-Objects) . . . . . . . . . 7.6 Referenzz¨ ahler (reference counting) . . . . . . . . . . . . . . . . . . . . 7.7 Garbage-Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.8 Die Captain-Oates Strategie . . . . . . . . . . . . . . . . . . . . . . . . . . 7.9 Speicherlimit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.10 Partieller Fehler (Partial Failure) . . . . . . . . . . . . . . . . . . . . . 7.11 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63 63 63 66 67 67 68 70 73 75 78 82 82 83 85 85 86 87 87
8
Prozesse und POSIX-Threads . . . . . . . . . . . . . . . . . . . . . . . . . . 89 8.1 Prozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 8.1.1 Der Lebenszyklus eines Prozesses . . . . . . . . . . . . . . 90 8.1.2 Prozesszust¨ ande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 8.1.3 Zombies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 8.1.4 Bestandteile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 8.1.5 Prozess mit fork erzeugen . . . . . . . . . . . . . . . . . . . . . . 93 8.1.6 Prozess mit exec erzeugen . . . . . . . . . . . . . . . . . . . . . 94 8.1.7 Prozess mit spawn erzeugen . . . . . . . . . . . . . . . . . . . . 96 8.1.8 Prozessvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 8.1.9 Prozessattribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 ¨ 8.1.10 Uber Prozessgrenzen hinweg . . . . . . . . . . . . . . . . . . . 98 8.2 Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 8.2.1 Erzeugung von POSIX-Threads . . . . . . . . . . . . . . . . . 99 8.2.2 Allgemeine Gestaltung eines Threads in eingebetteten Anwendungen . . . . . . . . . . . . . . . . 100 8.2.3 Thread-Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 ¨ 8.2.4 Uber Threadgrenzen hinweg . . . . . . . . . . . . . . . . . . . . 104 8.2.5 Thread-spezifische Daten . . . . . . . . . . . . . . . . . . . . . . 106 8.2.6 Thread-Tr¨ ager und Thread-Objekte . . . . . . . . . . . . . 108 8.3 Designentscheidung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
X
Inhaltsverzeichnis
9
Inter-Prozess-Kommunikationskan¨ ale, IPC . . . . . . . . . . . . . . 121 9.1 Pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 9.2 FIFO, Named-Pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 9.3 Socket, local . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 9.4 Socket-Verbindung zwischen Prozessoren . . . . . . . . . . . . . . . 127 9.5 Message-Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 9.5.1 System V Message-Queues . . . . . . . . . . . . . . . . . . . . . 134 9.5.2 POSIX Message-Queues . . . . . . . . . . . . . . . . . . . . . . . 134 9.6 QNX-Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 9.7 Shared-Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 ¨ 9.8 Shared-Memory gegen Uberschreiben sch¨ utzen . . . . . . . . . . 144 9.9 Zeitverhalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 9.10 Designentscheidung und prinzipielle Implementierung . . . . 150
10
Gemeinsame Nutzung von Code . . . . . . . . . . . . . . . . . . . . . . . . 151 10.1 DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 10.2 Shared-Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 10.3 Designentscheidung und prinzipielle Implementierung . . . . 152
11
Synchronisierungsmechanismen f¨ ur Prozesse und Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 11.1 Semaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 11.2 Unbenannter Semaphor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 11.3 Benannter Semaphor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 11.4 Mutex, rekursiver Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 11.5 Mutex-Guard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 11.6 Signale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 11.7 Bedingungsvariablen (Condition Variables) . . . . . . . . . . . . . 168 11.8 Zeitverhalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 11.9 Deadlock, Livelock und Priorit¨ atsinversion . . . . . . . . . . . . . 177 11.10 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
12
Kommunikation per Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 12.1 Wichtige Event-Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 12.1.1 Signal-Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 12.1.2 Events mit Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 12.1.3 Call-Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 12.1.4 Timer-Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 12.1.5 Change-Events (Notifikation - Das HollywoodPrinzip) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 12.2 Realisierungen von Events . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 12.2.1 Einfache Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 12.2.2 C-Strukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 12.2.3 Funktionszeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 12.2.4 Event-Klassen und Objekte . . . . . . . . . . . . . . . . . . . . 190
Inhaltsverzeichnis
12.3
XI
12.2.5 Zeiger auf Event-Objekte verschicken . . . . . . . . . . . . 192 12.2.6 Events am Beispiel von MOST . . . . . . . . . . . . . . . . . 195 Designentscheidung und prinzipielle Implementierung . . . . 196
13
Event-Verarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 13.1 Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 13.1.1 Externe Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 13.1.2 Interne Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 13.1.3 System-Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 13.1.4 Queue mit verbesserter Inversions-Eigenschaft . . . . 205 13.2 Dispatcher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 13.2.1 Der System-Dispatcher . . . . . . . . . . . . . . . . . . . . . . . . 211 13.2.2 Der lokale Dispatcher . . . . . . . . . . . . . . . . . . . . . . . . . 212 13.2.3 Signalisation im Dispatcher . . . . . . . . . . . . . . . . . . . . 213 13.2.4 Registrierung im Dispatcher . . . . . . . . . . . . . . . . . . . . 213 13.3 Synchrone Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
14
Zustandsautomaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 14.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 14.2 Kommunikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 14.3 Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 14.4 Statechart-Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 14.5 Probleme mit Statecharts und Ersatzl¨osungen . . . . . . . . . . 221 14.6 Implementierung dynamischen Verhaltens . . . . . . . . . . . . . . 223 14.6.1 Definition der Events . . . . . . . . . . . . . . . . . . . . . . . . . . 224 14.6.2 Implementierung der Aktionen . . . . . . . . . . . . . . . . . 225 14.7 Implementierung der Statecharts . . . . . . . . . . . . . . . . . . . . . . 226 14.7.1 Implementierung durch Aufz¨ahlung f¨ ur Zust¨ande und Unterzust¨ ande . . . . . . . . . . . . . . . . . . . . . . . . 226 14.7.2 Zustandsmuster (State-Pattern) . . . . . . . . . . . . . . . . 231 14.7.3 Verbesserte Aufz¨ ahlungsmethode . . . . . . . . . . . . . . . 240 14.8 Implementierung nach Samek . . . . . . . . . . . . . . . . . . . . . . . . 246
15
Externer Kommunikationskanal: MOST-Bus . . . . . . . . . . . . 253 15.1 Der synchrone Kanal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 15.2 Der asynchrone Kanal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 15.3 Der Kontrollkanal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 15.4 Ger¨ ate/Devices im Framework . . . . . . . . . . . . . . . . . . . . . . . . 255 15.4.1 Logische Ger¨ ate, Modelle . . . . . . . . . . . . . . . . . . . . . . 257 15.4.2 Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 15.4.3 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 15.4.4 MOST-Adressierung . . . . . . . . . . . . . . . . . . . . . . . . . . 259 15.4.5 Adressierungsbeispiele . . . . . . . . . . . . . . . . . . . . . . . . . 265 15.4.6 MOST-Events, prinzipielle Dekodierung des Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
XII
Inhaltsverzeichnis
15.4.7 MOST-Events, prinzipielle Dekodierung der Daten 268 15.4.8 MOST-Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 15.4.9 MOST-Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 Teil II Das Framework 16
Das Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
17
OS-Grundmechanismen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
18
Komponentenarchitektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 18.1 Event-Handler und Event-Formate . . . . . . . . . . . . . . . . . . . . 281 18.2 Implementierung von Schnittstellen, Proxy und Handler . 285 18.3 Komponentenarchitektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 18.3.1 Komponentenkontext und Daten im Shared-Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . 288 18.3.2 Erzeugung der Kontexte . . . . . . . . . . . . . . . . . . . . . . . 295 18.3.3 Main-/Admin-Task . . . . . . . . . . . . . . . . . . . . . . . . . . . 300 18.3.4 Signale, Mechanismen u ¨ber den Kontext . . . . . . . . . 302 18.3.5 Komponenten-Dispatcher . . . . . . . . . . . . . . . . . . . . . . 308 18.3.6 Softtimer und Timer-Manager . . . . . . . . . . . . . . . . . . 310 18.3.7 Registrierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318 18.3.8 Die Basisklasse AComponentBase und das Zusammenspiel mit der Umgebung . . . . . . . . . . 319 18.4 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
19
Main-Dispatcher-Komponente . . . . . . . . . . . . . . . . . . . . . . . . . . 331 19.1 Dispatcher-Receiver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331 19.2 Dispatcher-Main-Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
20
Modell-Komponenten, logische Ger¨ ate . . . . . . . . . . . . . . . . . . 341 20.1 Aufgaben des logischen Ger¨ ats . . . . . . . . . . . . . . . . . . . . . . . . 342 20.2 Was erwartet die HMI von einem logischen Ger¨at? . . . . . . 343 20.3 Zust¨ ande des Ger¨ ats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345 20.4 G¨ ultigkeit der Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 20.5 Kommunikationsanforderung . . . . . . . . . . . . . . . . . . . . . . . . . 347 20.6 Struktur eines logischen Ger¨ ats . . . . . . . . . . . . . . . . . . . . . . . 347 20.7 Datencontainer versus Event-Prinzip . . . . . . . . . . . . . . . . . . 347 20.8 Architektur eines Datencontainers . . . . . . . . . . . . . . . . . . . . 349 20.8.1 Struktur des Datencontainers . . . . . . . . . . . . . . . . . . 350 20.8.2 Lesezugriffe der HMI . . . . . . . . . . . . . . . . . . . . . . . . . . 356 20.8.3 Versenden eines HMI-Befehls . . . . . . . . . . . . . . . . . . . 357 20.8.4 Menge der erlaubten High-Level-Aktionen . . . . . . . 358 20.8.5 Abstrakte Klasse ADataContainerAccessor . . . . . . . 359 20.9 MOST-Codec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
Inhaltsverzeichnis
XIII
20.9.1 20.9.2 20.9.3 20.9.4 20.9.5
Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 Einfache Implementierung eines MOST-Decoders . 367 Objektorientierter Ansatz . . . . . . . . . . . . . . . . . . . . . . 370 Objekte zur Kompilierzeit . . . . . . . . . . . . . . . . . . . . . 372 Konstante Objekte zur Dekodierung der MOST-Nachrichten . . . . . . . . . . . . . . . . . . . . . . . . 374 20.10 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387 21
HMI-Komponente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
22
Persistenz-Controller und On/Off-Konzepte . . . . . . . . . . . . 391
23
Codegenerierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395 23.1 Erstellen der XML-Eingabe-Dateien . . . . . . . . . . . . . . . . . . . 395 23.2 Erzeugen des Quellcodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397 ¨ 23.3 Ubersicht der erzeugten Dateien . . . . . . . . . . . . . . . . . . . . . . 398 ¨ 23.4 Ubersetzen des Quellcodes . . . . . . . . . . . . . . . . . . . . . . . . . . . 404
24
Sonstige Aspekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405 24.1 Internes non-MOST-Device, Beispiel Mediaplayer . . . . . . . 405 24.2 Internes MOST-Device, Beispiel Tastatur . . . . . . . . . . . . . . 405 24.3 Treiber im Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406 24.4 Einfaches Debugging-Konzept . . . . . . . . . . . . . . . . . . . . . . . . 406 ¨ 24.5 Uberlastverhalten des Systems . . . . . . . . . . . . . . . . . . . . . . . . 408 24.6 Anmerkungen zur Komplexit¨ at und zum Testing . . . . . . . . 409
Anhang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 25.1 Timer und Zeitmessung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412 25.1.1 Sleep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 25.1.2 Timeout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414 25.2 Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414 25.2.1 InetAddr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414 25.2.2 CSockAcceptor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415 25.2.3 CSockConnector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 25.2.4 CSockStream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 25.3 Keyboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422 25.4 Shared-Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424 25.5 CBinarySemaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432 25.6 Extern const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439 Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
Die Listings
5-1 5-2 5-3 5-4 5-5 6-1 6-2 6-3 6-4 6-5 6-6 6-7 6-8 6-9 6-10 6-11 6-12 6-13 6-14 6-15 6-16 6-17 6-18 6-19 6-20 6-21 6-22 6-23
Standard-Typen und Konstanten, Global.h . . . . . . . . . . . . . . . Alignment-Makro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Makros zur Endian-Format-Anpassung . . . . . . . . . . . . . . . . . . . Max/Min-Typedefs f¨ ur eigene Datentypen . . . . . . . . . . . . . . . . . Eigene Assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Globales Objekt gem¨ aß Singleton-Pattern . . . . . . . . . . . . . . . . . Sp¨ate Konstruktion des globalen Singleton-Objekts . . . . . . . . . Sp¨ate Konstruktion des globalen Singleton-Objekts mit Mutex-Schutz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Singleton mit Doppel-Checking . . . . . . . . . . . . . . . . . . . . . . . . . . Sp¨ate Initialisierung eines Singletons . . . . . . . . . . . . . . . . . . . . . Lokale statische Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Struktur mit plain old Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Struktur mit Konstruktor-Aufruf . . . . . . . . . . . . . . . . . . . . . . . . . Struktur mit Konstruktor im Code-Segment . . . . . . . . . . . . . . . Instanz einer einfachen Klasse A . . . . . . . . . . . . . . . . . . . . . . . . . Speicherbedarf eines Objekts einer einfachen Klasse mit virtueller Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Speicherbedarf eines Objekts einer einfachen abgeleiteten Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Beispiel f¨ ur einen impliziten Up-Cast“ . . . . . . . . . . . . . . . . . . . ” Abgeleitete Klasse einer abstrakten Klasse . . . . . . . . . . . . . . . . Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mehrfachvererbung, Up-Cast und Down-Cast, Testausgaben . Beispiel-Interface in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Speicherbedarf f¨ ur int- und char-Variablen unter VC++ . . . Schlechte R¨ uckgabe, Beispiel 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . Schlechte R¨ uckgabe, Beispiel 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . Schlechte R¨ uckgabe, Beispiel 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . Schlechte R¨ uckgabe, Beispiel 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . Bessere R¨ uckgabe, Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23 23 25 25 26 35 36 37 38 39 40 41 42 42 43 45 47 49 51 52 53 55 56 57 58 58 58 59
XVI
Die Listings
6-24 6-25 7-1 7-2 7-3 7-4 7-5 7-6 7-7 7-8 7-9 7-10 7-11 7-12 7-13 7-14 7-15 7-16 7-17 7-18 7-19 8-1 8-2 8-3 8-4 8-5 8-6 8-7 8-8 8-9 8-10 8-11 8-12 8-13 8-14 9-1 9-2 9-3 9-4 9-5 9-6 9-7 9-8 9-9
Achtlos platzierte Member-Variablen . . . . . . . . . . . . . . . . . . . . . 59 Zeitmessungen zu Arrays und Vektoren, [Klein] . . . . . . . . . . . . 62 Systemobjekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Systemobjekt mit reserviertem Speicherbereich . . . . . . . . . . . . 66 Anlegen eines statischen Puffers . . . . . . . . . . . . . . . . . . . . . . . . . 67 Nutzung des Puffers durch eine C-Funktion . . . . . . . . . . . . . . . 67 Einsatz des Placement-new-Operators . . . . . . . . . . . . . . . . . . . . . 68 Syntax des Placement-new-Operators . . . . . . . . . . . . . . . . . . . . . 68 ¨ Eigenes Uberladen des Placement-new-Operators, [Nob] . . . . . 68 Alle Makros in FWMemory.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 Interface-Definition IAllocator.h . . . . . . . . . . . . . . . . . . . . . . . 72 Makros zur Allokation von Objekten . . . . . . . . . . . . . . . . . . . . . 73 Der Basisklassen-Prototyp CStaticAllocator.h . . . . . . . . . . . 74 Auszug aus CStaticAllocator.cpp . . . . . . . . . . . . . . . . . . . . . . 74 Vereinbarung eines Allocators . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Aufruf der Allocator-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Klasse CTemporaryHeapAllocator, Prototyp . . . . . . . . . . . . . . 77 CTemporaryHeapAllocator, Implementierung . . . . . . . . . . . . . 78 Prototyp der Klasse CObjectPool . . . . . . . . . . . . . . . . . . . . . . . . 79 Implementierung der Klasse CObjectPool . . . . . . . . . . . . . . . . . 81 Eigene Smart-Pointer-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Syntax von fork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Beispiel f¨ ur Prozess-Erzeugung mit fork . . . . . . . . . . . . . . . . . . 94 Aufruf von execlp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Start eines POSIX-Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Thread-Vereinbarung unter QNX, mit Lesen der Attribute . . 104 Erzeugung eines Threads mit eigenen Attributen . . . . . . . . . . . 104 Implementierung der TSD-Initialisierung . . . . . . . . . . . . . . . . . . 108 Zugriff auf TSD u ¨ber Makro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Basis-Interface IRunnable.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Prototyp der Thread-Klasse CThread.h . . . . . . . . . . . . . . . . . . . 112 Instantiierung eines CThread-Objekts . . . . . . . . . . . . . . . . . . . . 112 Instantiierung als Main-Thread . . . . . . . . . . . . . . . . . . . . . . . . . . 112 CThread-Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Kontextwechsel-Messungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Pipe erzeugen, Implementierung der Sendeseite . . . . . . . . . . . . 122 Pipe schreiben, lesen, schließen, Prototypen . . . . . . . . . . . . . . . 122 Erzeugen eines Socket-Paares, Prototyp . . . . . . . . . . . . . . . . . . . 126 Socket-Applikation, mit select-Implementierung . . . . . . . . . . 127 Prototyp f¨ ur Socket-Wrapper CSockStream.h . . . . . . . . . . . . . . 128 Implementierung des Socket-Wrapper CSockStream.cpp . . . . 129 Header-Datei des Connector-Wrappers CSockConnector.h . . 129 Implementierung des Connector-Wrappers CSockConnector.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 Header-Datei des Acceptor-Wrappers CSockAcceptor.h . . . . 130
Die Listings
9-10 9-11 9-12 9-13 9-14 9-15 9-16 9-17 9-18 9-19 9-20 9-21 9-22 9-23 9-24 11-1 11-2 11-3 11-4 11-5 11-6 11-7 11-8 11-9 11-10 11-11 11-12 11-13 12-1 12-2 12-3 12-4 12-5 12-6 12-7 12-8 12-9 12-10 12-11 13-1 13-2 13-3 13-4
XVII
Internet-Adress-Struktur sockaddr in in . . 131 Implementierung des Acceptor-Wrappers CSockAcceptor.cpp132 Wrapper zum Setzen der Socket-Adresse . . . . . . . . . . . . . . . . . . 133 Verwendete #includes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Klasse CMessageQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 Erzeugen Shared-Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Einblenden des Shared-Memory in den Speicherbereich . . . . . 139 Linux Kommandos f¨ ur Shared-Memory . . . . . . . . . . . . . . . . . . . 140 QNX Shared-Memory anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 Schreiben ins QNX Shared-Memory . . . . . . . . . . . . . . . . . . . . . . 142 Lesen aus dem Shared-Memory . . . . . . . . . . . . . . . . . . . . . . . . . . 143 CMemoryProtector.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 CMemoryProtector.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 Beispiel-Klasse CProtectedSharedMemory . . . . . . . . . . . . . . . . . 148 Demonstration des Shared-Memory-Schutzes . . . . . . . . . . . . . . 148 Semaphor und die Zugriffsfunktionen V(s) und P(s) . . . . . . . 155 C-Funktionen f¨ ur unbenannte Semaphore . . . . . . . . . . . . . . . . . 158 Synchronisierter Produzenten-/Konsumenten-Zugriff zweier Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Erzeugen und Schließen eines benannten Semaphors . . . . . . . . 161 Erzeugung einer Mutex-Variablen . . . . . . . . . . . . . . . . . . . . . . . . 162 Lesen der Thread-Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Implementierung eines Mutex-Schutzes f¨ ur eine Zugriffsfunktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Klasse CMutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 Guard-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 Typische Bedingungsvariablen-Anwendung . . . . . . . . . . . . . . . . 171 Code-Beispiel f¨ ur die Freigabe einer Bedingungsvariablen . . . 172 CBinarySemaphore.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Objektorientierte Implementierung CBinarySemaphore.cpp . 176 Enum f¨ ur einfache Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 Dispatcher f¨ ur Enum-Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 Event als C-Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Event als C-Struktur mit Union . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Event als C-Struktur mit Funktionszeiger und Parameter . . . 189 Implementierung der dispatch-Methode der Struktur . . . . . . 189 Event als Klasse CMessage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 Event-Klasse mit eigenen Methoden und Attributen . . . . . . . . 192 Verwendung von Event-Pool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Event-Basisklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Beispiele f¨ ur MOST-Events gem¨ aß MOST-Spezifikation . . . . . 195 Queue-Klassendefinition, Prototyp . . . . . . . . . . . . . . . . . . . . . . . 198 Eine Queue-Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Produzent-Konsument mit Counting-Semaphor . . . . . . . . . . . . 203 Teilimplementierung einer internen Queue . . . . . . . . . . . . . . . . . 205
XVIII Die Listings
13-5 13-6 13-7 13-8 14-1 14-2 14-3 14-4 14-5 14-6 14-7 14-8 14-9 14-10 14-11 14-12 14-13 14-14 14-15 14-16 14-17 14-18 14-19 14-20 15-1 15-2 15-3 15-4 15-5 15-6 18-1 18-2 18-3 18-4
Die Queue-Klasse CCommQueue.h, Prototyp . . . . . . . . . . . . . . . . 206 Basisklasse CCommQueue.cpp, Implementierung . . . . . . . . . . . . 211 Lokaler Dispatcher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 Registrierung und Deregistrierung in CDispatcher . . . . . . . . . 214 Methoden zum Beispielautomaten . . . . . . . . . . . . . . . . . . . . . . . . 225 Exit-Methoden von CFSM1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228 Die on Entry-Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 Beispiel von Event-Handler-Methoden . . . . . . . . . . . . . . . . . . . . 230 Basisklasse CBasisState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Der inEntry Oberzustand A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Klasse f¨ ur SuperState A, Implementierung . . . . . . . . . . . . . . . . . 236 Zustandsmaschine CFSM3, Header . . . . . . . . . . . . . . . . . . . . . . . . 236 Konstruktor von CFSM3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Reaktion von CFSM3 auf EV 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Zustandsmaschine CFSM3, Ereignisverteilung . . . . . . . . . . . . . . . 238 Wirkung von Event als Parameter . . . . . . . . . . . . . . . . . . . . . . . . 239 Verbesserte Aufz¨ ahlungsmethoden, Klassendeklaration . . . . . . 241 Dispatch-Methode von CSuperStateA . . . . . . . . . . . . . . . . . . . . 242 Verbesserte Aufz¨ ahlungsmethode, Implementierung . . . . . . . . . 245 Definition eines Funktionszeigers auf eine CFSM-Methode . . . 248 Makro f¨ ur den Funktionszeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 Basisklasse CFSM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 SuperState als abgeleitete Klasse . . . . . . . . . . . . . . . . . . . . . . . . . 250 FSM CFSM5 mit Funktionen als Zust¨anden . . . . . . . . . . . . . . . . 251 MOST-Event, Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 Beispiel eines NetBlock-Aufrufs . . . . . . . . . . . . . . . . . . . . . . . . . . 260 Aufbau der zentralen Registry . . . . . . . . . . . . . . . . . . . . . . . . . . . 264 Beispiele f¨ ur die Adressierung gem¨ aß MOST . . . . . . . . . . . . . . . 265 MOST-Sequenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266 Die Sammlung vieler Most-Konstanten, MOSTConst.h . . . . . . . 271 Schnittstelle IMessageHandler . . . . . . . . . . . . . . . . . . . . . . . . . . 282 Ausschnitt aus CMessage.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Der Kontext-Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291 Beschreibung des Komponentenkontexts, CComponentContext.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 18-5 Die createComponentContext-Methode der Klasse, Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 18-6 sDescriptionTable f¨ ur die Erzeugung von Kontexten . . . . . . 297 18-7 Klasse zur Erzeugung und Verwaltung der Kontexte . . . . . . . . 298 18-8 Code zur Erzeugung von Kontexten und Datencontainer . . . . 300 18-9 Pseudocode f¨ ur main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300 18-10 Auszug aus der run-Methode des Admin-Tasks, Watchdog-Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 18-11 Auszug aus der run-Methode des Admin-Tasks, Watchdog-Trigger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
Die Listings
18-12 18-13 18-14 18-15 18-16 18-17 18-18 18-19 18-20 18-21 18-22 18-23 19-1 19-2 19-3 19-4 19-5 19-6 19-7 20-1 20-2 20-3 20-4 20-5 20-6 20-7 20-8 20-9 20-10 20-11 20-12 20-13 20-14 20-15 20-16 20-17 20-18 20-19 20-20 20-21 20-22 20-23 20-24 20-25
XIX
Auszug aus der run-Methode einer Komponente . . . . . . . . . . . 303 handleTick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306 Auszug aus der Klasse CDispatcher . . . . . . . . . . . . . . . . . . . . . . 310 Klasse f¨ ur Softtimer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 Timer-Manager , CTimerManager.h . . . . . . . . . . . . . . . . . . . . . . 313 Auszug aus CObserverRegistry.cpp . . . . . . . . . . . . . . . . . . . . . 319 Headerfile der Klasse CComponentContext . . . . . . . . . . . . . . . . . 321 Komponentenservice-Klasse, CComponentServices.cpp . . . . . 323 init-Methode, Klasse AComponentBase . . . . . . . . . . . . . . . . . . 324 Hilfsklasse CSystemEventHandler . . . . . . . . . . . . . . . . . . . . . . . 325 cleanup-Methode, Klasse AComponentBase . . . . . . . . . . . . . . . 325 run-Methode, Klasse AComponentBase . . . . . . . . . . . . . . . . . . . 326 Start des MostDispatcher Receiver-Threads . . . . . . . . . . . . . . . 333 Zeiger auf Struktur MostHandle . . . . . . . . . . . . . . . . . . . . . . . . . 334 MOST-Empf¨ anger-Klasse CMostdispatcherReceiver . . . . . . 335 init-Methode von Dispatcher-Main-Thread . . . . . . . . . . . . . . . 336 Reaktionen von Dispatcher-Main-Thread . . . . . . . . . . . . . . . . . . 337 Reaktion auf Lock- und Unlock-Events . . . . . . . . . . . . . . . . . . . 337 Verteilung der MOST-Nachrichten im System-Dispatcher . . . 340 Datencontainer-Struktur f¨ ur AudioDiscInfo . . . . . . . . . . . . . . 352 Struktur StringBuffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 Struktur DataBuffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 MOST-Datencontainer f¨ ur AudioDiscPlayer . . . . . . . . . . . . . . . 355 Einige get-Methoden f¨ ur AudioDiscInfo . . . . . . . . . . . . . . . . . . 356 Implementierung der get-Methoden f¨ ur AudioDiscInfo . . . . . 357 Abstrakte Klasse ADataContainerAccessor.h . . . . . . . . . . . . . 361 Teilimplementierung CAudioDiscPlayerDCAccessor . . . . . . . . 361 Enum-Index f¨ ur den Datenzugriff . . . . . . . . . . . . . . . . . . . . . . . . . 362 Adapterklasse zum Lesen einer MOST-Nachricht . . . . . . . . . . . 365 Einige Methoden aus CMOSTInStream . . . . . . . . . . . . . . . . . . . . . 367 Dekodierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 Weitere Dekodierm¨ oglichkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371 Objekte zur Kompilierzeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372 Objekte im Code- und Datensegment . . . . . . . . . . . . . . . . . . . . . 373 MOST-Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374 CTypeInfo und CMOSTEnum zur Beschreibung der MOST-Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375 Die Methode invokeRead . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376 Hilfsstruktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377 ModelTypeEnum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377 Basis-Struktur CModelInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378 Beschreibungen von MOST-Properties, Prototyp . . . . . . . . . . . 380 Polymorphie-Ersatz f¨ ur CModelInfo . . . . . . . . . . . . . . . . . . . . . . 381 ModellInfo f¨ ur Array of Record, CArrayRecordProp.h . . . . . 381 CArrayRecordProp-Struktur f¨ ur AudioDiscInfo . . . . . . . . . . . 382
XX
Die Listings
20-26 20-27 20-28 22-1 23-1 23-2 23-3 23-4 23-5 23-6 23-7 23-8 23-9 23-10 23-11 23-12 24-1 25-1 25-2 25-3 25-4 25-5 25-6 25-7 25-8 25-9 25-10 25-11 25-12 25-13 25-14 25-15 25-16 25-17 25-18 25-19
Vom mType abh¨ angige Lesefunktion . . . . . . . . . . . . . . . . . . . . . . 383 Lesefunktion f¨ ur Array of Record . . . . . . . . . . . . . . . . . . . . . . . . 386 Lesen von Enum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386 Persistenz-Manager, Prototyp . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 Schnittstellenbeschreibung f¨ ur die Code-Generierung . . . . . . . 396 Komponenten f¨ ur den Code-Generator . . . . . . . . . . . . . . . . . . . . 397 Generierung des Projekts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397 Starten einer Komponente als Thread . . . . . . . . . . . . . . . . . . . . 398 Starten einer Komponente als Prozess am Beispiel MostDispatcher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398 Der Watchdog, der innerhalb main() ausgef¨ uhrt wird . . . . . . . 399 Schleife zum Erzeugen der Kontexte . . . . . . . . . . . . . . . . . . . . . . 400 Projekt mit Amp-, Debug- und Dispatcher-Komponente . . . . 401 DebugConfig.cpp am Beispiel eines Audioamplifiers . . . . . . . . 401 Beispiel f¨ ur den Audioamplifier . . . . . . . . . . . . . . . . . . . . . . . . . . 402 AudioamplifierPort.cpp, AudioamplifierPort.h . . . . . . . . 403 AudioamplifierHandler.cpp und AudioamplifierHandler.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404 Implementation of CCommQueue . . . . . . . . . . . . . . . . . . . . . . . . . . 408 Timer und Zeitmessung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 OS-Abh¨ angigkeiten f¨ ur eine sleep-Methode . . . . . . . . . . . . . . . 413 Abh¨angige includes f¨ ur Sem.take mit timeout . . . . . . . . . . . . 414 OS-abh¨ angige includes f¨ ur CInetAddr.h, Header . . . . . . . . . . 415 OS-abh¨ angige Implementierungen in CInetAddr . . . . . . . . . . . 415 OS-abh¨ angige includes und Definitionen f¨ ur CSockAcceptor, Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416 OS-abh¨ angige Implementierungen f¨ ur CSockAcceptor . . . . . . 417 OS-abh¨ angige Includes f¨ ur CSockConnector, Header . . . . . . 418 OS-abh¨ angige Implementierungen f¨ ur CSockConnector . . . . . 420 OS-abh¨ angige Includes f¨ ur CSockStream, Header . . . . . . . . . . 421 OS-abh¨ angige Implementierungen f¨ ur CSockStream . . . . . . . . . 422 OS-abh¨ angige Terminierung von Socket . . . . . . . . . . . . . . . . . . . 422 Tastatur- und Keyboard-Eingaben . . . . . . . . . . . . . . . . . . . . . . . 423 Abh¨angige Definitionen f¨ ur Shared-Memory . . . . . . . . . . . . . . . 424 PosixShm.h f¨ ur WIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 PosixShm.cpp f¨ ur WIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431 OS-abh¨ angige CBinarySemaphore.h . . . . . . . . . . . . . . . . . . . . . . 433 OS-abh¨ angige Implementierungen f¨ ur CBinarySemaphore . . . 438 Abh¨angige externe const-Definitionen . . . . . . . . . . . . . . . . . . . . 439
Abbildungsverzeichnis
2.1 2.2
Beispiel eines Infotainment-Systems . . . . . . . . . . . . . . . . . . . . . . . Beispiel eines Funktions-Blockbilds einer Headunit . . . . . . . . . .
7 9
3.1
MVC-Architektur am Beispiel Tuner . . . . . . . . . . . . . . . . . . . . . . 14
5.1
Big/Little-Endian . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
6.1 6.2 6.3 6.4 6.5 6.6 6.7
Speichermap, siehe auch [Mad] . . . . . . . . . . . . . . . . . . . . . . . . . . . Heap- und Stack im Adressraum . . . . . . . . . . . . . . . . . . . . . . . . . . Speicher-Layout eines Objekts mit virtueller Methode . . . . . . . Speicher-Layout eines B-Objekts mit virtueller Methode . . . . . Impliziter Up-Cast“, Speicher-Layout . . . . . . . . . . . . . . . . . . . . . ” Speicher-Layout zweier virtueller Klassen-Objekte . . . . . . . . . . . Mehrfach-Vererbung und falscher Cast . . . . . . . . . . . . . . . . . . . . .
7.1 7.2 7.3
Abh¨angige Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Klassendiagramm Allocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Statisches Allocator-Objekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
8.1 8.2 8.3
Speicherlayout nach fork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Nachrichtenkopplung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Speicherkopplung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
9.1 9.2 9.3 9.4 9.5 9.6
Pipe-Erzeugung, fork und Schließen der Pipe-Enden . . . . . . . . 123 Kommunikation mit Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Message-Queue Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 QNX-Message-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Bildliche Darstellung eines Shared-Memory Bereichs . . . . . . . . . 138 Zeitmessungen der unterschiedlichen IPC-Methoden, [Klein] . . 149
11.1 11.2
Zustandsdiagramm der m¨ oglichen Semaphore-Zust¨ande . . . . . . 155 Semaphore zwischen zwei Prozessen . . . . . . . . . . . . . . . . . . . . . . . 157
30 32 45 47 50 51 53
XXII
Abbildungsverzeichnis
11.3 11.4 11.5 11.6 11.7
Zustandsdiagramm der m¨ oglichen Mutex-Zust¨ande . . . . . . . . . . 162 Zeitverhalten der Synchronisationsmethoden . . . . . . . . . . . . . . . . 177 Deadlock-Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 Begrenzte Inversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 Priorit¨atsinversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
12.1
Die Nutzung eines Pools zur Event-Erzeugung . . . . . . . . . . . . . . 192
13.1 13.2 13.3
Symbolische Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 Reale Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 Handshake zwischen Produzenten und Konsument . . . . . . . . . . 208
14.1 14.2 14.3 14.4 14.5 14.6 14.7 14.8 14.9 14.10 14.11 14.12 14.13 14.14 14.15 14.16 14.17
Zustandsloses Objekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 Zustandsbehaftetes Objekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 Bedingter Zustands¨ ubergang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 Zustands¨ ubergangstabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 Aktionen im Zustandskontext . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 Zustandshierarchie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 ¨ Uberg¨ ange in der Zustandshierarchie . . . . . . . . . . . . . . . . . . . . . . 220 Default-Start- und End-Zust¨ ande . . . . . . . . . . . . . . . . . . . . . . . . . 220 Statechart mit Ged¨ achtnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Statechart mit tiefer Historie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Statechart mit Parallelismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Zustands¨ uberg¨ ange in Hierarchie . . . . . . . . . . . . . . . . . . . . . . . . . . 223 Beispiel eines Statecharts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Abge¨andertes Statechart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 Klassendiagramm des Zustandsmusters . . . . . . . . . . . . . . . . . . . . 233 Die FSM im Oberzustand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 Klassendiagramm von CFSM5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
15.1 15.2 15.3 15.4 15.5 15.6 15.7 15.8 15.9 15.10
MOST-Ring mit Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 Mehrere Function-Blocks in einem Ger¨at . . . . . . . . . . . . . . . . . . . 256 MOST-Funktionsaufruf, Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . 260 Beispiel einer DeviceID-Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . 260 Beispiel einiger FblockID-Tabellen . . . . . . . . . . . . . . . . . . . . . . . . 261 Verwendung der FktIDs am Beispiel eines Multimedia-Changers262 OPTypes f¨ ur Methoden und Eigenschaften . . . . . . . . . . . . . . . . . 262 Datentypen in MOST, aus [MOST] . . . . . . . . . . . . . . . . . . . . . . . . 263 Zentrale Registry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264 Beispiel einer Notifikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
18.1 18.2 18.3 18.4 18.5
Tasks der Headunit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 Klassendiagramm zu einer Komponente . . . . . . . . . . . . . . . . . . . . 289 Kontext-Header der Komponenten . . . . . . . . . . . . . . . . . . . . . . . . 291 Kontext einer Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 Eingebauter Watchdog-Mechanismus . . . . . . . . . . . . . . . . . . . . . . 304
Abbildungsverzeichnis XXIII
18.6 18.7 18.8 18.9
Tickservice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 Mechanismus zum Auffordern eines Zustandswechsels . . . . . . . . 307 Empfang eines Tick-Events, Timer-Manager und -Client . . . . . 317 Beispiel einer Komponente (logisches Tuner-Device) . . . . . . . . . 328
19.1 19.2
Main-Dispatcher-Komponente . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 MOST-Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333
20.1 20.2 20.3 20.4 20.5 20.6 20.7 20.8 20.9 20.10 20.11 20.12
Blockbild des logischen Ger¨ ats . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 Anforderungen der HMI an das logische Ger¨at . . . . . . . . . . . . . . 345 Zust¨ande des logischen Ger¨ ats . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 Daten-Container f¨ ur die HMI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349 Container f¨ ur Soll- und Ist-Daten . . . . . . . . . . . . . . . . . . . . . . . . . 350 Klassendiagramm des Datencontainers . . . . . . . . . . . . . . . . . . . . . 351 Struktur des Datencontainers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352 Adapter f¨ ur die HMI zum Lesen des Datencontainers . . . . . . . . 356 Versenden eines HMI-Befehls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357 Expertenwissen in der Ger¨ atesteuerung . . . . . . . . . . . . . . . . . . . . 359 Hierarchie der elementaren MOST-Datentypen . . . . . . . . . . . . . . 374 Vier Varianten von CModelInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . 380
21.1
HMI-Komponente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
1 Einleitung
Es gibt in der derzeitigen Fachliteratur einige Ausf¨ uhrungen u ¨ber EmbeddedSysteme. Ihre Schwerpunkte liegen oft auf den daf¨ ur verwendeten Betriebssystemen oder auf Applikationen in kompakter, nicht vernetzter Umgebung. Beide Autoren haben in den letzten Jahren die Einf¨ uhrung von modernen SW-Strukturen ins Auto miterlebt und mitgestaltet. Die Ausf¨ uhrungen beziehen sich deshalb im Folgenden beispielhaft auf vernetzte Systeme im Auto. Im Moment ist der europ¨aische Standard daf¨ ur ein optischer Bus, MOST-Bus genannt (media oriented system transport), [MOST]. Die Ausf¨ uhrungen sind aber allgemein genug, um auch f¨ ur andere Vernetzungen verwendbar zu sein. Das Buch ist in zwei Teile gegliedert. Teil 1: Welche Bausteine zu einem Framework geh¨oren, welche Dinge festgelegt werden m¨ ussen und welche Anforderungen an ein Framework gestellt werden, wird in diesem ersten Teil des Buches besprochen. Insbesondere der erste Teil l¨ asst sich auf viele Dom¨anen der Embedded Software anwenden. Da es vielen, auch erfahrenen SW-Ingenieuren nicht bekannt ist, wie sich ihre Programme im Speicher auswirken, beginnt der Teil 1 mit einigen Grundlagen dazu. Anschließend werden Grundkonzepte und Grundbausteine von Embedded Systemen er¨ ortert. Ein Schwerpunkt der Diskussion ist dabei das Speicherverhalten und die Startperformance. Grundmechanismen der Betriebssysteme werden erl¨autert und ausgemessen, um Speicherverwendung, Kommunikation und Synchronisationen effizient zu gestalten. Dabei wird darauf geachtet, dass Implementierungen unter Linux, QNX und Windows lauff¨ ahig sind. Dieser Teil ist sowohl an Studierende als auch an Embedded Experten gerichtet.
2
1 Einleitung
Teil 2: Im zweiten Teil des Buches wird eine prototypische objektorientierte Framework-Implementierung besprochen. Diese diente zur Erprobung besonders effizienter Synchronisations- und Kommunikationsans¨atze. Außerdem sollte das Framework besonders klein sein und gute Startzeiten haben. Die Vors¨atze sind in dem kleinen Rahmen recht gut gegl¨ uckt, ein Framework entsprechend der vorgestellten Erkenntnisse mit mittlerer Applikationsausstattung bewegte sich im Rahmen von 2,5 s Startzeit und einer Gr¨oße von einigen 100 kByte. Noch viel wichtiger betrug die Umsetzungszeit nur wenige Mannmonate. Neue Ideen In dieses Buch sind einige Konzepte eingeflossen, die den Autoren aus der Literatur noch nicht bekannt waren. Es sind dies: • Flexible, aber einheitliche Konfiguration f¨ ur Threads und Prozesse, • daf¨ ur intensive Nutzung des Shared-Memories in Verbindung mit dem fork-Konstrukt und von Thread-specific-Data; • Queue-Konstrukte mit Vorrichtungen gegen Priorit¨ats-Inversion; • Komponenten-Klassen mit festem Set von Queues und Services; • Hintergrundsysteme: Watchdog, Timer; • spezielle FSM-Architekturen f¨ ur Embedded Systeme; • Lauff¨ahigkeit auf QNX, Linux, Windows; • const-Objekte im Code-Segment zum Dekodieren der MOST-Nachrichten zur Performance-Optimierung. Eine Schwierigkeit bei der Erstellung war das Ziel, im Text verallgemeinert genug zu bleiben, um noch lesbar zu sein und doch gleichzeitig auch Quellcodes zu zeigen, damit sich der Leser ein substanzielles Bild machen kann, wie die Erkl¨arungen gemeint sind und umgesetzt werden k¨onnten. Ein Versuch, dieses Dilemma zu l¨ osen, war, die Quelltexte auf das N¨otigste zu k¨ urzen. S¨amtliche Includes wurden weggelassen. Nach dem Vorbild von [Stev] wurden alle wichtigen Includes in einen Abschnitt im Anhang verlagert. Weiterhin wurden viele Programmzeilen durch . . .“ ersetzt. ” Alle Sourcen laufen prinzipiell auf QNX, Linux und Windows. Allerdings wurden auch hier einige ifdefs herausgek¨ urzt, die n¨otig waren, um auf die Eigenheiten der Betriebssysteme einzugehen. Im Anhang finden sich daf¨ ur Hinweise. Ohne dass ein ganzes Framework in allen Einzelheiten vorgestellt wird, kann der Leser sicher manche Erkenntnisse aus den Beispielen gewinnen, sie umsetzen oder eigene Implementierungen an den vorgestellten Implementierungen messen. Unter dem Link www.fbi.fh-darmstadt.de/∼jwietzke/Das Buch k¨onnen viele Quellen angesehen und heruntergeladen werden. Auch ein aktueller Stand eines studentischen Framework-Projektes wird dort ab Juli 2005 zu finden sein.
1 Einleitung
3
Anmerkungen zur Dom¨ ane und zur Sprache: Ein Framework ist immer dom¨ anenspezifisch, auch viele SW-Muster (Pattern) sind dom¨anenspezifisch, in unseren Beispielen spezifisch f¨ ur Embedded Automotive Systeme. In jeder Dom¨ ane gibt es Fachbegriffe, die sich nur schwer u ¨bersetzen lassen. Wir werden auf die Eindeutschung von englischen Begriffen verzichten, ein Thread wird nicht als Laufzeitfaden u ¨bersetzt, sondern bleibt, nach einer inhaltlichen Erl¨auterung, ein Thread. Auch die Frage der Groß- oder Kleinschreibung englischer Begriffe, die eventuell im Deutschen schon bekannt sind, wird systematisch ignoriert. Wir w¨ ahlen, wann immer wir darauf achten, die Großschreibung oder die Kleinschreibung. Auch die Frage, welche Sprache in den Kommentaren der Implementierung gew¨ahlt wird, soll ohne Bedeutung sein, wesentlich ist nur das Vorhandensein hoffentlich aufschlussreicher Kommentare.
Teil I
Bausteine eines Embedded Frameworks
2 Ein Infotainment-System
Ein typisches so genanntes Infotainment-System im KFZ besteht aus einigen miteinander vernetzten Komponenten. In unseren Beispielen und Bildern gehen wir von einer MOST-Vernetzung aus. (Der MOST-Bus ist ein optischer Bus, der f¨ ur automobile Anwendungen definiert wurde. Die Details, die wir f¨ ur unsere Software-Betrachtungen brauchen, werden wir an passender Stelle er¨ ortern und verwenden bis dahin den MOST-Bus als Platzhalter f¨ ur irgendeine Vernetzung).
Abbildung 2.1. Beispiel eines Infotainment-Systems
Minimalumfang ist die Headunit, die als Bedieneinheit (HMI= human machine interface) f¨ ur das System und ggf. auch f¨ ur weitere Funktionalit¨aten des KFZ dient. Beispielsweise steuert sie auch Klimaanlage, Sitzverstellung und Diagnosefunktionen. Die Headunit ist die Kommandozentrale (HMI) des Multimedia-Systems. An ihr sind das Display und die meisten Bedienelemen-
8
2 Ein Infotainment-System
te angeschlossen. In einem vernetzten System u ¨bernimmt sie normalerweise auch die Masterfunktionen im Netz bez¨ uglich On/Off, Audiokanalvergabe, Display-Vergabe und die Priorit¨ atsvergabe von Ressourcen. Sie befindet sich sichtbar im Armaturenbrett mit einem Display und einigen Bedienschaltern und beinhaltet den Hauptrechner, welcher die Masterfunktion im Netzwerk hat. Zum System selbst geh¨ oren je nach Ausstattungsgrad: Die Headunit Gateway Audiokomponenten
Display, Bedienelemente, Netzwerkmaster Br¨ ucke zu Kfz-Bussystemen wie CAN Tuner, Amplifier, Soundsystem, Kopfh¨ orerverst¨arker, CD-Laufwerk, CD-Wechsler, DVD-Laufwerk, Kassettenlaufwerk Videokomponenten Video-/DVD-Laufwerk, TV-Tuner andere Komponenten Navigation mit Sensorik wie GPS, Radimpulse, R¨ uckw¨artsgang Kommunikationskomponenten Telefon/Telematik-Box Rear Seat Entertainment Unterhaltung f¨ ur Fonds-Passagiere, zweite Slave-Headunit
2.1 Die Headunit In der Headunit werden alle Funktionen des Netzwerkes gesteuert. Sie ist gleichzeitig der Bedienknoten f¨ ur den Benutzer. F¨ahigkeiten, die einzelne angeschlossene Ger¨ate haben, k¨ onnen in der Headunit miteinander zu neuen Funktionen kombiniert werden. Die Hinterleuchtung des Displays kann vom Fahrlicht abh¨angen, Fenster k¨ onnen geschlossen werden, wenn das Telefon klingelt, abh¨angig vom Z¨ undschl¨ ussel k¨ onnen Nutzerprofile mit unterschiedlichen Bediensprachen, Sitzeinstellungen und Sender-Favoritenlisten angeboten werden. Ersichtlich ist die Headunit der Netzwerkknoten mit der h¨ochsten Funktionalit¨ at und Komplexit¨ at im Netzwerk, eine gew¨ ahlte Software-Architektur muss sich mit vielen Eigenheiten der angeschlossenen Ger¨ate auseinandersetzen. Hinzu kommt, dass die Hardware im Wesentlichen einem Embedded System entspricht, das heißt vor allem, dass Speicher knapp und Rechenleistung nicht allzu hoch sind. Da immer kritische Ruhe- und/oder Betriebsstromforderungen gestellt werden, sind die Hardware-Kenngr¨oßen eine bis zwei Generationen hinter den allgemeinen PC-Trends. Zudem sind die Systeme nicht immer in ihrer Software nachladbar und m¨ ussen ca. 20 Jahre schadlos u ¨berdauern und wartbar bleiben. Ein wenn auch kleiner Teil der Embedded Anforderungen an die Software beinhaltet Echtzeit-Anforderungen. Protokoll-Zeiten im MOST-System
2.2 Ger¨ ate/Devices im System
9
m¨ ussen eingehalten werden, Positionsgenauigkeiten der Navigation, Reaktionszeiten in der Bedienung und schnelle Startzeiten der Systeme (2 sec).
Abbildung 2.2. Beispiel eines Funktions-Blockbilds einer Headunit
2.2 Ger¨ ate/Devices im System Devices sind Ger¨ ate mit abgegrenzter Funktion. Wir werden sp¨ater sehen, dass jedes Device einen eigenen Namen im Netzwerk hat, beispielsweise sind Tuner und Amplifier einzelne Devices, auch wenn sie sich tats¨achlich im gleichen Geh¨ause auf der gleichen Hardware befinden k¨onnen. Sie sind normalerweise u ¨ber eine Vernetzung an der Headunit angeschlossen, durch die heutige Hochintegration gibt es aber auch Devices, die im selben Geh¨ause wie die eigentliche Headunit verbaut sind. Es gibt die Unterscheidung in interne Devices und externe, d.h. vernetzte Devices. Wir unterscheiden weiterhin in MOST- und non-MOST-Devices, also Devices, die dem Vernetzungsprotokoll folgen, und solche, die andere, normalerweise low-level APIs (Application Programming Interface) haben. Ein u ¨ber das MOST-System angeschlossener DVD-Player w¨are demnach ein externes
10
2 Ein Infotainment-System
MOST-Device; die in der Headunit integrierte Navigation w¨are ein internes MOST-Device, der DVD-Mediaplayer, der u ¨ber ein ATAPI-Interface angesprochen wird, w¨ are ein internes non-MOST-Device.
2.3 Begriffskl¨ arung: Was ist ein Framework Ein Framework ist normalerweise nicht explizit n¨otig bei kleinen Arbeitsgruppen, die ein einmaliges Projekt ohne geplante Wiederverwendung realisieren. Alle Regeln, Schnittstellen und Abstraktionen geschehen auf Zuruf, Dokumentation ist eher schwach ausgepr¨ agt. Sobald gr¨oßere Gruppen, eventuell sogar verteilt u ¨ber Standorte, an einem oder mehreren Projekten arbeiten, m¨ ussen Regeln und L¨osungen festgelegt werden, die im Framework niedergelegt werden. Das Framework legt fest: • • • • • • • • • • • • • • • • •
Design Rules, Namenskonventionen, do’s und don’ts; die Definition der Komponenten als Threads oder Prozesse; die Kommunikation zwischen Komponenten; Speicherdefinitionen, viele effiziente Mechanismen f¨ ur Embedded Systeme; Kapselung des Expertenwissens bez¨ uglich Vernetzung, OS-Spezialit¨aten, Prozessverwaltung, Prozesskommunikation, On/Off . . . und damit Trennung von Schnittstellen und Implementierungen; Kapselung und Trennung von Information/Daten/Zust¨anden und algorithmischer Implementierung; Hardware-Abstraktionen; OS-Abstraktionen; Portabilit¨atsdefinition; Basisklassen und Patterns f¨ ur Strukturen, Controller, Proxies, Treiber, Anzeigen; Header, Interfaces, Beispiele; Verwendbarkeit: Wartbarkeit, Erweiterbarkeit, Restrukturierbarkeit, Portabilit¨at; Interoperabilit¨ at; Effizienz; Testbarkeit; Wiederverwendbarkeit; das Framework legt Performance und Ressourcen grunds¨atzlich fest, Fehler, die hier gemacht werden, k¨ onnen durch sp¨atere Korrekturen im Handwerk nicht mehr verbessert werden. Normalerweise werden solche Fehler erst kurz vor Abschluss eines Projektes entdeckt und k¨onnen nicht mehr ¨ zufriedenstellend beseitigt werden. Ublicherweise haben Kunden in Folgeprojekten kein Verst¨ andnis f¨ ur eine Alles-neu-Entwicklung“, weshalb sich ” die Fehler durch mehrere Generationen fortpflanzen.
2.4 Fokus unserer Framework-Betrachtungen
11
In Erg¨anzung zum Framework (und hier von nicht scharf unterscheidbar) wird normalerweise eine Architektur definiert, in der z.B. die Partitionierung in einzelne Baugruppen, Komponenten und Aufgaben festgelegt wird. Ein Framework ist ein nur teilweise komplettes Fundament, das f¨ ur ein konkretes Projekt abgeleitet und konkretisiert wird. Diese Konkretisierungen sind die projektvarianten Teile (Hot Spots), wie zum Beispiel: Bedienphilosophien und Darstellungseigenarten, konkrete Speicherpartitionierungen und Hardware-Anpassungen. In anderen Teilen ist Expertenwissen u ¨ber Abl¨aufe, Protokolle und Performance implementiert und gekapselt (Frozen Spots), diese Teile sind projektinvariant. Es gibt in der Literatur verschiedene Prinzipien f¨ ur Frameworks, die auch kombiniert werden k¨ onnen. • Vererbung: Basisklassen-Framework. Die Aufgabenstellungen sind vorher klar, virtuelle Methoden, die vom Applikateur ausprogrammiert werden m¨ ussen. • Plug-In: Variante Methoden u ¨ber Schnittstellen. • Templates: Kompliziert, bedingt debugf¨ ahig durch neue Compiler, Erlernen eigener Syntax und Semantik erforderlich. • Codegeneratoren: Vereinfachung der Templates durch lesbare Beschreibungen, z.B. in XML. Aber: Statische Source-Code-Generierung, keine Interpretation zur Laufzeit. Die letzte Variante liegt diesem Buch zu Grunde, etwa die H¨alfte des vorgestellten Anwendungscodes wird mit einem Codegenerator erzeugt. Dennoch wird u ¨ber Code-Generierung nur sehr wenig zu lesen sein, da es gute Tradition ist, ein Konzept und eine Implementierung ein erstes Mal per Kopf und Hand zu verstehen und zu implementieren, dabei zu lernen und erst dann die entsprechenden Generatoren zu erfinden. Die Codegenerierung k¨onnte einmal Teil des Inhalts eines folgenden Bandes sein. Die dahinter liegende Codegenerierung erkl¨art (und entschuldigt hoffentlich) aber auch die verteilten Informationen, die im Zusammenhang mit dem Hinzuf¨ ugen neuer Komponenten bearbeitet werden m¨ ussen.
2.4 Fokus unserer Framework-Betrachtungen Wenn wir im Folgenden u uste ¨ber Frameworks sprechen, so sind damit SW-Ger¨ f¨ ur eine umfangreiche Headunit-Funktionalit¨ at gemeint. Eine Headunit ist die Bedieneinheit im Armaturenbrett, von der aus vielf¨altige InfotainmentFunktionen wie Radio, Navigation, Telefon, Sound, TV, Internet, Sprachbedienung etc. bedient und gesteuert werden. Auch KFZ-Funktionen wie Klimaanlage, Schließsysteme, Diagnose etc. k¨ onnen Teil des Steuerumfangs sein.
12
2 Ein Infotainment-System
Nat¨ urlich k¨onnen gr¨ oßere Teile eines solchen Frameworks auch in Einzelkomponenten im Fahrzeug verwendet werden, die umfassendsten Anforderungen an Parallelit¨ at, Speicher, Darstellung und Bedienung stellen sich aber in der Headunit, weshalb wir ein Framework von hier aus entwickeln.
3 Anforderungen an ein Embedded Automotive Framework
Es gibt die u ¨blichen Anforderungen an Erweiterbarkeit, Robustheit, aber auch Anforderungen an die verteilte Entwicklung, die oft durch Zuk¨aufe von Kompetenzfirmen entsteht.
3.1 Verteilte Entwicklung Wie bereits erw¨ ahnt, sind bei u ¨blichen Projekten die Entwicklergruppen u ur ¨ber mehrere Standorte verteilt. Historisch bedingt gibt es Standorte f¨ Navigations-Systeme, andere sind spezialisiert f¨ ur Sprachbedienung oder f¨ ur Systemintegration. Zumeist sind in Automotiv-Systemen auch mehrere Lieferanten beteiligt. Selbst wenn alle Expertengruppen an einem Standort arbeiten, reicht schon der Abstand von einigen 10 Metern oder Geschossen, um von einer verteilten Entwicklung sprechen zu m¨ ussen. Ein Framework muss also Designregeln, Ressourcenverteilung und anderes so festlegen, dass nicht st¨andige Kommunikation erforderlich ist.
3.2 MVC, Organisationen Software-Architekturen werden oft unbewusst so entwickelt, dass sie ein Abbild der Organisation sind, die mit ihnen arbeiten muss. Beispielsweise werden Prozesse so verteilt, dass jede Abteilung ihren eigenen Prozess hat, mit klaren Abteilungsschnittstellen nach außen. Dies ist sicher kein sinnvoller Ansatz bei der Suche nach der technisch besten L¨ osung. Andererseits muss große Software so zerlegt werden, dass sie in Modulen und in Arbeitsgruppen erarbeitet werden kann. Eine sinnvolle Aufteilung ist die Zerlegung in Schichten. Eine protokollnahe Schicht kapselt das Wissen, das f¨ ur die Kommunikation zwischen Ger¨aten erforderlich ist, das Kommunikationswissen.
14
3 Anforderungen an ein Embedded Automotive Framework
Eine dar¨ uber liegende Schicht verarbeitet die Eingangsanregungen aus der Kommunikation und bereitet Ausgaben auf. Es kapselt das Ger¨ atewissen und implementiert die eigentliche Ger¨ atesteuerung. Diese Schicht ist weitgehend projektinvariant, solange nicht die Hardware ge¨andert wird. In dem bekannten MVC-Pattern [Bus] w¨ are das die Modell-Schicht. Dar¨ uber stellt eine Schicht die Bedienfunktionalit¨at des Systems und deren Darstellung zur Verf¨ ugung. Hier wird System- und Bedienwissen gekapselt, wann welches Men¨ u zur Verf¨ ugung steht und welche Daten angezeigt werden. Man spricht hier von der Controller-Schicht, die projektvariant ist. Schließlich gibt es eine Darstellungsschicht, in der die Art der Datenanzeige definiert ist. Auch kleine Animationen werden hier lokal realisiert. Diese Schicht, im MVC-Pattern View genannt, ist der varianteste Teil in einem ¨ Projekt und unterliegt st¨ andiger Anderungen durch Kunden und Vorgesetz¨ te. Es ist deshalb sehr sinnvoll, die Anderungen hier lokal vorzunehmen und m¨oglichst andere Schichten nicht anzutasten.
Abbildung 3.1. MVC-Architektur am Beispiel Tuner
Ersichtlich nimmt die projektabh¨ angige Varianz von oben nach unten ab. Normalerweise wird nach einmaliger Festlegung innerhalb eines Projektes nicht mehr viel an den Bedienfolgen ver¨ andert und nur noch ganz wenig an den Komponenten selbst, sofern die HW konstant bleibt. Diese Architektur wird sp¨ ater noch erweitert, da wir es mit vernetzten Systemen zu tun haben, in denen das eigentliche Ger¨at u ¨ber eine zeitverz¨ogernde Vernetzung angeschlossen ist und durch lokale Stellvertreter (Proxies) ersetzt werden muss. Dies wird sp¨ ater im Realisierungsteil noch genauer besprochen.
3.4 Startzeiten
15
3.3 Speicheranforderungen Im Gegensatz zur PC-Welt ist der Speicher in der Embedded Welt knapp. Speichergr¨oßen von 32 Mbyte RAM und 16 Mbyte Flash sind typisch und schon komfortabel. Es gibt normalerweise keine Hard-Disk oder andere Massenspeicher. Mit einem solch knappen Speicherplatz und einer Vielzahl von geforderten Features muss das Framework Vorgaben zur Speicherorganisation machen, so dass nicht jeder Entwickler alle Tricks der sparsamen Speicherorganisation kennen muss. Auch das Thema Speicherfragmentierung, das in der PC-Welt keine große Rolle spielt, hat f¨ ur automotive Systeme, die auch Dauerl¨aufe u ussen, Relevanz1 . Tabu ist dabei zurzeit noch eine ¨ber Wochen u ¨berstehen m¨ Garbage-Collection“, die die Zeitanforderungen an das System st¨oren w¨ urde. ”
3.4 Startzeiten Die Vorgabe lautet hier zurzeit 2 Sekunden vom Anlegen der Betriebsspannung bis zum vollst¨ andigen Funktionieren der Schl¨ usselfunktionen Ton (Audio), Bild (Display) und Bedienbarkeit. Hintergrund ist der sicherheitsrelevante Parkdistanzwarner, der sp¨ atestens 2 Sekunden nach Start des Motors funktionieren und Hindernisse akustisch und optisch auf dem System melden muss. ¨ Vorstartkonzepte, die das System schon beim Offnen der T¨ uren starten, helfen hier nicht wirklich, da auch nach einem Start aus einem UnterspannungsReset (Winterbetrieb bei schwacher Batterie) oder wenn der Fahrer nach einer Schlafpause bereits im Auto sitzend startet, das System nach 2 Sekunden funktionieren muss. ¨ Ublicherweise ist eine Embedded Hardware so ausgelegt, dass Code- und Datensegmente aus einem Flash-Speicher in einen Arbeitsspeicher (RAM) entpackt und kopiert werden. Anschließend wird aus dem RAM gestartet (boot). Es gibt erste Vorschl¨ age, diese Images so zu generieren, dass sie bereits einen Zustand im oder nach dem Booten darstellen, um so Startzeiten zu verk¨ urzen. 1
Ein anschauliches Beispiel f¨ ur Speicherfragmentierung ist ein Parkplatz f¨ ur PKWs. Jeder hat sich schon dar¨ uber ge¨ argert, dass eigentlich noch Platz f¨ ur das eigene Auto w¨ are, h¨ atten nicht Vorg¨ anger so dusselig“ geparkt, dass die L¨ ucken ” gerade so eng sind, dass kein Auto mehr dazwischen passt. In Summe w¨ are bei geschickterem Parken noch Platz f¨ ur einige Autos. Oft liegt diese Parkplatzfragmentierung nicht an der fehlenden Kompetenz der Fahrer, sondern einfach nur an der Vorgeschichte und verschieden breiten Autos.
16
3 Anforderungen an ein Embedded Automotive Framework
3.5 Menu ¨ wechselzeiten Erfahrungsgem¨aß werden Men¨ uwechselzeiten von 50 Millisekunden akzeptiert. L¨ angere Zeiten werden vom Kunden als Performance-Problem des Systems gedeutet.
3.6 Konfiguration versus Dynamik Ein großes Thema der letzten Jahre war die Dynamik in Automotive Systemen. Die Vorstellung eines Plug&Play im Auto war bestechend. Am besten noch u ¨ber Ger¨ate verschiedener Lieferanten hinweg. Daraus entstanden Konzepte, die in der Startphase die Eigenschaften der angeschlossenen Ger¨ate abfragten und registrierten. Um den Busverkehr im Betrieb zu reduzieren, wurden Notifizierungsmechanismen implementiert. Die Erfahrungen zeigen aber, dass diese Dynamik zwischen Komponenten oft nicht beherrscht wird, speziell in Fehlerf¨allen zwischen den Komponenten gibt es bis heute Probleme, die erst durch Neustart zu beheben sind. Kurzfristig ist deshalb die statische Konfiguration der Systeme vorzuziehen, dies spart Startzeit, ist in Fehlerf¨ allen unkritischer und generell weniger komplex. Es hat sich auch gezeigt, dass die urspr¨ unglich geforderte hohe Flexibilit¨at am Ende ohnehin nicht an den Kunden weitergegeben wurde.
3.7 Datenfluss/Kontrollfluss/Zustandssteuerung Automotive Infotainment Systeme sind hochgradig zustandsorientiert. Die meisten Abl¨aufe lassen sich gut mit Zustandsautomaten oder Sequenzdiagrammen beschreiben. Die wenigen Abl¨ aufe, die von Daten (zum Beispiel Feldst¨arken) abh¨ angen, lassen sich durch Entscheidungsschwellen auf Zust¨ande abbilden. Wir werden uns daher in einem sp¨ ateren Kapitel ausf¨ uhrlicher mit den Konzepten von Zustandsautomaten befassen.
4 Betriebssysteme
In fr¨ uhen Embedded Systemen war kein Betriebssystem notwendig. Das Ger¨at basierte auf einer einfachen Hauptschleife, die nacheinander Bedienelemente abfragte und entsprechende Ger¨ atefunktionen ansteuerte. Wegen zunehmender Funktionalit¨ aten, sich ¨ andernder Hardware-Plattformen und wegen der hohen Anforderungen an gef¨ uhlte Bedienbarkeit kam man bereits fr¨ uh zur Notwendigkeit von Betriebssystemen, die eine scheinbare Parallelit¨at erlaubten, so dass nun, wenn z.B. in einer Applikation auf eine Tastatureingabe gewartet wird, eine andere inzwischen weiterarbeiten kann. Betriebssysteme organisieren die • • • •
quasi parallele Abarbeitung verschiedener Prozesse oder Threads; sie verwalten Hardware- und Software-Ressourcen; sie kapseln und abstrahieren Hardware-Funktionen und Schnittstellen; sie stellen zum Teil standardisierte Services zur Verf¨ ugung, wie z.B. Speicherverwaltung, Kommunikations-Schnittstellen, Lader, Fehlerroutinen, Synchronisations- und Kommunikationsmethoden.
Im Zusammenhang mit der Framework-Diskussion im automotiven Umfeld sind gleichzeitig einige Betriebssysteme in der Diskussion. Diese Verkn¨ upfung ist zun¨achst einmal ungl¨ ucksselig, da die Begriffe Framework und Betriebssystem eigentlich verschiedene Schichten betreffen. Durch das Paket WinCE, das Betriebssystem und Framework fast untrennbar verbindet, kommt es zur Vermischung der Begriffe. Auch ein GUI-Builder (Graphical User Interface) wie Photon f¨ ur QNX vermischt Framework und OS-Schichten. Trotzdem sollten die weitgehend u ¨blichen Betriebssysteme erw¨ahnt werden. Es sind: VxWorks, das sehr erprobt und geeignet in der kleinen Multithread- Umgebung ist, f¨ ur das es aber noch nicht viel Erfahrung in Multiprozess-Systemen gibt. In a¨hnlicher Weise ist I-Tron einzuordnen, das sehr h¨aufig in japanischen Systemen eingesetzt wird.
18
4 Betriebssysteme
Mit m¨achtigeren Prozessoren wie dem SH4 von Hitachi und dem PowerPC von Motorola und anderen kommen die M¨oglichkeit und die Forderung nach Multiprozess-OS auf. Hier greift im Moment QNX, dessen Entwicklungsumgebung (Momentics) allerdings speziell bei großen Projekten viele Schw¨achen hat. Die St¨arken von QNX sind die Echtzeitorientierung und die POSIX-Konformit¨at. WinCE kommt immer wieder in die Diskussion mit der Vorstellung eines Standards, genauso oft stellt sich aber auch heraus, dass die Verquickung von Framework und Betriebssystem bei WinCE von den Entwicklern nicht geliebt wird. Auch sind die Einschr¨ ankungen im Framework noch so groß, dass am Ende im Wesentlichen nur der OS-Teil genutzt und ein eigenes Framework drumherum implementiert wird. Daf¨ ur ist es dann aber zu aufw¨andig und teuer. Die richtige Design-Entscheidung scheint deshalb hier zu sein, sich vom OS zu abstrahieren und mehrere Betriebssysteme zu unterst¨ utzen. Die vorgestellten Implementierungen werden deshalb in der Regel auf VxWorks, QNX und Windows lauff¨ ahig sein, dies zwingt auch wieder dazu, sich nicht zu sehr auf Spezialit¨aten nur eines Betriebssystems einzulassen. In sp¨ateren Kapiteln wird gezeigt, wie Programme wahlweise unter QNX, Windows und Linux laufen k¨ onnen, solange man sich bei der Auswahl der Betriebssystem-Mittel beschr¨ ankt. Deshalb werden wir hier auf Betriebssysteme nicht weiter eingehen, nur drei notwendige Begriffe kurz erl¨ autern und sp¨ater in den Implementierungsbeispielen von Betriebssystem-Funktionalit¨aten einfach nur ohne weitere Erl¨auterung Gebrauch machen.
4.1 Prozesse, Tasks, Threads Die Literatur unterscheidet nicht scharf zwischen Prozessen und Tasks. Beide Begriffe beschreiben den Ablauf eines Programms auf einem quasi eigenen Prozessor. Es gibt Lesarten, nach denen ein Prozess ein Ablauf auf einem idealen Prozessor ist, d.h. Prozesse haben virtuell unendlich viel Speicher und ihr Speicher ist gut gegen andere Prozesse gesch¨ utzt. Eine Memory Management Unit (MMU) wird eingesetzt und kann bei Bedarf weiteren Arbeitsspeicher zuteilen (mappen). Demgegen¨ uber setzen Multitask-Systeme nur einfache MMUs ein, ihre Speicherbereiche sind zwar gegeneinander gesch¨ utzt, es wird jedoch kein Speicher umgemapped. Da diese Unterschiede nicht klar definiert sind und der Gebrauch der Begriffe fließend ist, werden wir den Begriff Task f¨ ur abgegrenzte Aufgaben verwenden, die als Prozess oder als Thread implementiert sein k¨onnen. Der Begriff Prozess wird definiert als oben beschriebener Ablauf in eigenem Speicher.
4.2 Der Scheduler
19
Threads sind ebenfalls eigene Ablauff¨ aden auf einem virtuell eigenen Prozessor, allerdings teilen sich Threads einen gemeinsamen Speicher. Dies wird in einem sp¨ateren Kapitel noch n¨ aher erl¨ autert. Alle drei Begriffe beschreiben Programmabl¨aufe, die zeitlich schnell abwechselnd von einem Betriebssystem-Scheduler organisiert werden, so dass es nach außen erscheint, als liefen die Programme quasi parallel.
4.2 Der Scheduler Der Scheduler organisiert, in welcher Abfolge und f¨ ur wie lange verschiedene Aufgaben quasi parallel abgearbeitet werden. Er teilt Rechenzeit zu und verwaltet die Zust¨ ande (aktiv, blockiert, bereit) der Prozesse. Die Prozesse haben nach ihrer Wichtigkeit unterschiedliche Priorit¨aten, die beim Start eines Prozesses festgelegt werden. Interrupt-Routinen, die HW-Interrupts bedienen, werden nicht vom Scheduler bedient, sondern direkt durch priorit¨ atsgesteuerte Hardware. 4.2.1 Pr¨ aemptive Priorit¨ atssteuerung Es gibt verschiedene Strategien f¨ ur das Scheduling, normalerweise wird f¨ ur Tasks verschiedener Priorit¨ at pr¨ aemptiv und priorit¨atsgesteuert vorgegangen, d.h. ein Task h¨oherer Priorit¨ at l¨ ost, wenn er bereit ist, sofort einen laufenden Task niedrigerer Priorit¨ at ab und versetzt diesen in den Bereit-Zustand. 4.2.2 Round-Robin Normalerweise werden Tasks gleicher Priorit¨ at abwechselnd mit gleichen Zeitschlitzen (im Kreis) bedient. Dieses Prinzip ist meistens als Default eingestellt und nennt sich Round-Robin. Eine andere Variante w¨ are FIFO-Scheduling, bei dem der Task, der zuerst aktiv wurde, solange am Zug“ bleibt, bis er selbst freiwillig abgibt, z.B. durch ” Aufruf von sleep(0). Diese Scheduling-Policy erfordert kooperative Programmierung zwischen Tasks und sehr viel Einsicht in m¨ ogliche Abh¨ angigkeiten und Blockaden. Deshalb wird u ¨blicherweise Round-Robin bevorzugt. Diese Strategie ließe sich auch f¨ ur Tasks mit unterschiedlicher Priorit¨at einsetzen, die Abl¨ aufe w¨ aren dann streng geordnet und determiniert. Allerdings k¨onnten dann zeitkritische Aufgaben eventuell nicht schnell genug an die Reihe kommen (Echtzeitanforderung). 4.2.3 Priorit¨ ats-Inversion, Priorit¨ ats-Vererbung Es kann passieren, dass ein hochpriorer Prozess oder Thread blockiert wird und auf einen niedrigprioren warten muss, dieser aber nicht gescheduled wird,
20
4 Betriebssysteme
da er ja niedrige Priorit¨ at hat und gerade ein Task mittlerer Priorit¨at aktiv ist. Damit w¨ urde ein mittelpriorer Task einen hochprioren blockieren (Priorit¨ats-Inversion), was der Bedeutung der Priorit¨aten widerspr¨ache. Wartet ein Task auf eine Nachricht oder auf die Freigabe eines Synchronisationsobjekts eines anderen, so erbt dieser die Priorit¨at des Wartenden. Damit kann Priorit¨ ats-Inversion vermieden werden. Man nennt diesen Mechanismus im Englischen Priority-Inheritance. Wir werden diesen Abschnitt beim Kapitel Synchronisierungsmechanismen wiederholen und besser verstehen. 4.2.4 Task-Switch, Kontextwechsel Aktiviert der Scheduler den n¨ achsten Task, so erfolgt ein Task-Switch. Hierbei wird der alte Zustand des Prozessors mit allen Registerwerten gespeichert und der Zustand des neuen Tasks geholt. Auch der Sprung in eine InterruptRoutine und der Aufruf von System-Routinen im Kernel beinhalten einen Kontext-Switch.
4.3 Zusammenspiel mit der MMU Es ist zwar ein Thema der Hardware, da die MMU (memory management unit) aber f¨ ur Multi-Prozess-Systeme eine Rolle spielt, sei sie hier kurz erw¨ahnt. Eine MMU setzt logische Adressen des Prozessors in physikalische des Speichers um. Sie mapped daf¨ ur Speicherkacheln, die z.B. 4 KByte groß sind. Der virtuelle, logische Adressraum ist gr¨ oßer als der tats¨achliche physikalische. Die CPU bekommt einen ideal großen Speicher vorgespielt, in dem die MMU nicht gebrauchte Kacheln ummappen kann oder noch relevante Kacheln auf Massenspeicher auslagert (swapping). Weiterhin h¨alt die MMU Attribute der Kacheln, Informationen wie Codeoder Daten-Page, schreibbar, lesbar, ausf¨ uhrbar, User Mode, Privileged Mode. Wird versucht, diese Attribute zu verletzen, l¨ ost die MMU eine Exception, ein Signal oder einen Interrupt beim Prozessor aus. Es gibt einige Betriebssysteme, die nur die Schutzattribute der MMU verwenden, nicht aber die virtuelle Adressierbarkeit. Mit performanten MMUs kann das Problem der m¨oglichen Speicherfragmentierung reduziert werden, da sie freie Kacheln logisch neu zusammenf¨ ugen k¨ onnen.
5 Design-Rules und Konventionen
In der Literatur werden Design-Rules nicht zum Framework gez¨ahlt. Die Praxis zeigt aber, dass eine der ersten Maßnahmen f¨ ur eine funktionierende Teamarbeit und auch f¨ ur die Erstellung eines SW-Frameworks gemeinsam erstellte und akzeptierte Design-Regeln sind. Diese umfassen eine gr¨oßere Sammlung von Namenskonventionen, Dokumentationsregeln, do’s“ und don’ts“ und ” ” ggf. einige zeitliche Randbedingungen f¨ ur Software-Module. Auch der Umgang mit Priorit¨ aten kann in Design-Rules festgelegt sein. Ein kleiner Ausschnitt aus Konventionen zur Diskussion siehe unten: • • • • • • • • • • • • • • •
Interfaces statt Mehrfachvererbung; alle Klassen Pr¨ afix ”C”, alle Interfaces Pr¨ afix ”I”; Attribute: Prefix member ”m”, static ”s”; in Lib-Namen werden die Teams genannt, z.B. Standorte; Methoden mit beschreibendem Verb im ersten Teil des Namens; Ersatz HW abh¨ angiger Datentypen durch eigene Definitionen; Makros f¨ ur Speichervergabe; Vorschriften f¨ ur Assertions, Exceptions und Traces; eigenen Company Namen in alle Klassennamen einbauen; Kopierkonstruktor durch eigene private Deklaration blockieren, ohne Implementierung. Damit kommt es zum Compilerfehler, sollte man den Kopierkonstruktor implizit oder gewollt verwenden. Dito f¨ ur Zuweisungsoperator; ggf. nur asynchrone Kommunikation, asymmetrische Kommunikation; keine Speicherdynamik; abstrakte Klassen; keine Vererbung tiefer als 2 Stufen.
Eine weitere sehr n¨ utzliche Festlegung ist die maximal erlaubte Komplexit¨at eines Moduls, die mit Tools gemessen werden kann.
22
5 Design-Rules und Konventionen
5.1 Namenskonventionen Mindestens bei den Namenskonventionen kann man auf Standards zur¨ uckgreifen, z.B. [Mis]. Es gibt auch schon Tools, die die Einhaltung u berpr¨ u fen. ¨ F¨ ur uns ist f¨ ur die Quelltexte wichtig, dass Basisklassen mit CNameDer” Klasse“ beschrieben werden, abstrakte Klassen mit ANameDerKlasse“ und ” Interface-Klassen mit INameDerKlasse“. ”
5.2 Shared-Verzeichnisse, Libraries Immer wenn es mehrere Projekte gibt, kann man gemeinsame Teile definieren oder finden. Wenn alles optimal verl¨ auft, gibt es bereits ein gemeinsames Framework noch bevor die einzelnen Projekte angefangen werden. Diese Sammlung von Klassen, Werkzeugen, gemeinsamen Utilities und von gemeinsam verwendbaren Programmteilen wird oft in einem Quell-Verzeichnis abgelegt, das man Shared nennt und das von einer separaten Gruppe gepflegt wird. Die Projektgruppen stellen eigenen projektspezifischen Code in diesem Rahmen ein. Ein weiterer zumindest teilweise invarianter Teil der Software sind Treiber und Zusatz-Libraries. Diese werden auf Object-Level den Projekten zur Verf¨ ugung gestellt. Die Organisation dieser Libraries f¨ ur alle wird im Management oft u ¨bersehen, kleine Hardware-Unterschiede in den Projekten verleiten auch dazu, den f¨ unften universellen und generischen CD-Treiber“ zu schrei” ben.
5.3 Eigene Definitionen im Shared Bei C/C++ h¨angt es bei elementaren Datentypen vom Compiler und vom Ziel-Prozessor ab, wie viel Speicher jeweils verwendet wird. So kann ein Integer-Typ 16 oder 32 Bit haben. Soll ein Programm unabh¨angig von Compiler und Hardware sein, muss auf Little- und Big-Endian R¨ ucksicht genommen werden. Mit eigenen elementaren Datentypen kann man sich im eigentlichen Code unabh¨angig von Compilern und Prozessoren machen und somit den Code portierbar halten. Diese Datentypen werden in einem so genannten SharedBereich z.B. als "Global.h" gehalten und gepflegt. 5.3.1 Standard-Typen Zun¨achst werden alle Standardtypen selbst definiert, um von Prozessoren und Compilern unabh¨ angig zu werden.
5.3 Eigene Definitionen im Shared
23
Wenn m¨oglich sollten die Regeln auch nur signed Standardtypen erlauben. Oft wird unsigned vereinbart und dann doch aus Versehen ein (-1) als Returnwert im Fehlerfall u ¨bergeben. Z.B. mit dem sp¨ ater vorgestellten MOST-Protokoll ist unsigned nicht vermeidbar, weil es signed und unsigned Datentypen in der Protokoll-Definition gibt. Anschließend werden ein eigener NULLPTR und ein eigener NULLCHAR festgelegt, deren Verwendung nur zur Klarheit dient.
// synonyme for elementar data types typedef signed char Int8; typedef unsigned char UInt8; typedef signed short Int16; typedef unsigned short UInt16; typedef signed int Int32; typedef unsigned int UInt32; // Definition f¨ ur nullpointer und null character #define NULLPTR 0 #define NULLCHAR 0 Listing 5-1. Standard-Typen und Konstanten, Global.h
5.3.2 Alignment Es wird an vielen Stellen in der Implementierung Alignment-Aufgaben geben. Bei einem 32-Bit Prozessor z.B. m¨ ussen Variablen auf Doppelwortgrenze ausgerichtet sein, damit er mit einem 32-Bit Zugriff auf einen kompletten Integer-Wert zugreifen kann. Ist die Variable nicht ausgerichtet, so gibt es entweder einen Memory-Fault oder der Prozessor muss den Zugriff in mehrere Einzelzugriffe zerlegen und den Wert zusammensetzen. Bei einem 16-Bit Prozessor erfolgt die Ausrichtung normalerweise auf Wortgrenze. Andererseits gibt es 32-Bit Prozessoren, die sogar Byte-weise den Speicher adressieren k¨ onnen. Um von diesen Fragen unabh¨angig zu werden, definieren wir ein eigenes Makro, das die Gr¨oße eines Speicherblocks auf 32-Bit Doppelwortgrenzen ausrichtet.
// Macro used to round up the memory required due to the alignment #define MAKE_ALIGNMENT_SIZE(size) ((( (size) + (sizeof(Int32) - 1) )\ / sizeof(Int32))*sizeof(Int32)) Listing 5-2. Alignment-Makro
24
5 Design-Rules und Konventionen
5.3.3 Little-Endian, Big-Endian Bei der Interpretation einer Integer-Zahl ’1234’ ist zu unterscheiden, ob das niederwertige Byte an der h¨ ochsten Adresse ( Big-Endian“, z.B. Apple) oder ” an der niedrigsten Adresse ( Little-Endian“, z.B. PC) gespeichert wird. Die ” gleiche Frage stellt sich wieder bei 32-Bit Integers, deren niederwertiges Wort an der h¨oheren Adresse und das h¨ oherwertige Wort an der niedrigeren Adresse gespeichert werden.
Abbildung 5.1. Big/Little-Endian
Insbesondere dann, wenn Daten u ¨ber externe Schnittstellen gelesen werden, muss das Format klar sein. Auch wenn Software portierbar bleiben soll, muss das Speicherformat bekannt und anpassbar sein. H¨aufige Fehler sind die Byte-Adressierung innerhalb eines Doppelwortes ohne Ber¨ ucksichtigung des Endian-Formates oder die Zeigeradressierung in Strukturen. An jeder Stelle, an der direkt in ein Speicherlayout adressiert werden soll, muss ein Makro verwendet werden, das die Unabh¨angigkeit vom EndianFormat sicherstellt. Mit einer globalen Typdefinition kann dann auf die ZielHardware angepasst werden.
// Macros for convering 2 bytes to a word (16 bits) #ifdef BIG_ENDIAN // Syntax MAKE_UINT16(FirstByte, SecondByte) #define MAKE_INT16(a,b) Int16( (a 8) #define HIGH_BYTE(w) UInt8(w & 0XFF) #else // little endian
5.3 Eigene Definitionen im Shared
25
#define MAKE_INT16(a,b) Int16( (b 8) #endif // BIG_ENDIAN Listing 5-3. Makros zur Endian-Format-Anpassung
5.3.4 Zahlengrenzen Oft wird in Programmen die Abpr¨ ufung von Werten gegen die Grenzen der Zahlendarstellung gebraucht. Diese Compiler- und Hardware-abh¨angigen Konstanten werden ebenfalls in einer include-Datei, z.B. in einer eigenen "ownlimit.h" festgelegt, als Erg¨ anzung zu .
#define #define ... #define #define ... #define #define
INT8_MAX INT8_MIN
(Int8) (0x7F) (Int8) (0x80)
UINT8_MAX UINT8_MIN
(UInt8) (0xFF) (UInt8) (0)
UINT32_MAX UINT32_MIN
(UInt32) (0xFFFFFFFF) (UInt32) (0)
Listing 5-4. Max/Min-Typedefs f¨ ur eigene Datentypen
5.3.5 Const Definitionen Es gibt Konstanten, die f¨ ur alle Projekte gleich sind. Daf¨ ur ist die oben gezeigte Definition in einer "globallimit.h", die in ein Shared-Verzeichnis gelegt wird, sinnvoll. Es gibt aber auch Konstanten, die projektabh¨angig definiert werden m¨ ussen. Dennoch sollten die Namen in allen Projekten gleich sein. Die u osung in Projekten ist, von den Spielregeln f¨ ur Shared¨bliche L¨ Dateien abzuweichen und eigene Global-Dateien zu definieren. Damit gibt es dann aber drei St¨ ande zu pflegen: Die Globals im Shared, die dazugeh¨origen ¨ Implementierungen und die Ubertragung in eigene Globals. Sinnvoller ist es, eine weitere globale Definitionsdatei "GlobalExternal.h" einzuf¨ uhren, in der diese projektvarianten Konstanten extern deklariert, aber nicht definiert werden. Durch die Deklaration als extern wird das Projekt gezwungen, diese Konstanten, wenn sie verwendet werden, an geeigneter Stelle zu definieren, Name und Verwendung der Konstanten in der Implementierung sind aber im Sinne von Shared vorgegeben. Bei der Definition muss
26
5 Design-Rules und Konventionen
ebenfalls extern angegeben werden, sonst antworten manche Compiler mit einem schwer verst¨ andlichen Fehler. ... extern const Int32 TICK_DURATION; // constant specifying the tick duration extern const Int32 AUDIODISCPLAYER_COMPONENT_ID; extern const Int32 AUDIODISCPLAYERDEVICE_COMPONENT_ID; extern const Int32 KEYEVENTDISPATCHER_COMPONENT_ID; .....
In der Implementierung im Projekt werden die Werte dann wie ben¨otigt gesetzt. extern extern extern extern ...
const const const const
Int32 Int32 Int32 Int32
TICK_DURATION = 100; AUDIODISCPLAYER_COMPONENT_ID = 1; AUDIODISCPLAYERDEVICE_COMPONENT_ID = 2; KEYEVENTDISPATCHER_COMPONENT_ID = 3;
5.3.6 Eigene Assertions An einer definierten Stelle werden die Assertions f¨ ur das gesamte System umgelenkt und definiert. Die richtigen Aktionen wie Ausgabe u ¨ber einen DebugPort m¨ ussen entsprechend implementiert werden.
#ifndef ASSERTION_H #define ASSERTION_H // eigene Assertionsbehandlung extern void (*handleAssert) (unsigned int, const char*, const char*); #ifndef NDEBUG #define ASSERTION(cond) do { if (!cond) handleAssert(__LINE__, \ __FILE__, #cond); \ } while (0) #else #define ASSERTION(cond) ((void) 0) #endif #endif // end of ASSERTION_H Listing 5-5. Eigene Assertions
Mit der handleAssert-Funktion kann der Fehler z.B. in einen Fehlerspeicher abgelegt werden und ggf. ein Neustart initiiert werden. Mit dem #ifndefKonstrukt kann abgepr¨ uft werden, ob die Includes und Defines schon in einem anderen, bereits compilierten Modul inkludiert wurden [Thoe]. Damit
5.3 Eigene Definitionen im Shared
27
k¨onnen Doppeldefinitionen und Fehlermeldungen vermieden werden. Diese Vorgehensweise wird grunds¨ atzlich in allen *.h Headerdateien verwendet. Der ¨ Ubersichtlichkeit wegen werden wir diese Zeilen aber in den folgenden Listings einsparen. 5.3.7 Exceptions und Signale Exceptions und Signale werden in Embedded Frameworks verboten. Gr¨ unde sind Performanz und Speichereffizienz [Mey1]. 5.3.8 Eigene Ausgaben Mit Ausgaben sind im Moment eigene Textausgaben gemeint, die normalerweise zum sehr klassischen printf-Debugging“ eingesetzt werden und die ” Ausgaben auf eine Standard-Konsole geben. Diese Konsole kann das Display des Systems nutzen, meistens jedoch eine Schnittstelle des Systems zur Entwicklungsumgebung, wie z.B. Ethernet oder eine serielle Schnittstelle. Bessere Debugging-Konzepte werden in einem sp¨ateren Kapitel vorgestellt. Es kann beobachtet werden, dass bei großen Buffern (ab ca. 2000 Eintr¨ agen) Threads (Round-Robin ist eingestellt) und Prozesse ihre Ausgaben mischen, im Falle von QNX sogar Ausgaben verlieren oder verdoppeln. Die Testprogramme daf¨ ur sind unter unserem Link zu finden. Abhilfe m¨ ussen hier eigene Standard-Ausgaben schaffen, die z.B. je konkurrierender Einheit einen eigenen Kanal bekommen. Dieser wird am Ende der gew¨ unschten Ausgabe per flush im Format Thread-ID Meldungen“ aus” gegeben. Wir haben dieses Vorgehen dadurch angedeutet, dass an allen Ausgabestellen in Listings das u ¨bliche printf und cout symbolisch durch eigene Ausgaben sOwnCout zentral ersetzt werden. Auch die Standard-Funktion perror sollte ersetzt werden, z.B. durch sOwnPerror.
6 Speicherverwaltung
In Embedded Systemen ist der Speicher aus Kostengr¨ unden prinzipiell knapp. In kleinen oder in skalierbaren Systemen kann nicht von so genannten secon” dary“ Speichern (HDD, Flash-B¨ anke...) ausgegangen werden. Durch strikte Qualit¨ats- und Real-Time-Anforderungen k¨ onnen dynamische Speicherverwaltungen nicht unbesehen verwendet werden, wegen langer Betriebszeiten ohne Neustart muss das Entstehen von Memory Leaks prinzipiell vermieden werden. Bevor solche Themen vertieft werden k¨ onnen, sollte zun¨achst in Erinnerung gerufen werden, welche Sprachkonstrukte welchen Speicher beanspruchen. Daf¨ ur ist im Bild unten ein Speichermodell abgebildet. Es gibt dort vier Speichersegmente: Es sind dies das Stack-Segment f¨ ur die tempor¨are Speicherung, hier werden die lokalen Daten abgelegt und die Parameter und R¨ ucksprungadressen von Funktionen. Der Stack ist nach dem LIFO (last in first out) Prinzip organisiert. Er ist nur durch die Operation push und pop implizit zugreifbar, also nicht frei adressierbar. Weiterhin das Heap-Segment, der freie Arbeitsspeicher, in dem Speicher allokiert werden kann. Im Data-Segment werden Daten gehalten, die solange leben wie das Programm selbst. Das umfasst auch alle globalen und statischen Variablen. Das Datensegment wird in einen Bereich f¨ ur initialisierte Daten, einen f¨ ur nicht initialisierte Daten und einen const-Datenbereich unterteilt. Das Code-Segment oder auch Text-Segment speichert die auszuf¨ uhrenden Programmzeilen, den Start-Up Code, der vor der eigentlichen main aufgerufen wird, und den Terminate-Code, der nach Beendigung der main aufgerufen wird. Das Code-Segment ist im Programm-Verlauf nur lesbar (Read-Only). Im Prozessor befinden sich unter anderem zwei Register, ein Stack-Pointer Register und ein Instruction-Pointer Register. Wird ein Programm nach der Compilierung und Bindung in den Prozessorspeicher geladen, so werden die Maschinenbefehle vom Lader in das Code-Segment abgelegt. Der Instruktionszeiger zeigt dann auf den ersten auszuf¨ uhrenden Befehl im Code-Segment.
30
6 Speicherverwaltung
Abbildung 6.1. Speichermap, siehe auch [Mad]
Wird in einem Programm ein Unterprogramm (Funktionsaufruf) per Call aufgerufen, so wird der lineare Programmablauf verlassen und an eine andere Stelle im Code-Segment gesprungen. Daf¨ ur wird der letzte InstruktionsZeigerwert auf dem Stack gespeichert und dann die Einsprungadresse in den Instruktions-Zeiger geladen. Beim Verlassen des Unterprogramms wird die alte Instruktionsadresse zur¨ uckgeholt und das Programm bei der n¨ achsten Adresse fortgesetzt. ¨ Uber den Stack werden auch Parameter (Auto-Variable) f¨ ur das gerufene Unterprogramm u ¨bergeben und im Unterprogramm vom Stack gelesen. Umgekehrt werden Return-Werte u uckgegeben. Dies al¨ber den Stack zur¨ lerdings nur, wenn die Prozessor-Register nicht ausreichen. Die Variablen¨ ubergabe funktioniert in C/C++ per Default als Call-byvalue, also als Wert¨ ubergabe. Dies geschieht durch Kopieren des Werts auf den Stack oder durch Aufruf des Kopierkonstruktors f¨ ur Objekte oder Klassentypen, der dann das Objekt auf den Stack kopiert. Vermeiden l¨asst sich dieser Aufwand durch Referenz¨ ubergabe oder Zeiger¨ ubergabe.
6.2 Dynamische Speicherverwaltung
31
6.1 Statische Speicherverwaltung Im n¨achsten Kapitel werden Begriffe wie Prozess und Thread genauer erkl¨art. Vorweggenommen sei hier, dass jeder Prozess einen eigenen Prozessraum besitzt, mit Stack und Heap im privaten Adressraum, also auch mit eigenem Instruktionszeiger und Stack-Zeiger. Statische Speicherverwaltung wird die Speicherverwaltung genannt, die bereits zur Compile-/Link/Load-Zeit Daten und Datenaufbewahrungsorte definiert. Sie arbeitet also mit dem Datensegment. Zur Kompilierzeit wird definiert, dass es sich um Variablen im Daten-Segment handelt, der Linker legt relokatierbare Adressen fest und der Loader schließlich bildet das Speicherlayout auf endg¨ ultige Adressen ab. Wie erw¨ahnt, werden all die Variablen im Datensegment angelegt, die global außerhalb jeder Funktion definiert werden oder die lokal als static deklariert sind. Sie werden im Start-Up Code noch vor der main angelegt. Tats¨achlich beginnt die Programmausf¨ uhrung im Code-Segment mit dem Start-Up Code, ruft die main als Funktion auf und endet nach der main-Funktion mit einigen Destruktoren und dem R¨ ucksprung zum Betriebssystem. Die Reihenfolge der Datendefinitionen verl¨auft wie folgt: • Konstante Daten mit Initialisierung (const data)1 ; • Daten mit Initialisierung (init data); • Daten mit Default Initialisierung (uninit data = 0); • Klassenobjekte u ¨ber Konstruktor. Anschließend wird main aufgerufen. Die Reihenfolge der Erzeugung der init und uninit Data ist Compilerabh¨angig und nicht festgelegt. Die Reihenfolge der Klassenobjekte folgt innerhalb eines Files der Reihenfolge im Source-Code. Die Reihenfolge ist nicht festgelegt, falls zwei Klassenobjekte in verschiedenen Files definiert werden. Sollte sie wichtig sein, m¨ ussen beide Definitionen in einer Datei erfolgen.
6.2 Dynamische Speicherverwaltung Die dynamische Speicherallokation wird gebraucht, wenn erst zur Laufzeit des Programms feststeht, welche und wie viele Daten ben¨otigt werden. Auch Variablen und Objekte, die nur f¨ ur die Laufzeit einer Funktion ben¨otigt werden, sind im strengen Sinn dynamisch verwendete Variablen. Dies betrifft die 1
Viele Konstanten ben¨ otigen keinen Speicherplatz, sondern werden nur textuell ersetzt. Beispielsweise werden deshalb konstante Texte im Code-Segment angelegt. Dies allerdings nur, wenn der Compiler erkennen kann, dass keine Referenzen und keine Zeiger auf die Konstante mehr verwendet werden. Dies ist bei gr¨ oßeren include-Systemen“ nicht mehr m¨ oglich. ”
32
6 Speicherverwaltung
lokalen Variablen und die Variablen, die f¨ ur die Parameter¨ ubergabe angelegt werden. Da sie alle auf dem Stack angelegt und immer in umgekehrter Reihenfolge wieder vom Stack entfernt werden, ist die Verwaltung dieser Variablen einfach und ohne Probleme, solange die Stack-Gr¨oße nicht u ¨berschritten wird. Unter dynamischer Speicherverwaltung im eigentlichen Sinn versteht man die Allokation von Objekten auf dem Heap. ¨ Ubliche Anwendungsbeispiele sind offene Listen, deren Elementanzahl erst durch den Kunden w¨ ahrend der Nutzung festgelegt wird. Im unten stehenden Beispiel werden Variablen auf dem Heap allokiert, und zwar zur Laufzeit. Das gesamte Heap-Segment wird ebenfalls zur Ladezeit des Programms angelegt, normalerweise zun¨ achst mit fester Gr¨oße. Wie man im Bild unten erkennen kann, ist zwischen Stack und Heap Reservespeicher vorgehalten, so dass Stack, Heap oder beide bei Bedarf zur Laufzeit wachsen k¨ onnen. Es gibt auch Systeme, bei denen Stack und Heap auf benachbarten Adressen starten und voneinander weg wachsen.
Abbildung 6.2. Heap- und Stack im Adressraum
Die Konstrukte zur dynamischen Allokation sind malloc und free in C oder new und delete in C++.
6.4 Speicherbedarf und kleine Pittfalls
33
Der im Hintergrund arbeitende Heap Manager verwaltet den Speicher normalerweise in zwei Listen, einer Liste der freien Bl¨ocke und einer Liste der genutzten Bl¨ocke. Die Laufzeit-Routinen zur dynamischen Allokation von Speicher sind normalerweise auf verschieden definierte Blockgr¨oßen ausgelegt und ber¨ ucksichtigen auch die Ausrichtung (Alignment) des Speichers.
6.3 Start-Up-Strategien 6.4 Speicherbedarf und kleine Pittfalls Ist man sich nicht u ¨ber die Umsetzung der Speicherkonstrukte durch Compiler und ggf. Betriebssystem bewusst, k¨ onnen einige Fehler oder Ineffizienzen passieren. Deshalb frischen wir zun¨ achst unsere Kenntnisse bez¨ uglich der Speicherheimat“ verschiedener Objekte auf. Anschließend betrachten wir den ” Speicherbedarf verschiedener Objekte. Schließlich gibt es einige konkrete Hinweise zu m¨oglicher schlechter Implementierung. Einigen Lesern werden die Abschnitte banal vorkommen, sie m¨ogen die folgenden Unterkapitel gerne u attern. ¨berbl¨ Da solche Speicherfragen jedoch nicht u ¨berall zur Standard-Ausbildung geh¨oren, ist das Wissen u ¨ber den Speicherbedarf konkreter Implementierungen bei vielen auch erfahrenen Ingenieuren zuf¨ allig oder schwach ausgepr¨agt. 6.4.1 Welche Variable wo? Globale Variablen sind im ganzen Programm bekannt und behalten ihren Wert w¨ahrend der gesamten Laufzeit. Sie werden außerhalb jeder Funktion deklariert. Ihr Speicherort ist das Datensegement. Lokale Variable werden innerhalb von Funktionen deklariert. Gelegentlich werden sie auch automatische Variable genannt. Sie sind außerhalb ihres Blocks {} unbekannt. Gespeichert werden sie auf dem Stack. Elementare Daten im Datensegment werden u ¨blicherweise w¨ahrend des Programmstarts noch vor der main-Funktion initialisiert, elementare Daten auf dem Stack nicht. Lokale Daten werden auf dem Stack angelegt und leben“ solange, bis die ” lokale Umgebung (scope), z.B. die Funktion, verlassen wird. Soll die Variable auch im n¨ achsten Funktionsaufruf noch den alten Wert garantieren, so muss sie static deklariert werden. Damit wird sie im Datensegment angelegt. Das Datensegment wird u ¨blicherweise beim Hochstart des Systems einmal mit Nullen u ¨berl¨oscht. Es wird auch BSS-Section genannt. Unter dem angegebenen Link finden sich diverse Programme, mit dem man die Adressen und Segment-Bereiche vereinbarter Variablen u ufen ¨berpr¨
34
6 Speicherverwaltung
kann. In Linux kann man sich die Adressen des Text- und des Datensegments ausgeben lassen. Unter Windows geht das zun¨ achst nicht, man kann allerdings u ber eigene Variablen, deren Platz man kennt, R¨ uckschl¨ usse ziehen. ¨ Folgende Variablen werden untersucht, die Ergebnisse sind jeweils im Kommentar dargestellt. Im Kommentar wird der Begriff Plain-Old-Data verwendet. Plain-Old-Data sind elementare Datentypen, Zeiger, Arrays, Strukturen, Unions und alle Typen, die keinen Konstruktor ben¨otigen. •
• • • • • • • •
const //Code- oder Datensegment, davon abh¨ angig, ob POD, //Plain-old-Data (-> Code-Segment), und ob Referenzen //(-> Data-Segment) verwendet werden const Text //Code-Segment statische Variablen,Standardtyp //Datensegment,init oder uinit Bereich statische eigene Datentypen //Datensegment,init oder uinit Bereich Klassenvariable //Datensegment, init oder uinit Bereich globale Variablen //Datensegment, init oder uinit Bereich lokale Variablen //Stack lokal statische Variablen //Datensegment, init oder uinit Bereich dynamische Variablen //Heap
F¨ ur alle globale Variablen im Datensegment gilt: • Sie werden mit Null gef¨ ullt; • Objekte, die mit definierten Werten belegt werden sollen bzw. u ¨ber den Konstruktor erzeugt werden, werden durch den Start-Up-Code initialisiert; • Lokal statische Variablen werden nur beim ersten Durchlauf initialisiert. Zusammenfassung: Folgende Daten/Objekte werden im Datensegment abgespeichert: •
Globale Variablen (von allen Namensbereichen). Es sind Variablen, die außerhalb einer Funktion ohne das Schl¨ usselwort static definiert sind. Sie k¨onnen ohne weiteres von anderen Einheiten zugegriffen werden. • Modulglobale Variablen. Dies sind Variablen, die außerhalb von Funktionen definiert sind. Sie haben durch die Angabe des Schl¨ usselworts static ¨ eine Sichtbarkeit in der Ubersetzungseinheit, in der sie definiert sind.
6.4 Speicherbedarf und kleine Pittfalls
35
• Klassenvariablen. Dies sind statische Klassenelemente, deren Sichtbarkeit durch Zugriffsmodifikatoren geregelt wird. • Lokale statische Variablen. Dies sind statische Variablen in Funktionen. Globale Objekte werden in C++ h¨ aufig durch das Einzelexemplarmuster (Singleton-Pattern) realisiert. Die L¨ osung in der Literatur sieht vereinfacht etwa so aus: Im Headerfile:
class A { public: static A& getInstance() { return sInstance; } private: // Konstruktor privat. Evtl. mit Parameter A(); ~A(); static A sInstance; }; // Im cpp-File: ... A A::sInstance; // wg. static, evtl mit Parameter ... Listing 6-1. Globales Objekt gem¨ aß Singleton-Pattern
Das Klassenelement sInstance wird beim Starten des Programms angelegt und am Ende zerst¨ ort. Mit ¨alteren Compilern kann man eventuell einen Kompilierfehler erhalten, weil der Destruktor privat (z.B. Visual C++ 6.0) ist [Vlis]. Man erh¨alt auch wegen des privaten Konstruktors eine Warnung. Dies kann man u ¨ber eine Dummy friend Klasse beheben. Die Vorteile sind Einfachheit und deterministische Zugriffszeit. Von Nachteil kann die Verl¨ angerung der Startzeit sein, wenn zur Erzeugung bzw. Initialisierung des Objekts im Konstruktor komplexe Aktivit¨aten ausgef¨ uhrt werden m¨ ussen. In der Praxis k¨ onnen so einige Sekunden f¨ ur den Start-up-Code zusammenkommen. Zur Vermeidung der genannten Nachteile gibt es verschiedene Strategien:
36
6 Speicherverwaltung
6.4.2 Alternative Strategie 1: Sp¨ ate Konstruktion Erst wenn das Objekt ben¨ otigt wird (beim ersten Zugriff), wird es angelegt:
class A { public: static A& getInstance(); private: // Konstruktor privat, evtl. mit Parameter A(); ~A(); static A *sInstancePtr; }; //Im cpp-File: ... A A::*sInstancePtr; A& A::getInstance() { if (0 == sInstancePtr) { sInstancePtr = new A(); } return *sInstancePtr; }
// wg. static
Listing 6-2. Sp¨ ate Konstruktion des globalen Singleton-Objekts
Mit dieser L¨osung hat man allerdings andere Nachteile: • Der Destruktor kann nicht aufgerufen werden, um Aufr¨aumarbeiten zu erledigen. ¨ • Ein Uberpr¨ ufungsprogramm wie Purify wird aufgrund eines fehlenden delete-Aufrufs eine Warnung ausgeben. • Wenn man den Destruktor ¨ offentlich macht und ein globales Objekt verwendet, um verschiedene globale Objekte anzulegen und zu zerst¨oren, siehe unten, so kann der Benutzer ihn auch falsch verwenden. A& a = A::getInstance(); delete &a;
•
// globales Objekt wird zerst¨ ort!
Das Objekt liegt im Heap und soll bis zum Ende des Programms existieren. Die Gefahr der Speicherfragmentierung wird dadurch h¨oher. • Die Zeit des ersten Zugriffs nicht deterministisch.
6.4 Speicherbedarf und kleine Pittfalls
37
• Schließlich kann es einen Wettlauf zwischen Objekten oder Komponenten geben, Objekte werden aus Versehen angesprochen, die noch nicht existieren. Problematisch ist es auch, wenn das Objekt in einer Multithread-Umgebung eingesetzt wird. Es kann unter Umst¨anden mehr als ein Objekt angelegt werden, wenn mehrere Threads gleichzeitig die Abfrage der getInstance-Methode durchf¨ uhren. Um das letztgenannte Problem zu vermeiden, muss man ggf. einen Mutex einf¨ uhren. Der kritische Abschnitt wird dann durch den Mutex gesch¨ utzt2 .
class A { public: static A& getInstance(); private: A(); ~A(); static A *sInstancePtr; static CMutex sMutex; };
// Konstruktor private. Evtl mit Parameter
Im cpp-File: ... A A::*sInstancePtr; CMutex A::sMutex; A& A::getInstance() { sMutex.take(); if (0 == sInstancePtr) { sInstancePtr = new A(); } sMutex.give(); return *sInstancePtr; } Listing 6-3. Sp¨ ate Konstruktion des globalen Singleton-Objekts mit Mutex-Schutz 2
Wir arbeiten hier schon unvermeidbar mit Begriffen, die erst sp¨ ater im richtigen Zusammenhang erl¨ autert werden k¨ onnen. Hier soll die Erkl¨ arung gen¨ ugen, dass in einer Mutithread-Umgebung mehrere Aufgaben quasi gleichzeitig bearbeitet werden k¨ onnen, was auch zu Konflikten und Kollisionen f¨ uhren kann. Deshalb synchronisiert man kritische Bereiche mit Semaphoren oder Mutexen, die nur jeweils eine Aufgabe zulassen. Mit take erfolgt die Sperrung f¨ ur andere, mit give die Freigabe f¨ ur alle.
38
6 Speicherverwaltung
Um den Mutex nicht jedes Mal zuzugreifen und um die Anzahl der verwendeten Mutexen zu reduzieren (nur einen f¨ ur mehrere Klassen, weil der Mutex nur bei der Erzeugung des Objekts ben¨ otigt wird), wird das Doppel-CheckingMuster verwendet:
A& A::getInstance() { if (0 == sInstancePtr) { sMutex.take(); if (0 == sInstancePtr) sInstancePtr = new A(); sMutex.give(); } return *sInstancePtr; }
// erst auf NULL checken // dann nehmen // zweite ¨ Uberpr¨ ufung, immer noch NULL?
Listing 6-4. Singleton mit Doppel-Checking
Das Problem der Allokation auf dem Heap kann man durch Placement-new l¨osen (siehe Abschnitt u ¨ber die quasi statische Allokation) und das oder die Objekte in einen vorreservierten Speicher platzieren. 6.4.3 Alternative Strategie 2: Sp¨ ate Initialisierung Werden in Projekten alle Objekte erst beim Start oder zur Laufzeit angelegt, so kommt es leicht zu inakzeptablen Startzeiten. Deshalb w¨ahlt man oft die Strategie der sp¨aten Initialisierung. Man nennt dies auch lazy Initialisation“ ” oder auch Load/Init on Demand“. ” Der Speicher f¨ ur das Klassenobjekt ist im Datensegment, und das Objekt wird nur beim ersten Zugriff initialisiert. Die L¨osung (f¨ ur MultithreadUmgebung) sieht etwa wie folgt aus:
class A { public: static A& getInstance(); private: //einfacher Konstruktor private. Evtl. mit Parameter A(); ~A(); void init(); static A sInstance; static bool sInitialized;
6.4 Speicherbedarf und kleine Pittfalls static CMutex sMutex; }; /Im cpp-File: ... A A::sInstance; bool A::sInitialized; CMutex A::sMutex; A& A::getInstance() { if (false == sInitialized) { sMutex.take(); if (false == sInitialized) { sInitialized = true; sInstance.init(); } sMutex.give(); } return sInstance; }
39
// standard mit 0 gef¨ ullt. d.h. false
// doppel-checking
Listing 6-5. Sp¨ ate Initialisierung eines Singletons
Mit dieser L¨osung hat man noch den Nachteil, dass der erste Zugriff nicht deterministisch ist. Die Start-Up-Zeit und der Heap werden dadurch aber nicht mehr belastet. 6.4.4 Alternative Strategie 3: Lokale statische Variablen Wenn das Objekt nicht in Multithread-Umgebung eingesetzt wird, kann man es als lokales statisches Objekt definieren. Die L¨osung ist dann
class A { public: static A& getInstance(); private: // einfacher Konstruktor private. Evtl mit Parameter A(); ~A(); }; //Im cpp-File: ... A& A::getInstance() { static A sInstance;
40
6 Speicherverwaltung return sInstance;
} Listing 6-6. Lokale statische Variablen
Der Compiler generiert daraus einen Code, der etwa wie die zweite L¨osung aussieht (mit einer Hilfsvariablen zum Merken, ob das Objekt schon angelegt wurde). Diese L¨ osung hat alle Vorteile der zweiten, kann allerdings ¨ in Multithread-Umgebungen durch Zugriff-Uberschneidungen zum Problem f¨ uhren. Man kann auch eine L¨ osung f¨ ur Multithread-Umgebung mit lokal statischen Variablen bauen. Sie ist allerdings teurer als die zweite Variante. 6.4.5 Speicherort von Konstanten Zu beachten ist, dass globale Konstanten sowohl im Code- als auch in Datensegment platziert werden. Konstanten von primitiven Datentypen (bzw. von Strukturen mit primitiven Datentypen) werden im Code-Segment angelegt, so dass ein Const-Cast unter Umst¨ anden (wenn das Code-Segment gesch¨ utzt wird) unm¨oglich ist. const char* txtptr = "hello\n"; .. const_cast (txtptr)[0] = ’H’;
//Absturzgefahr
Der globale Zeiger textPtr liegt im Datensegment. Er enth¨alt die Adresse der C-Zeichenkette "hello\n". Die Zeichenkette selbst ist im Code-Segment gespeichert und kann nicht modifiziert werden. Dagegen liegen konstante, globale Objekte im Datensegment, so dass Folgendes kein Problem verursacht (die Frage, warum man const verwendet, bleibt außer Betracht). In der Anwendung kann const verwendet werden, um Schattenkonstanten zu realisieren. Dies sind Konstanten, die Kopien von bestimmten Werten sind und von außerhalb gelesen werden k¨onnen. Sie werden nur ge¨andert, wenn sich die Originale ge¨ andert haben.): class A { public: int i; }; ... const A a; ... (const_cast(a)).i = 5;
//¨ Anderung der Daten von a
Ein weiteres Beispiel zeigt, dass eine Struktur mit plain old Data in der Regel im Code-Segment angelegt wird. Hier f¨ uhrt ein Schreibversuch zum Fehler, da das Code-Segement nur gelesen werden kann.
6.4 Speicherbedarf und kleine Pittfalls
41
//Struktur mit plain old type //Initialisierung ben¨ otigt keinen Konstruktor struct B { int a; int b; }; const B obj = {1,2}; int main() { cout <parameter /> <parameter />