Inhalt
Topic:LibJc.Threads
Topic:LibJc.Threads.JavaThreads
Java hat bereits in der Version 1.0 eine vollständige Threadunterstützung enthalten. In der Version 1.1 erfolgenten dann noch einige kleine aber wesentliche Korrekturen in der Hinsicht, dass bestimmte Methoden als deprecated und unrecommended bezeichnet wurden, womit wichtige Anwendungserfahrungen reflektiert wurden. Ansonsten ist das Thread-System von Java sehr stabil.
Folgende wesentliche Klassen und Funktionalitäten bietet Java:
Die Klasse sunJavadoc/java/lang/Thread bildet eine Instanz eines Threads. Genaugenommen wird damit nur der äußere Zugang zur Threadimplementierung des Betriebssystems oder der Threadimplementierung der Virtuellen Maschine dargestellt.
Das Interface sunJavadoc/java/lang/Runnable kennzeichnet Anwenderklassen, die die Thread-Routine enthalten. Diese ist in diesem Interface definiert. Da die Klasse java/lang/Thread selbst Runnable implementiert, kann man auch alternativ die Klasse Thread erweitern, um eine Thread-Routine zu definieren. Man hat dann nur eine Instanz für beides.
Die Anlage der Klasse Thread sorgt noch nicht für das Vorhandensein des Threads, erst der Aufruf sunJavadoc/java/lang/Thread#start() führt zum Anstarten der Threadroutine im eigenem Thread. Der Thread läuft solange, wie die Threadroutine läuft. Er kann aber
nicht zweimal gestartet werden. Mit dem start()
wird lediglich das Aktivschalten des vorher vereinbarten Threads zu einem späteren Zeitpunkt bewirkt.
Die Methoden sunJavadoc/java/lang/Thread#stop(), sunJavadoc/java/lang/Thread#resume() und sunJavadoc/java/lang/Thread#destroy() sind als @deprecated bezeichnet. Die Java-Dokumentation erklärt warum: Wenn ein Anwender andere Threads anhält, dann ist nicht gesichert, dass diese Threads Ressourcen nicht belegen. Es geht dabei nicht um die einfache Frage von Semaphoren, dieses kann vom Betriebssystem oder der virtuellen Maschnine geklärt werden. Es geht um die Belegung von Ressourcen als Teil der Anwenderprogrammierung. Nur im betreffenden Thread selbst können sind diese Ressourcen bekannt und daher freigebbar. Ein Thread kann nur dadurch sauber abgebrochen werden, in dem das ihm per Variable, Event oder dergleichen mitgeteilt wird und er sich dann selbst abbricht.
Der gegenseitige Ausschluss des Zugriffes auf Daten (kritische Bereiche) ist mit dem Schlüsselwort synchronized
geklärt, dass sich auf eine beliebige Instanz abgeleitet von sunJavadoc/java/lang/Object bezieht. Damit ist an dieser Stelle die Bindung der Semaphore (intern verwendet) mit den betreffenden Daten anwendersichtlich
getroffen.
Die gegenseitige Abstimmung des Ablaufes von Threads meist mit Datenaustausch verbunden ist mit dem Methodenpaar sunJavadoc/java/lang/Object#wait(long) und sunJavadoc/java/lang/Object#notify() verbunden. Über diesen Weg ist ein Austausch von Events möglich, indem die wait/notify-Objecte Queues sind, beispielsweise ein sunJavadoc/java/util/LinkedList. Eine Übergabe von Events ist dann mit sunJavadoc/java/util/LinkedList#add(java.lang.Object) und ein anschließendes notify verbunden. Ein Test der Eventqueue ist als Abfrage sunJavadoc/java/util/LinkedList#size() und ein bedingtes wait() der selben Instanz möglich. Die Eventqueue ist damit im Userspace angelegt, was an sich kein Nachteil ist. Der Anwender hat in seiner Programmierung die Auswahl, wie die Queue organisiert ist. Ab der Java Version 5 kann beispielsweise auch eine sunJavadoc/java/util/concurrent/ConcurrentLinkedQueue benutzt werden.
Es sind Gruppen von Threads bildbar: sunJavadoc/java/lang/ThreadGroup.
Mit diesen Basisfeatures ausgerüstet sind in Java alle Belange von Multithreading aus dem Anwenderbereich heraus programmierbar. Nicht enthalten ist die Formulierung von Interrupts und der Übergang von Interruptroutinen in die Thread-Ebene. Das sind jedoch Probleme der Hardwareanpassung (Treiber), die nicht in die Aufgabenbereiche einer Java-Programmierung und auch nicht in die allgemeine Anwenderprogrammierung gehören.
Topic:LibJc.Threads.ThreadsInCRuntimeJavalike
Die Arbeit mit Threads ist mit der CRuntimeJavalike oder JcLib gegenüber dem speziell ausgeprägten Aufrufen der Thread-Schnittstellen eines Betriebssystems gekapselt. Die Funktionalität entspricht dabei dem System des Java. Lediglich Bezeichnungen und Aufrufkonventionen sind der Programmierung in C angepasst.
Die Kapselung enthält noch ein weiteres Layer: OSAL (Operation System Adaption Layer). Der Grund dafür ist: Nur die allgemein notwendigen Anpassungen sollen vorgenommen werden. Nebeneffekt: Es wird eine betriebssystemunspezifische OSAL-Thread-Schnittstelle bereitgestellt, die auch außerhalb der CRuntimeJavalike verwendet werden kann. Das ist wichtig, da insbesondere im Treiber- und hardwarenahem Bereich direkt oft in C programmiert wird.
Topic:LibJc.Threads.OS_Interface
Topic:LibJc.Threads.OS_Interface.OS_Thread
Topic:LibJc.Threads.OS_Interface.OS_Thread.usingOsysSupport
Eine Java-Virtuelle Maschine ist in der Lage, nebenläufige Programmabarbeitung zu bieten, ohne dass das Betriebssystem dabei Multithreading unterstützen muss. Diese Eigenschaft ist in Zeiten entstanden als Windows noch kein richtiges Multithreading konnte, zudem soll Java auf möglichst vielen Plattformen laufen und darf daher nicht zuviel vom Betriebssystem abverlangen. Die Möglichkeit dieser Arbeitsweise liegt darin begründet, dass die Threadverwaltung kooperativ auf Userlevel erfolgte oder die virtuelle Maschine macht die Threadverwaltung. ((Allerdings wird in Java-VMs die Threadunterstützung des Betriebssystems auch genutzt, wenn eine solche verfügbar ist.))
Die Arbeitsweise ist im Embedded-Bereich nicht zweckbringend. Multithreading gibt es deshalb, weil dieses Multithreading in harter Echtzeit unter den Bedingungen des Betriebssystems erfolgen muss, mit Interrupt-Einbindung. Die CRuntimeJavalike oder JcLib enthält also keinen eigenen Multithread-Kern.
Vielmehr werden Schnittstellen der OS-Anbindung aufgerufen. Die OS-Anbindung ist als allgemeingültige Schnittstelle definiert, die Implementierung dieser Schnittstelle ruft die Routinen des Betriebssystems.und muss demzufolge angepasst werden.
Topic:LibJc.Threads.OS_Interface.OS_Thread.os_thread
Das Headerfile os_thread.h.html definiert die vom Betriebssystem abverlangten Schnittstellen. Deren Implementierung für Windows befindet sich in platform_Windows/os_thread.c.html. Eine Implementierung für andere Betriebssysteme muss ähnlich aussehen.
Topic:LibJc.Threads.OS_Interface.OS_Thread.threadRoutine
Die Thread-Routine ist diejenige Subroutine, die im Thread aufgerufen wird. Der Prototyp der Subroutine ist mit dem Typ OS_ThreadRoutine
definiert. Der Routinen wird ein untypisierter Datenzeiger übergeben, dessen Struktur in der Routine dann bekannt sein muss,
es wird gecastet.
Wenn die Threadroutine beendet ist, dann wird auch der Thread beendet. Eine Variantenvielfalt, die das eine oder andere Multithread- oder Multitask-Betriebssystem bietet, ist oft nicht nutzbringend verwendbar sondern kostet nur die Portabilität. Wenn beispielsweise eine Aufgabe beendet ist, dann könnte eine Threadroutine beendet werden, aber der Thread möge nach Wunsch von Entwicklern und Leistungsfähigkeit eines Betriebssystems noch startbereit bleiben um die Aufgabe, den Thread dann erneut starten zu können. Der Thread bleibt dann faktisch erhalten, nur ist er wartend. Das gleiche kann man aber mit dieser Schnittstellendefinition auch erreichen, in dem die Threadroutine nicht beendet sondern in wait for signal des Neustartes schickt und eine übergeordnete Schleife bildet:
int threadRouine(void* data) ... do { dotheTaskOfTheThread(); os_wait(handleWaitForRestart, mutex, -1); } while(!abort)
Dann braucht man keine speziellen Betriebssystembefehle und hat eine einheitlichere Handhabung vergleichbarer Probleme. Die oft angebotenen umfangreichen Möglichkeiten eines Betriebssytems braucht man defakto nicht, sie bieten hierfür keinerlei Vorteil.
Topic:LibJc.Threads.OS_Interface.OS_Thread.createThread
Die Anlage und der Aufruf eines Threads geschieht mit Aufruf der Routine os_createThread(handle, threadRoutine, userData, name, prio, stackSize)
. Diese Routine legt einen Thread im Betriebssystem an und startet ihn sofort. Einen extra Befehl für den Threadstart würde
auch nur einen Sonderfall produzieren. Threads sollten in embedded-Systemen in der Hochlaufphase angelegt werden. Wenn die
zugehörige Aufgabe nicht sofort gestartet werden soll, dann ist es besser, in der Threadroutine innen ein waitForStart einzuprogrammieren als Besonderheiten der Betriebssystem-Threadverwaltung zu nuzten. Letzteres bietet keine Vorteile sondern
nur den Nachteil, dass eine Kontrolle des initialen ordnungsgemäßen Anlaufes aller Threads nicht erfolgt.
Der handle
wird verwendet, um von außen etwas mit dem Thread zu tun. Ein OS_HandleThread
ist als Zeiger auf eine unbekannte Struktur definiert und hat damit die Byteanzahl eines Zeigers. Es kann der Zeiger auf
einen systeminternen Datenbereich sein, es kann sich aber auch nur um eine einfache Zahl handeln. Das kann implementierungsspezifisch
sein. Die Verwendung eines Zeigertyps statt int
ermöglicht dem Compiler den Typtest. Werden alle handle, egal welche, mit int
typisisert,dann wird ein Verwechslungsfehler beispielsweise zwischen Handles von Threads und derjenigen von Files nicht bemerkt.
Die Threadroutine muss der obigen Definition entsprechen, sonst gibt es einen Zeigertypfehler beim compilieren.
Der Aufbau des dem Thread zugeordneten Datenbereiches kann aber nicht vor bestimmt werden. Es ist keine Verallgemeinerung
ableitbar, daher void*
.
Ein Threadname dient der Anzeige von Betriebssystemaktivitäten und hat keinen Einfluss auf den Softwarelauf.
Die Priorität ist initial und kann im Verlaufe des Threads geändert werden. Eine solche Änderung ist notwendig in einer critical section, wenn ein hochpriorer Thread wartet. Diese Eigenschaft muss von einem Multithread-Betriebssystem unterstützt werden,sonst taugt es nichts. Eine manuelle Prioritätsänderung ist vorgesehen, das ist allerdings eher Kür. Die Priorität wird hier mit einer Zahl zwischen 1 und 254 angegeben. Damit ist eine feinfühlige Priotitätsstaffelung möglich, die von Multithreadbetriebssystemen in dieser Feinausprägung nicht unbedingt unterstützt wird. Eine Prioritätsfestlegung muss sehr gewissenhaft erfolgen und kann gegebenenfalls im Verlaufe der Entwicklung noch feinjustiert werden.
Die Angabe einer Stackgröße ist der kritischste Parameter, da oft nicht die notwendige Stackgröße bekannt ist. Dieses Problem ist aber im Themenkreis der Multithread-Anwendungen bekannt.
Topic:LibJc.Threads.OS_Interface.ThreadContext
Ein User-Threadcontext ist ein Speicherbereich, der außerhalb des OSAL beliebig definiert ist, in diesem Fall innerhalb der CRuntimeJavalike oder JcLib - Implementierung.
Für die OSAL-Schnittstelle bedeutet das zunächst nur, dass der Zeiger auf den User-Threadcontext und dessen Länge in der OSAL-Schicht vermerkt wird, und zwar im OS_ThreadContext oder anders geeignet. Die Verwendung des OS_ThreadContext ist kein muss, sondern eine Implementierungseigenschaft der vorliegenden OSAL-Implementierungen. Dieser Speicherbereich kann sowohl im geschützten Bereich des Betriebssystems liegen als auch in dem OSAL-Layer. Die Implementierung hängt von den Möglichkeiten des RTOS ab. Dieser Thread-Context ist OSAL-intern.
Der User-ThreadContext wird dem OSAL mitgeteilt durch die Methode os_setCurrentUserThreadContext(mem)
definiert in OSAL/inc/os_Thread.h. Dabei wird in einer Struktur OS_ValuePtr
definiert in der os_types_def.h
Zeiger und Länge übergeben. Das Abfragen geschieht dann mit os_getCurrentUserThreadContext()
.
Topic:LibJc.Threads.OS_Interface.OS_Mutex
Topic:LibJc.Threads.OS_Interface.OS_Mutex.generally
Kritische Bereiche (crtitical sections) sind diejenigen Code-Stellen, in denen eine Unterbrechung von anderen Threads Dateninkonsitenzen bewirken können bis zum Hängenbleiben des Systems. Der Begriff Gegenseitiger Ausschluss (mutual exclusion) eines Zugriffes ist hier noch prägnanter. In einfachen auf Interrupts basierenden embedded Programmierungen gibt es dazu die globale Interruptsperre, die einfach aber hilfreich ist. Fehler einer vergessenen Freigabe wirken sich allerdings fatal aus. Außerdem sind längere Interruptsperren ungünstig für die schnellen Interrupts. Daher ist in Multithread-Betriebssystemen die Interuptsprerre dem Betriebssytem-Kern vorbehalten. Für die Synchronisierung der Treads für den Mutex-Zugriff gibt es selektiv wirkende Mechanismen
Topic:LibJc.Threads.OS_Interface.OS_Mutex.os_mutex
Auf OSAL-Ebene sind in der os_sync.h.html die Aufrufe os_lockMutex(...)
und os_unlockMutex(...)
vorgesehen. Dazu ist ein Handle vom Typ OS_Mutex
notwendig, das wie ein OS_HandleThread
eine Zeiger auf eine nicht näher definierte struct
ist, hinter der sich auch ein einfaches int
als Handle aus dem Betriebssystem verbergen kann. Die notwendigen Daten für ein OS_Mutex
sind gegebenenfalls bzw. meist betriebssystemintern. Um ein solches Handle zu bekommen, ist ein Aufruf von os_createMutex(name, dst)
notwendig. Der übergebene Name dient zu Debugzwecken mit dem RTOS.
Topic:LibJc.Threads.OS_Interface.OS_WaitNotify
Topic:LibJc.Threads.OS_Interface.OS_WaitNotify.generally
Als Synchronisierung wird oft das gegenseitige Warten von Threads wegen Mutex bezeichnet, auch in Java ist das mit dem Schlüsselwort synchronize
gekennzeichnet. Synchronisierung im normalem Sprachgebrauch ist aber weiterführend: Mehrere Threads müssen irgendwie geartet
gleichlaufend Daten und Ereignisse behandeln. Implementierungstechnisch ist das mit einem Warten (wait) und Anzeigen (notify) verbunden, was denn auch die beiden Schlüsselworte dafür in Java sind.
Topic:LibJc.Threads.OS_Interface.OS_WaitNotify.os_wait
Auf OSAL-Ebene sind in der os_sync.h.html die Aufrufe os_wait(...)
und os_notify(...)
vorgesehen. Dazu ist ein Handle vom Typ OS_HandleWaitNotify
notwendig, das wie ein OS_HandleThread
eine Zeiger auf eine nicht näher definierte struct
ist, hinter der sich auch ein einfaches int
als Handle aus dem Betriebssystem verbergen kann. Die notwendigen Daten für ein OS_HandleWaitNotify
sind gegebenenfalls bzw. meist betriebssystemintern. Um ein solches Handle zu bekommen, ist ein Aufruf von os_createWaitNotifyObject(name, dst)
notwendig. Der übergebene Name dient zu Debugzwecken mit dem RTOS.
Topic:LibJc.Threads.OS_Interface.OS_WaitNotify.os_wait_Mutex
Ein wait(...)
und notify(...)
funktioniert nur zusammen mit einem Mutex. Das liegt daran, dass Daten, die mit dem wait(...)
und notify(...)
erwartet und übergeben werden, unter Mutex gelesen und gespeichert werden müssen. Ansonsten kann es beispielsweise vorkommen,
dass ein wait(...)
wegen nicht vorhandener Daten ausgelöst wurde, während dessen zufällig ein Threadwechsel stattfindet, mit dem die Daten eintreffen.
Da die Daten in dem Moment aber noch keiner erwartet, wird kein notify(...)
gerufen, es ist nicht bekannt wer notifiziert werden soll. Danach erfolgt die Weiterarbeit im unterbrochenem Thread, der
zum warten führt, und zwar unendlich lange, obwohl oder gerade weil die Daten schon da sind. Diese Aufrufe mit einem Mutex
zu verbinden, der vor dem wait(...)
oder notify(...)
gesetzt wird, von der internen Abarbeitung im wait(...)
bzw notify(...)
gelöst wird, bevor der Thread schlafengelegt wird bzw. notifiziert und beim Austritt intern wieder gesetzt wird, da vom Anwender
danach gelöst, ist der allgemeine immer funktionstüchtige Ansatz. Eine Programmierung ohne diesen Mechanismus, wie er mit
einer einfachen Semaphorenbehandlung möglich ist, ist nur dann sicher, wenn bekannt ist welcher Thread wartet und wenn es
garantiert ein sauberes Wechselspiel von wait und notify gibt. Nur dann kann man eine Semaphore bei notify setzen auch wenn
die zugehörige Semaphorenabfrage noch nicht geschehen ist, um die Tatsache zu nutzen dass ein Thread bei der Semaphorenabfrage
nicht stehenbleibt wenn diese gesetzt ist. Wenn es kein garantiertes sauberes Wechselspiel gibt, es also ein quasi nicht vorgesehenes
notify geben könnte, wenn ein anderer Thread noch gar nicht im Abfragezustand ist, dann wird bei der nächsten Abfrage der
Thread nicht warten, weil alte Daten vorliegen. Auf diese Problematik sei an dieser Stelle deshalb so deutlich hingewiesen,
weil das wait/notify-Schema oft mit der einfachen Semaphorenabfrage gelöst wird, wenn manuell in C programmiert wird. Diese
einfache Semaphorenabfrage ist daher in der CRuntimeJavalike oder JcLib und in der zugehörigen OSAL-Schnittstellendefinition
nicht vorgesehen, weil sie zu unischerer Programmierung verleitet.
Topic:LibJc.Threads.OS_Interface.os_ErrorHandling
Topic:LibJc.Threads.OS_Interface.os_ErrorHandling.generally
In der Regel sind die Aufrufe von RTOS-Routinen so gebaut, dass bei Nichtausführbarkeit ein Fehler-Code zurückgegeben wird. Nur in extremen Fällen wird ein RTOS selbst eine Fehlermeldung absetzen und die Weiterarbeit verweigern. Fehler können beispielsweise dann auftreten, wenn die Anzahl der Threads erschöpft ist oder der übergebene Name bereits vorhanden ist. Das aufrufende Programm, muss immer den Fehlercode auswerten und aus RTOS-Sicht irgendwie geeignet reagieren.
Für die OSAL-Schnittstelle nach außen (zur CRuntimeJavalike, zum User) gelten folgende Regeln:
Die Aufrufe der OSAL-Routinen liefern einen negativen Fehlercode oder einen positven Wert im Erfolgsfall zurück. Der positive Returnwert ist meist 0. Ist er nicht 0, dann sollte dies als Warnung mindestens beim Debuggen berücksichtigt werden oder geeignet gemeldet werden.
Bei einem negativem Fehlercode ist die entsprechende Aktion nicht ausgeführt.
Die Fehlercodes sollen nicht programmtechnisch ausgewertet werden. Sie dienen nur der Information für gegebenfalls erzeugte Log-Meldungen. Für den Aufruf einer OSAL-Routine gibt es also nur die Aussage erfolgreich oder nicht.
Im OSAL-Layer, bei geeigneter Gestaltung des RTOS auch dort kann eine Fehlerroutine os_Error(text, value, value)
gerufen werden, die User-beeinflussbar ist, siehe os_setErrorRoutine(...)
. Dieser Routine wird eine textuelle Information und zwei int-Werte übergeben, womit genauere Informationen über die Fehlerursache
bekannt sind. Die User-Implementierung dieser Routine kann so ausgestaltet sein, dass direkt das Exceptionhandling der CRuntimeJavalike
definiert in Fwc/fw_Excption.h gerufen wird.
Topic:LibJc.Threads.OS_Interface.os_ErrorHandling.throwException
Das Exceptionhandling wie es in Exception_Jc.html beschrieben ist kann sowohl in der OSAL-Schicht als auch bereits aus der RTOS-Schicht heraus und dort auch in Ausnaheminterrupt-Behandlungen
gerufen werden. Da das Exceptionhandling in diesem Layer aber unbekannt ist sondern zu einem darüberliegenden Layer (Fwc)
gehört, erfolgt kein direkter Aufruf. Stattdessen ist eine Schnittstelle als Routine os_Error(text, value1, value2)
in OSAL/src/os_internal_common.h
definiert. In dieser Routine erfolgt ein callback (über Funktionspointer) auf die Routine, die mit os_setErrorRoutine(...)
(OSAL/inc/error.h) übergeben wurde. Von der JcLib wird dort eine Routine eingetragen, die ein THROW
erzeugt, das im Exceptionhandling abgefangen wird. Dieser Umweg ist deshalb notwendig, weil sonst eine Verletzung der Layermodell-Regel
stattfindet, nach der nie eine Routinen eines oberen Layers von unten gerufen werden darf. Diese Verletzung würde sich darin
äußern, dass die OSAL-Schicht mit dem RTOS nicht mehr eigenständig link- und testbar ist.
Topic:LibJc.Threads.OS_Interface.os_ErrorHandling.RTOS_ExceptionInterrupt
Wenn aufgrund einer Ausnahmesituation von der CPU ein Ausnahme-Interrupt gerufen wird, beispielsweise wegen Division durch 0 oder null-Pointer, dann wird diese reine Interrupt-Routine den Stack
des laufenden Threads benutzen. Der Interrupt muss quittiert werden. Wenn danach der Aufruf von os_error(text, value1, value2)
programmiert ist, dann wird nichts ausgeführt wenn keine User-(Exception-) -Routine gesetzt ist. Da aber ein zu behandelnter
Fehler vorliegt, ist meist vorgesehen, dass das System dann in den Stop geht. System-Halt wegen Division durch 0 oder null-Pointer.
Kennt sicherlich jeder. Eine andere Möglichkeit wäre eine Notation der Fehlersituation (irgendeine betriebssystemnahe Queue)
und beenden des Interrupts. In diesem Fall läuft der Thread weiter mit einem fehlerhaftem Wert. Entweder Folgefehler werden
erzeugt, oder das Programm hat falsche Daten. Alle diese Varianten sind eigentlich nicht tragbar. Situationen der Ausnahme,
die zu einem CPU-Interrupt führen, kommen aber in der Praxis während der Programmentwicklung häufig vor.
Wenn die os_error(...)
-Routine aber vom Exceptionhandling gesetzt ist, dann gilt folgendes:
Der CPU-Interrupt benutzt den Stack des letzten Threads, das ist genau der Thread, der für die Auslösung der CPU-Exception verantwortlich war.
Der Interrupt muss bereits quittiert worden sein! Das Interruptprogramm läuft noch. Ein Eintritt in den selben Interrupt ist dann theoretisch möglich, aber wird nicht ausgelöst.
Über den throw-Aufruf bzw. longjmp in C wird auf den möglichen try-Punkt gesprungen, genauso wie bei einer Exception im Thread. Da der selbe Stack verwendet wird, ist das zulässig.
Damit erfolgt die Exceptionbehandlung genauso wie bei anderen User-Exceptions: Der auslösende Programmblock ist abgebrochen und die catch-Klauseln werden durchlaufen.
Diese Behandlung erfordert, dass die CPU-Exceptioninterrupts entsprechend programmiert werden.
Topic:LibJc.Threads.ErrorHandling
Die Strategie der CRuntimeJavalike oder JcLib ist das Exceptionhandling. Dieses ist auf diesem Layer bereits definiert und soll daher auch genutzt werden. Damit führen Fehler-Rückgabewerte in den OSAL-Threadroutinen in der CRuntimeJavalike zu einer Exception, wie es auch in Java ist. Der Vorteil ist: Der Anwender braucht sich nicht um die Fehlerbehandlung zu kümmern. Es sind meist nicht erwartete Fehler, die also auch kaum abgefangen werden müssen. Ein weiterer Vorteil ist: Der Anwender kann eine Auswertung eines Rückgabecodes nicht vergessen, da es diesen gar nicht gibt. Damit werden Fehler in jedem Falle erkannt und erzeugen keine Folgefehler.
Topic:LibJc.Threads.ThreadJc
Topic:LibJc.Threads.ThreadJc.content
Die Klasse docLibJc:ThreadJc enthält nur einige wenige Werte, die den Thread äußerlich beschreiben. Die wichtigsten Daten sind:
target
: Referenz zu einer Instanz der Aktiven Klasse des Threads, das ist diejenige, die das Interface docLibJc:RunnableJc implementiert. Diese Aggregation ist null
, wenn die Aktive Klasse auf ThreadJc
basiert
hThread
: Dieser Zeiger speichert den Handle des Threads aus dem RTOS (OSAL). Ist dieser Wert null
, dann ist zwar eine Instanz von ThreadJc angelegt, aber noch kein Thread im RTOS gestartet.
Eine Instanz von ThreadJc
kann die Basis der Aktiven Klasse bilden, oder auch nur eine relativ kleine Verwaltungsinstanz des Threads sein, je nachdem
ob die Aktive Klasse auf ThreadJc
basiert oder stattdessen 'nur' das Interface RunnableJc
implementiert. Das sind dieselben beiden Wahlmöglichkeiten wie in Java. Die zweite Möglichkeit ist dann notwendig, wenn die
Aktive Klasse eine andere Klasse erweitert. Der Begriff Aktive Klasse ist wie in der UML gemeint: Instanzen von Aktive Klassen bilden einen eignenen Thread. Die aktive Klasse enthält die Methode
run()
, die in RunnableJc
definiert ist. Diese Methode ist die main-Methode des aktivierten Threads. Solange diese Methode abgearbeitet wird, existiert
der Thread. Die Auswahl, ob ThreadJc die Basis der aktiven Klasse ist oder nur ein Helfer, wird mit dem Konstruktor geklärt:
ctorO_Runnable_s_ThreadJc(othis, runnable, name,...)
: Es wird eine RunnableJc
-Instanz angegeben, die als target
dann aggregiert ist.
ctorO_s_ThreadJc(othis, name,...)
: Keine RunnableJc
-Instanz, also muss die run()
-Methode der ThreadJc
überladen werden. ThreadJc
basiert ebenfalls auf RunnableJc
.
Der Aufruf von start_ThreadJc()
testet dann, ob target.run() als Thread-Routine angegeben wird oder die eigene run()
. Diese Methode ruft dann os_createThread(...)
auf.
Topic:LibJc.Threads.ThreadJc.stackSize
In Java wird keine StackSize für einen Thread angegeben. Die Java-VM verwaltet dies selbst. In der VM ist es grundsätzlich auch möglich, den Stack eines Threads umzulagern und dabei zu vergrößern, wenn eine Grenze erreicht ist.
Letzteres ist bei schnellen Echtzeitsystemen nicht möglich, da eine Umorganisation Zeit benötigt. Die Wahl der Stackgröße ist eine Aufgabe, bei der nach Erfahrungen abgeschätzt werden muss, wieviel notwendig ist. Dieser Wert hängt von verschiedenen Variationen des Programmlaufes ab und hat im Nichtfehlerfall ein Maximum, das aber leider nirgends direkt ablesbar ist.
Für schnelle Echtzeitverarbeitung ist die Vorgabe einer Stackgröße notwendig. Man kann sich mit einem Defaultwert aus der Unkenntnis retten, oder einen recht hohen Wert angegeben, um auf der sicheren Seite zu sein. Es kommt darauf an, ob Speicher dafür knapp ist.
Entsprechend den Darstellungen in $chapter ist es möglich, im Stack auch große Instanzen anzulegen. Deren Gesamtgröße ist selbstverständlich bei der Kalkulation der Stackgröße zu berücksichtigen. Man wird dann meist nicht mehr mit dem Defaultwert hinkommen.
Die Stackgröße wird immer mit der Methode start_ThreadJc
angegeben. Der Defaultwert gilt, wenn 0 oder kleiner 0 (-1) parametriert wird. Da die Angabe in einem Java-Programm grundsätzlich
nicht erfolgt, wird dies als Sonderfall einer Java2C-Translation berücksichtigt. Man kann bei Java2C die Stacksize als Annotation
@java2c=stackSize(value).
angegeben. Ohne diese Angabe wird dann auch dort der Defaultwert benutzt.
Topic:LibJc.Threads.ThreadJc.dynCall
Der Aufruf der richtigen, überladenen Routine für run() geschieht mit den Mitteln des dynamischen Bindens, wie es in dynCall.html dargestellt ist.
Topic:LibJc.Threads.synchronized
Topic:LibJc.Threads.synchronized.idSyncHandles
Jedes Object hat die Möglichkeit der Nutzung eines Mutex. Die Idee, eine Mutex-handle nicht extra zu führen sondern an die
Basisklasse Object
zu binden, ist eine Java-Idee für eine semantisch sichere Programmierung. Es besteht zwar immer noch die Verwechslungsmöglichkeit
wie
synchronized(object1) { objectOther->access(); }
aber solche Fehler fallen bei einem formellen Review bzw. beim normalen Durchschauen der Programmierung auf.
In einem ObjectJc
sind nun für diesen Fall nur 2 Bytes spendiert, für einen 16-bit-Index. Damit ist die Tatsache beachtet worden,dass die meisten
Instanzen nicht für synchronized
benutzt werden. Der Index steht zunächst auf -1. Erst wenn die Routine synchronized_ObjectJc(instance)
gerufen wird, dann wird ein Mutex im RTOS angelegt. Dessen Handle wird in einer Tabelle data_OsWrapperJc.handleItemsJc[...]
gespeichert. Der Index auf dieses Array wird dann in ObjectJc::idSyncHandles
vermerkt. Die Größe dieser Tabelle ist fest und auf den Anwendungsfall zuzuschneiden. Allerdings kann man hier reichlich
bemessen, da pro Eintrag 8 Byte benötigt werden und die Anzahl der notwendigen Mutex-Handle eher meist nur zweistellig ist.
Nach dem ersten Aufruf von synchronized
ist das Handle für den Mutex des RTOS fest mit dem Object verbunden und bleibt solange erhalten, bis das Object gelöscht
wird.
Topic:LibJc.Threads.synchronized.synchroizedViaMPU
Es gibt eine andere zweckmäßige Lösung, die für Anwendungen der sicherheitsrelevanten Programmierung wichtig sein könnte:
Ein Object wird in einem Speicherbereich angelegt, der vom unterlagertem RTOS verwaltet wird. Ein Zugriff darauf wird mit
einer MPU (Memory Protection Unit) als Teil der CPU geschützt. Der Zugriff ist nur möglich, wenn synchronized_ObjectJc(object)
gerufen wird. In diesem Fall wird vom RTOS geprüft, ob das Object frei ist, und dessen Adressbereich wird in der MPU dann
vom RTOS freigeschaltet bis endSynchronized...
gerufen wird. Es kann dann in dieser Zeit kein anderer Thread auf diesem Object arbeiten, insbesondere nicht mit unsynchronisierten
Zugriffen. Damit werden Programmierfehler des unsynchronisierten Zugriffes zur Laufzeit entdeckt und sicher vermieden.
Topic:LibJc.Threads.synchronized.time_nested
Die Nutzungs-Zeit eines synchronized-Zugriffes auf ein Object kann in der Regel jeweils sehr kurz sein, es kann sich aber
auch um eine längere Zugriffsrechtvergabe handeln, beispielsweise für Berechnungen jeweils mit Warten auf Außenkommunikation.
Alle anderen Threads, die auf dieses Object zugreifen, werden dann angehalten. Wenn aber ein anderweitiger Zugriff nicht stattfindet,
dann ist der längere Zugriff statthaft. Ähnlich ist es mit geschachtelten synchronized jeweils auf verschiedene Objects. Diese
sind dann statthaft wenn funktional gerechtfertigt und wenn eine Blockade ausgeschlossen ist. Ansonsten kann so ein geschachteltes
synchronized
allerdings sehr schnell zu einem Deadlock führen. Das geschachtelte Aufrufen eines synchronized
auf das selbe Object vom selben Thread ist dagegen immer möglich. Diese Dinge sind in Java genauso gültig wie in der CRuntimeJavalike
oder JcLib.
Topic:LibJc.Threads.waitNotify
Topic:LibJc.Threads.waitNotify.idSyncHandles
Für wait und notify ist ein OS_HandleWaitNotify
des RTOS notwendig. Dieses wird zusammen mit dem OS_Mutex
-handle in derselben Tabelle niederlegt und gemeinsam mit dem ObjectJc::idSyncHandles
-Element indiziert. Das Handle wird im RTOS angelegt beim ersten Aufruf von wait oder notify.
UML=/Package[ThreadJc_h]/Namespace.ownedElement[]/Class[RunnableJc_s]
Class RunnableJc_s im Package ThreadJc_h
UML=/Package[ThreadJc_h]/Namespace.ownedElement[]/Class[RunnableJc_s]
+-------'-------'-------'-------'-------'-------'-------'-------+
| base |
+-------'-------'-------'-------'-------'-------'-------'-------
Inner class RunnableJc_s.Mtbl_ definiert in ThreadJc_h/RunnableJc_s
UML=/Class[RunnableJc_s]/Namespace.ownedElement[]/Class[Mtbl_]
+-------'-------'-------'-------'-------'-------'-------'-------+
| head |
+-------'-------'-------'-------+-------'-------'-------'--...8+
| run | ObjectJc |
+-------'-------'-------'-------'-------'-------'-------'------
UML=/Package[ThreadJc_h]/Namespace.ownedElement[]/Class[ThreadJc_s]
Class ThreadJc_s im Package ThreadJc_h
UML=/Package[ThreadJc_h]/Namespace.ownedElement[]/Class[ThreadJc_s]
+-------'-------'-------'-------'-------'-------'-------'-------+
| base |
+-------'-------'-------'-------'-------'-------'-------'-------+
| hThread |
+-------'-------'-------'-------'-------'-------'-------'-------+
| name |
+-------'-------'-------'-------'-------'-------'-------'-------+
| target |
+-------'-------'-------'-------+-------'-------'-------'-------+
| stackSize | state |
+-------'-------'-------'-------+-------+
| nPriority |sCalledName|
+-------'-------'-------'-------'-------
Ident of the thread, to check the correctnis (debugmodi).
Name of the thread.
The pointer to the associated Runnable Object, contains the run()-method. This pointer will be set only in the constructor, if an associated Runnable-object is given. The pointer may be null, if the run-method of this class is overridden.
The given stackSize.
State of the thread.
The priority of thread is able to adjust.
pointer to the Stack with name of the actual called method saved on Thread switch
Inner class ThreadJc_s.Mtbl_ definiert in ThreadJc_h/ThreadJc_s
UML=/Class[ThreadJc_s]/Namespace.ownedElement[]/Class[Mtbl_]
+-------'-------'-------'-------'-------'-------'-------'-------+
| head |
+-------'-------'-------'-------'-------'-------'-------'-------+
| ObjectJc |
+-------'-------'-------'-------'-------'-------'-------'-------+
| RunnableJc |
+-------'-------'-------'-------'-------'-------'-------'-------
returns: -
Finalize declaration. It is called by Garbage collector and inside other finalized methods. It should be called by the user if the instance is removed.
returns: void -
OTHIS: -
THCXT: -
returns: -
OTHIS: -
NAME: -
THCXT: -
returns: -
OTHIS: -
NAME: -
THCXT: -
returns: -
OTHIS: -
RUN: -
NAME: -
THCXT: -
returns: -
pRunnable: RunnableJc_s -
pName: StringJc -
returns: ThreadJc_s -
THCXT: -
returns: -
newPriority: int -
returns: void -
returns: int -
stackSize: int -
returns: void -
The original virtual method run of class Thread, used for an immediate non-dynamic call.
returns: void -
The call of run, executes a dynamic call of the override-able method.
returns: void -
Tests if this thread is alive. A thread is alive if it has
been started and has not yet died.
': ThreadJc_s -
returns: bool - <code>true</code> if this thread is alive;
<code>false</code> otherwise.
': ThreadJc_s -
returns: void -
milliseconds: int32 -
returns: void -
javalike:
returns: ThreadJc_s -
CONST-Initializer sind Makros für C-Konstanten, sie sind in Headerfiles definiert. C-Konstante sind Konstrukte in { ... } mit ausschließlich konstanten Werten. Die Makros der CONST-Initializer übernehmen Argumente, die zur Compilezeit die Konstanten bestimmen. Die Initializer sind deshalb als Makros definiert, weil die Struktur der Konstanten exakt der struct-Definition folgen muss, die struct ist im Headerfile definiert, daher auch dazu passend diese Makros. Mit denCONST-Intializer ist es auf einfache Weise möglich, C-struct-Daten (Plain Old Data) zu initialisieren. Für Klassen (C++) ist dieses Konzept nicht geeignet.
RUNNABLE -
Constant in form {...} defines a Thread with associated Runnable class
RUNNABLE -
NAME -