Ein mehrfadiger Java-Mikrocontroller fur eingebettete Systeme U. Brinkschulte, C. Krakowski Institut fur Prozerechentechnik, Automation und Robotik Universitat Karlsruhe D-76128 Karlsruhe J. Kreuzinger, Th. Ungerer Institut fur Rechnerentwurf und Fehlertoleranz Universitat Karlsruhe D-76128 Karlsruhe In der vorliegenden Arbeit werden der Einsatz von Java in eingebetteten Systemen analysiert und Techniken fur eine echtzeitfahige Behandlung mehrerer sich uberlappender Ereignisse durch JavaThreads vorgeschlagen. Diese Techniken werden hardwaremaig durch einen mehrfadigen Java-Mikrocontroller unterstutzt. 1 Einleitung Der Markt fur eingebettete Systeme ist einer raschen Ausdehnung unterworfen. Als Folge hiervon verfugen Mikrocontroller derzeit uber eine groere Zuwachsrate als Mikroprozessoren. Schatzungen zufolge wird um das Jahr 2000 das durchschnittliche Haus in den USA 50 bis 100 Mikrocontroller umfassen. Daruber hinaus wird es Millionen von Handies, Set-top-Boxen, Personal digital assistants, Netzwerkterminals und weitere Internet-Gerate geben, die in einer Netzwerkumgebung arbeiten und fur spezische Anwendungen optimiert sind. Auch im Bereich der Automatisierungstechnik werden eingebettete Systeme zunehmend untereinander vernetzt. Ein wesentliches kunftiges Merkmal solcher Systeme ist die Notwendigkeit zur umfassenden Ferndiagnose und Fernwartung. Aus diesen Grunden wird in letzter Zeit zunehmend der Einsatz von Java Softund Hardware fur eingebettete Systeme untersucht. Die objektorientierte Sprache Java [1] war zunachst als Sprache fur eingebettete Systeme konzipiert, bevor der Aspekt der Internet-Unterstutzung den Vorrang erhielt und dadurch die schnelle Verbreitung von Java hervorgerufen wurde. Die Objektorientierung von Java fuhrt zu einer einfachen Programmierung, klaren Schnittstellen und unterstutzt die Wiederverwendbarkeit von Software sowie die Entwicklung robuster Programme. Es existiert eine sehr umfangreiche, genormte Klassenbibliothek. Java Bytecode ([2], [3]) ist die Java Maschinensprache der Java Virtual Machine (JVM). Java Bytecode ist rechnerunabhangig, er kann problemlos auf alle Arten von Prozessoren geladen werden. Dieser Aspekt ist insbesondere fur die Fernwartung und -diagnose in eingebetteten Systemen von groer Bedeutung. Durch den Bytecode-Verier konnen Java Programme sicher verwendet werden, d.h. ein Mibrauch auf dem ausfuhrenden Rechner durch ein fehlerhaftes oder mibrauchlich verandertes Programm wird verhindert. Eine spezielle Anforderung an eingebettete Systeme in der Automation ist die Echtzeitfahigkeit. Die hier vorgestellte Arbeit betrachtet den Einsatz von Java sowie spezieller Java-Hardware fur eingebettete Systeme in der Automa- tion. Wesentlicher Schwerpunkt ist hierbei die echtzeitfahige Behandlung konkurrierender Ereignisse aus Sicht von Java Hard- und Software. Hierauf aufbauend wird die Konzeption einer Architektur und Mikroarchitektur fur einen mehrfadigen Java-Mikrocontroller vorgestellt. 2 Java und Echtzeit Fur den Bereich der Echtzeitsyteme ist Java in seiner ursprunglichen Form zunachst wenig geeignet [4]. Die Sprache enthalt zum einen keinerlei Konstrukte zur Denition von Echtzeitbedingungen fur die Programmausfuhrung, zum anderen ist die Ausfuhrungsgeschwindigkeit in vielen Java-Implementierungen durch Interpretation des Java Bytecode sehr langsam bzw. durch Just-in-timeCompiler und Speicherbereinigung wenig vorhersagbar. Um diese Nachteile zu beseitigen, gibt es eine Reihe von Losungen: Im einfachsten Fall wird die Sprache Java in hybrider Form mit einem Echtzeitbetriebssystem kombiniert. Beispiele hierfur sind JWorks von WindRiver [5], Java auf OS-9 [6] oder JavaOS [7]. Die Vorteile von Java werden hierbei fur die nicht echtzeitkritischen Anwendungsteile eingesetzt, wahrend echtzeitkritische Anwendungsteile in konventioneller Form, z. B. in C, realisiert werden und als Echtzeit-Tasks auf dem Echtzeitbetriebssystem ablaufen. Java selbst ist hierbei jedoch nicht echtzeitfahig. Weitergehende Losungen bestehen darin, Java selbst echtzeitfahig zu machen. Hierbei konnen zwei Typen unterschieden werden: Der erste Typ basiert auf einer echtzeitfahigen Java Virtual Machine (JVM). Diese ist in der Lage, Java Bytecode echtzeitfahig auszufuhren. Minimal notwendig ist hierzu eine echtzeitfahige Speicherbereinigung sowie die echtzeitfahige Ausfuhrung von Java Threads. Dieses Ziel wird z. B. in der in [8] beschriebenen Realisierung von Java Realtime Threads verfolgt. Hier wird eine JVM auf der Basis des RT-Mach Mikrokernels [9] realisiert. Java-Threads werden auf RT-Mach Threads abgebildet, welche die Denition von Startzeit, Periode und Deadline fur einen Thread erlauben. Die Synchronisation zwischen Threads wird auf Echtzeit-Mutexe abgebildet, welche Prioritateninversionen durch Prioritatenvererbung vermeiden. Ein weiterer Vertreter dieses Typs ist PERC [10]. PERC bietet verschiedene Klassen von Echtzeit-Tasks (periodisch, spontan, sporadisch, fortlaufend) sowie Mechanismen zur Aushandlung und Reservierung von Ressourcen. Daruber hinaus besteht durch Spracherweiterungen die Moglichkeit zur zeitlichen Beschrankung von Code-Abschnitten sowie die Denition atomarer Code-Abschnitte. Schlielich erlaubt PERC fur einen eingeschrankten Sprachumfang eine Worst-case-Laufzeitanalyse. PERC erzeugt annotierten Bytecode, der auf der echtzeitfahigen PERC Virtual Machine ausgefuhrt wird. Es besteht auch die Moglichkeit, diesen annotierten Bytecode ohne Einhaltung der Echtzeitbedingungen auf einer beliebigen JVM auszufuhren. Alle Losungen dieses Typs haben als gemeinsame Eigenschaft die Interpretation des Java Bytecode. Dies fuhrt jedoch im Vergleich zu konventionellen Sprachen wie etwa C zu einer deutlich schlechteren Verarbeitungsgeschwindigkeit, welche insbesondere im Bereich der eingebetteten Systeme durch die dort verwendete eher schwache Hardware problematisch ist. Der zweite Typ von Systemen vermeidet diesen Nachteil, dadurch da Java Bytecode weiter auf Maschinencode u bersetzt wird. Ein Vertreter hiervon ist JBed [11]. Hierdurch konnen Ausfuhrungszeiten erreicht werden, die durchaus im Bereich von C++ Programmen liegen. Durch die Umgehung des Java Bytecode verlieren diese Systeme jedoch dessen wesentliche Vorteile von Plattformunabhangigkeit und Sicherheit. Will man den Bytecode beibehalten und die Java-Klassen zur Laufzeit laden aber auch die Performance von compiliertem Code nutzen, gibt es zwei Moglichkeiten. Ein Just-in-time-Compiler fuhrt die U bersetzung wahrend der Ausfuhrung durch. Dies ist jedoch durch mangelnde zeitliche Vorhersagbarkeit fur Echtzeitsysteme problematisch. Die zweite Moglichkeit besteht darin, die ganze Klasse nach dem Laden zu u bersetzen (durch einen sogenannten Flash-Compiler). Dies fuhrt zu verbesserter Vorhersagbarkeit, bewirkt jedoch verlangerte Ladezeiten und im allgemeinen einen \schlechteren" Code, da der Compiler auf dem eingebetteten System nicht so gut optimieren kann. Daruber hinaus benotigen beide Varianten nicht unerheblich zusatzlichen Speicher, welcher gerade im Bereich der eingebetteten Systeme meist knapp bemessen ist. 3 Java-Prozessoren Java-Prozessoren konnen durch die direkte Ausfuhrung der Bytecode-Befehle eine vergleichsweise schnelle Ausfuhrung mit geringem Speicherbedarf realisieren [12]. Durch die Spezialisierung des Prozessors auf die Sprache Java konnen beim Entwurf Optimierungen entwickelt werden, die zu einer weiteren Leistungssteigerung fuhren. Besonderheiten, die bei der Ausfuhrung von Java auftreten, sind im wesentlichen die stack-basierte Verarbeitung der BytecodeBefehle und die Speicherbereinigung. Im picoJava I von Sun [13] wird daher statt eines wahlfrei zugreifbaren Registersatzes ein Stack-Registersatz mit 64 Eintragen auf dem Chip integriert. Der Stack-Registersatz kann durch Methodenaufrufe und Operanden immer weiter wachsen und ist daher in seiner Groe nicht begrenzt. Der Dribbler [14] ist ein Hardware-Mechanismus, der den aktuellen Fullstand des Stacks uberwacht und bei einem drohenden U berlauf Daten in den Speicher auslagert. Umgekehrt werden beim Erreichen einer unteren Fullmarke die Daten wieder aus dem Speicher gelesen und auf dem Stack abgelegt. Eine weitere Technik, die als Hardware-Optimierung von Sun eingefuhrt wurde, ist das Folding. Bei einer Stackarchitektur bezieht sich jede Berechnung auf die Operanden \oben" auf dem Stack, d.h., eine Addition nimmt zwei Operanden vom Stack, berechnet das Ergebnis und legt diese wieder auf den Stack. Sind die Operanden nun lokale Variablen, liegen diese tief im Stack verborgen und mussen erst durch einen speziellen Befehl auf den Stack geholt werden. Gleiches gilt fur das Ergebnis, wenn es in einer lokalen Variablen abgelegt werden soll. Eine Addition zweier lokaler Variablen in eine lokale Variable benotigt also vier Befehle. Das Folding kann diese Situation beim Decodieren erkennen und einen RISC-ahnlichen Befehl mit Quell- und Zieloperanden daraus erzeugen, der nur einen statt vier Takte benotigt [15]. Diese Technik kann auch bei weniger optimalen Fallen eingesetzt werden, wenn z. B. schon ein Operand auf dem Stack zur Verfugung steht. Zur Unterstutzung von Speicherbereinigungsalgorithmen, die mit mehreren Speichersegmenten arbeiten (z. B. generationelle Speicherbereiniger), konnen sogenannte Schreibbarrieren [15] in Hardware implementiert werden. Diese losen eine Unterbrechung aus, wenn beim Schreiben von Daten eine Speicherreferenz zwischen den Segmenten erzeugt oder verandert wird. Damit konnen die Wurzelzeiger, welche die Basis fur das Durchsuchen der Segmente nach benutzten Speicherblocken bilden, akualisiert werden. Ohne Schreibbarrieren sind fur diese U berprufung zusatzliche Befehle fur alle Schreibbefehle notig. Sun gilt als Vorreiter im Bereich der Java-Prozessoren. Mit dem picoJava I und II, microJava-701 und ultraJava sollen Java-Prozessoren angefangen von eingebetteten Systemen bis hin zu Arbeitsplatzrechnern zum Einsatz kommen. Das Interesse an Suns Technologie wird durch die Lizenzabkommen mit Fujitsu Limited, IBM Corporation, LG Semicon Co, Ltd. NEC Corporation und Rockwell Collins, Inc. deutlich. Es ist daher zu erwarten, da sich die JavaProzessoren in naher Zukunft rasch verbreiten werden. 4 Ereignisbehandlung in eingebetteten Systemen Eingebettete Systeme zeichnen sich unter anderem auch dadurch aus, da sie in Umgebungen mit vielen synchronen und asynchronen Ereignissen operieren. Eine wesentliche Anforderung an eingebettete Systeme ist eine schnelle Reaktionszeit auf Ereignisse innerhalb eines vordenierten Zeitfensters (Echtzeiteigenschaft). Die Behandlung von Ereignissen (externe oder interne Signale) erfolgt bei eingebetteten Systemen meist durch Interrupt-Service-Routinen (ISRs). Dabei wird die momentane Programmausfuhrung unterbrochen, der Kontext gesichert und die ISR gestartet. Die Kontextsicherung wird haug durch das Vorhandensein von mehreren Registersatzen beschleunigt. Ein paar Takte Kontextwechselaufwand werden jedoch fur den Aufruf trotzdem benotigt. Treten mehrere Ereignisse zeitgleich auf, so werden diese nach Prioritaten geordnet verarbeitet, d.h., die ISR des hochstprioren Ereignisses wird sofort ausgefuhrt und die anderen mussen auf deren Ende warten. Dies entspricht dem sogenannten Fixed-priority-preemptive-Scheduling, welches nur eine Prozessorauslastung von etwa 70% ermoglicht [16]. Desweiteren konnen nur fur das Ereignis hochster Prioritat in jedem Fall Zeitschranken garantiert werden, Ereignisse niederer Prioritat konnen auf unabsehbare Zeit unterbrochen werden. Fur dieses Problem gibt es mehrere Losungen: 1. Samtliche ISRs werden so kurz wie moglich gehalten, um Unterbrechungszeiten zu minimieren. Dies erfordert jedoch, da ein Groteil der Aktivitaten aus den ISRs in die normale Programmausfuhrung verlagert wird, was zu erhohtem Kommunikationsaufwand, komplexer Programmierung und schwierigen Tests fuhrt. Dieser Aspekt wirkt sich negativ auf die Zuverlassigkeit des Systems aus. 2. Der Einsatz eines Echtzeitbetriebssystems vereinfacht die Entwicklung, kann jedoch bei kleinen Systemen durch die Lizenzgebuhren den Gesamtpreis erheblich erhohen. Dies ist bei hohen Stuckzahlen oft nicht akzeptabel. Desweiteren ist in diesem Fall mehr Speicher erforderlich. 3. Pro Ereignis mit Echtzeitbedingung wird ein eigener Mikrocontroller eingesetzt. Auch hier steigen die Kosten je nach Anforderung an den Mikrocontroller. 4. Pro Ereignis wird ein eigener Kontrollfaden ausgelost, der konkurrent auf demselben Mikrocontroller ablauft. Schon in Bearbeitung bendliche Kontrollfaden werden nicht unterbrochen, jedoch wird entsprechend der Prioritat eine Ressourcenzuteilung auf der Ebene einzelner Prozessortakte durchgefuhrt. Diese Vorgehensweise entspricht der Proportional-share Schedulingstrategie in Echtzeitbetriebssystemen [17]. Die vierte Variante stellt eine neue Technik dar. Hierbei werden ISRs durch Interrupt Service Threads (ISTs) ersetzt. Dies ist jedoch nur auf mehrfadigen Prozessoren moglich und wird im nachsten Abschnitt betrachtet. Auf konventionellen Prozessoren besteht jedoch die Moglichkeit, diese Technik nachzubilden, indem eine ISR einen Betriebssystem-Thread zur Ereignisbehandlung startet. So erlaubt es z. B. der in [18] beschriebene AST-Mechanismus (Asynchronous System Traps), ein asynchrones Ereignis an einen Proze weiterzuleiten. Ein Nachteil dieser und ahnlicher Software-Erweiterungen ist die hohe Latenzzeit, die durch das Weiterreichen des Ereignisses entsteht. Die hohen Latenzzeiten konnen durch die Behandlung der Ereignisse mit ISTs, die direkt von der Hardware unterstutzt werden, vermieden werden, wenn der Kontextwechsel zwischen den ISTs sehr schnell ist. 5 Die Architektur eines mehrfadigen Java-Mikrocontrollers Die Idee, Echtzeitereignisse mit Threads zu behandeln, erfordert einen sehr schnellen Kontextwechsel. Dies ist im allgemeinen nur mit einer mehrfadigen Prozessorarchitektur moglich [19], wobei diese Technik bisher fast nur zur U berbruckung von Latenzen eingesetzt wurde. Ein mehrfadiger Prozessor kann Befehle mehrerer Threads gleichzeitig in der Prozessor-Pipeline ausfuhren. Er besitzt mehrere Befehlszahler und meist auch mehrere Registersatze, die den geladenen Threads zugeordnet sind, so da ein Kontextwechsel einfach moglich ist. Die Ausfuhrungs-Pipeline des mehrfadigen Java-Mikrocontrollers (siehe Abb. 1) ist ahnlich der von Suns picoJava-I Prozessor: Befehl holen, Befehl decodieren und Operanden bereitstellen, Befehl ausfuhren bzw. Lade-Speicherzugri und Resultat oder geladener Wert auf dem Stack ablegen. Die Befehlsholestufe (instruction fetch) beinhaltet fur jeden der vier Threads einen Befehlszahler (PC) mit zugehorigen Statusbits (z. B. Thread aktiv, PC gultig). Anhand der Statusbits und den Fullstanden der Befehlsfenster wird entschieden, u ber welchen PC neue Befehle geladen werden. Mit einem Takt werden 4 Bytes uber die Speicherschnittstelle (memory interface) geholt und in das entsprechende Befehlsfenster (IW) geschrieben. Da ein Bytecode-Befehl im Durchschnitt 1.8 Byte lang ist, sind in den 4 Bytes meist mehrere Befehle enthalten. instruction fetch address PC1 PC2 PC3 PC4 memory interface instructions IW1 IW2 IW3 IW4 priority manager micro-ops ROM data path signal unit extern signals instruction decode MEM ALU stack register sets Abbildung 1: Struktur des Java-Mikrocontrollers Zu der Decodierstufe (instruction decode) gehoren die bereits erwahnten Befehlsfenster, verschiedene Statusbits (z. B. Prioritat, letzter Befehlstyp) und Zahler fur die Anzahl Befehle pro Zeitscheibe zur Unterstutzung des Proportional-share Schedulings. Der Prioritatenmanager bestimmt, in Abhangigkeit von den Statusbits und Zahlern, von welchem der Befehlsfenster der nachste Befehl decodiert werden soll. Damit lassen sich verschiedene Schedulingmechanismen (z. B. Fixed-priority, Proportional-share) implementieren. Ein Bytecode-Befehl wird je nach Komplexitat direkt oder in eine Sequenz von Mikrobefehlen aufgelost, oder es wird eine Trap-Routine aufgerufen. Jeder Befehl in der Pipeline ist mit einer Thread-Kennung versehen, damit Befehle von unterschiedlichen Threads in der Pipeline gleichzeitig verarbeitet werden konnen. Befehle, die auf den Speicher zugreifen, werden von der Speichereinheit (MEM) verarbeitet. Ist pro Takt nur ein Zugri u ber die Speicherschnittstelle moglich, mu ein Arbiter den Konikt zwischen dem Holen der Befehle und dem Datenzugri auosen. Alle anderen Befehle werden von der ALU ausgefuhrt. Abschlieend werden die Ergebnisse oder geladenen Werte auf dem entsprechenden Stack abgelegt. Externe Signale von Zeitgebern, seriellen Schnittstellen oder Peripheriegeraten werden u ber die Signaleinheit auf den Prozessorkern gebracht. Tritt ein solches Signal auf, wird der zugehorige Interrupt-Service-Thread (IST) aktiviert und kann nun vom Prioritatenmanager ausgewahlt werden. Sobald die Arbeit des IST vollendet ist, wird der zugehorige Echtzeit-Thread angehalten und sein Status gesichert. Tritt das Signal erneut auf, wird er wieder aktiviert. Damit keine Pipeline-Hemmnisse entstehen, konnen in jedem Takt Befehle aus unterschiedlichen Threads in die Pipeline eingefuttert werden. Grunde fur Leerlaufzeiten konnten Sprunge oder Speicherzugrie sein. Die Decodierstufe kann anhand der Befehle solche Latenzen erkennen und mit einem anderen Befehlsfenster fortfahren. Solch ein Kontextwechsel benotigt keinen zusatzlichen Zeitaufwand. Es mussen weder Register gerettet oder geladen, noch Befehle aus der Pipeline verworfen werden. Fur jeden Thread ist ein eigener Kontext auf dem Prozessor vorhanden. Hardwareoptimierungen wie sie von Sun vorgeschlagen werden (siehe Abschnitt 3) sollen bei diesem Entwurf ebenfalls zum Einsatz kommen. Der Dribbler kann jedoch nicht auf die Stack-Registersatze der Echtzeit-Threads angewandt werden, da er in seiner Arbeitsweise zeitlich nicht vorhersagbar ist. 6 Zusammenfassung Java bietet durch seine Objektorientierung und den Bytecode viele Vorteile. Beim Einsatz von Java in eingebetteten Echtzeitsystemen zeigt sich jedoch, da interpretierter Bytecode in der Regel zu langsam ist und JIT-Compiler zu viel Speicher benotigen und Antwortzeiten schwer vorhersagbar sind. Bei vollcompilierten Losungen gehen die Vorteile des Java Bytecode verloren. Diese Probleme sollen in einem dedizierten Systementwurf aus Anwendung, angepater Java-Umgebung und einem echtzeitfahigen, mehrfadigen Java-Mikrocontroller untersucht werden. Statt Interrupt-Service-Routinen sollen Java-Threads als Interrupt-Service-Threads (ISTs) fur die Ereignisbehandlung eingesetzt werden. Der vorgeschlagene Java-Mikrocontroller ist kompatibel zu Suns picoJava-I gehalten, jedoch um Mehrfadigkeit, eine Signaleinheit und einen Prioritatenmanager erweitert. Derzeit wird der mehrfadige Java-Mikrocontroller in Software simuliert und fur seinen Einsatz in einer eingebetteten Echtzeitumgebung getestet. Geplant ist eine Hardware-Realisierung auf einem FPGA-Board und sein Einsatz in einem fahrerlosen Transportsystem (FTS). Literatur [1] D. Flanagan. Java in a Nutshell. OReilly, 2nd edition, 1997. [2] J. Gosling. Java Intermediate Bytecodes. Sigplan Notices, 30, March 1995, 111-118. [3] J. Meyer, T. Downing. Java Virtual Machine. OReilly, Cambridge, 1997. [4] K. Nilsen. Java for Real-Time. Real-Time Systems Journal, 11(2), 1996. [5] Wind River Systems. Tornado for Embedded Internet. http://www.wrs.com/embedweb/html/white.html. [6] Microware Systems Corporation. Java for OS-9. http://spin.microware.com/html/wp index.html. [7] Sun Microsystems. JavaOS for Consumers. http://www.sun.com/javaos/consumers. [8] A. Miyoshi, T. Kitayama, H. Tokuda. Implementation and Evaluation of Real-Time Java Threads. IEEE Real-Time Systems Symposium, San Francisco, 1992. [9] H. Tokuda, T. Nakajiama, P.Rao. Real-Time Mach: Towards a Predictable Real-Time System. Proceedings of USENIX 2nd Mach Symposium, Oct. 1991. [10] NewMonics, Inc. PERC Virtual Machine 1.0. http://www.newmonics.com/WebRoot/perc.info/data.sheets/percvm.html. [11] Cuno Pster, Oberon microsystems. JBed Whitepaper: Component Software and Real-Time Computing. http://www.oberon.ch/rtos/whitepaper. [12] P. Wayner. Sun Gambles on Java Chips. BYTE, November 1996, 79-88. [13] Sun: Picojava-I Microprocessor Core Architecture. Sun Microsystems, Hardware and Networking Microelectronic Whitepapers, WPR-0014-01, 1997. [14] J. M. O'Connor, M. Tremblay. PicoJava-I: The Java Virtual Machine in Hardware. IEEE Computer, March/April 1997, 45-53. [15] H. McGhan, M. O'Conner PicoJava: A Direct Execution Engine For Java Bytecode. IEEE Computer, October 1998, 22-30. [16] C. L. Liu, J. W. Layland. Scheduling Algorithms for multiprogramming in a hard real-time environment. JACM, 20(1), 46-61, 1973. [17] I. Stoica, H. Abdel-Wahab, J.E. Gehrke, K. Jeay, S. K. Baruah, C. G. Plexton. A Proportional Share Resource Allocation Algorithm for RealTime, Time-Shared Systems. Proceedings of the 17th IEEE Real-Time Systems Symposium Washington, DC, December 1996 pages 288-299. [18] Digital Equipment Corporation. Guide to DECthreads. Digital Equipment Corporation, March 1996. [19] J. Silc, B. Robic, T. Ungerer. Processor Architecture: From Dataow to Superscalar and Beyond. Springer-Verlag, Heidelberg, 1999.