Inhalt
• Antrieb | Abtrennung der Steuerlogik |
• Hilfsmittel | Wie hilft mir Ada? |
• Kodierung | Wie geht es weiter? |
• Test | Funktioniert es denn auch? |
• Quellcode | Zum Weitermachen |
• Compiler | Das perfekte Werkzeug |
• Literatur | Das gute, alte Buch |
Antrieb
Trennung von Steuerlogik und Funktionalität
Wenn das Projekt ein Langläufer ist, werden dadurch auch harte Rahmenbedingungen gesetzt, etwa die (fast) ausschließliche Verwendung der Programmierspravche Ada der Ausbaustufe 1995. Ada hat sich aber sehr harmonisch weiter entwickelt, es gibt den Sprachstandard 2005 und 2012. Da sollte man sich doch ein wenig im neuen Terrain umschauen ...
In einem Programmsystem gibt es meist mehr oder weniger viele Aktivitäten, die periodisch ausgeführt werden müssen, so etwa das Auslesen und Auswerten von Sensordaten. Und meist gibt es verschiedene Betriebsmodi, in denen etwa die Sensordaten unterschiedlich oft ausgelesen werden müssen.
Es liegt nahe, die periodischen Aktivitäten durch nebenläufige Ada-Tasks abzubilden, deren Rumpf typischerweise wie folgt aufgebaut ist: In der Zeile 135 wird eine absolute Zeit Next_Time berechnet und in der Zeile 136 wird dann mittel delay until gewartet, bis die Zeit zur Ausführung der Aktivität in Zeile 138 gekommen ist.
Eine mit 'delay until' zeitgesteuerte Task
So weit, so gut. Die Aktivität wird durch ein aktives Objekt implementiert, welches warten kann - wie lange gewartet werden soll, ist der Aktivität aber egal. Die Berechnung der Wartezeit hat in dem aktiven Objekt eigentlich nichts zu suchen.
Und was wäre wenn sich die Periode, hier Storage_Duration, bei einem Moduswechsel ändert? Eigentlich entstünde kein großartiges Problem, aber das aktive Objekt würde mit weiterer, noch mehr Steuerlogik befrachtet. Eine häßliche Sache - finde ich.
Was ist zu tun? Wir trennen die Steuerung des Ablaufverhaltens von der auszuführenden Aufgabe! Der obige Task-Rumpf müsste etwa wie das folgende Muster aussehen: Mittels der Zeile 19 wird die Task nach Wunsch blockiert, Einzelheiten interessieren hier nicht, die auszuführende Aufgabe wird durch eine Prozedur implementiert, die in Zeile 21 aufgerufen wird. Wunderbar und schlicht und schön.
Die Details der Zeitsteuerung sind ausgelagert!
Hilfsmittel
Wie hilft mir die Programmiersprache?
Schon Ada95 definiert im Anhang C System Programming zwei Pakete, die hier Verwendung finden können:
- Ada.Task_Identification
- Ada.Task_Attributes
Das Paket Task_Identification erlaubt es, existierende Tasks zu identifizieren, um sie etwa abzuschießen:
Paketkopf Ada.Task_Identification (Ada2012)
Das generische Paket Task_Attributes gibt dem Programmierer die Möglichkeit, durch Ausprägen dieses Paketes beliebige nutzerdefinierte Daten an identifizierte Tasks zu binden - und zwar mithilfe der Prozedur Set_Value, mithilfe der Prozedur Value können diese Daten dann wieder ausgelesen werden.
Paketkopf Ada.Task_Attributes (Ada2012)
Es liegt nahe, dieses Paket zu verwenden, um den periodischen Tasks ihr gewünschtes Zeitverhalten anzuheften.
Weiter verwende ich die neue Möglichkeit, Tasks mit Sprachmitteln CPU-Kerne zuzuordnen zu können, und zwar über das Ada2012-Paket Multiprocessors (Mps):
- System.Multiprocessors
Paketkopf System.Multiprocessors (Ada2012)
Kodierung
Wie geht es weiter?
Die periodischen Aktivitäten werden über einen Task-Typ implementiert. Es wird explizit ein Eingang Start spendiert. Für jedes Task-Objekt werden als Diskriminanten übergeben
- ein Zeiger auf die Prozedur Tasked_Procedure_Access,
- die Priorität der Task und
- der CPU-Kern, auf dem die Task ausgeführt werden soll.
Die periodischen Tasks als Task-Typ
Die Perioden und die nächsten 'Abfahrtszeiten' der Task-Objekte werden über einen Verbund- oder Rekordtypen Task_Information_Record_Type zusammengefasst (Zeilen 9-14), mit dem auch gleich eine Ausprägung des Ada-Paketes Ada.Task_Attributes zur Paketinstanz Periodic_Task_Attributes vorgenommen wird (Zeilen 20-22).
Scheduling-Informationen und Paketinstanz
Schnittstelle I
Die engere Anwendungsschnittstelle für ein einzelnes periodisches Taskobjekt liefert das Paket Periodic_Task_Scheduler. Es wäre natürlich wünschenswert, alle periodischen Tasks gleich als Verband ansprechen zu können - ein nächster Schritt zu einem wirklichen Baustein.
Paketkopf Periodic_Task_Scheduler
Mit Set_Characteristic (Zeilen 11-14) werden einer bekannten Task die Scheduling-Daten mitgeteilt, mit Abort (Zeile 20) kann die Task hart beendet werden und mithilfe der blockierenden Prozedur Wait_Until_Next_Schedule (Zeile 16) kann eine Task fremdgesteuert auf ihre nächste Abfahrtzeit warten.
Schnittstelle II
Die weitere Anwendungsschnittstelle ist noch verbesserungswürdig. Hier fasse ich im Paket Many_Periodic_Tasks_Template die relevanten Daten der periodischen Task zu einem Verbundtypen zusammen:
Alles zusammengepackt!
Alle periodischen Tasks können nun in einem Rutsch verwaltet werden - ein Beispiel:
Lohn der Mühe
Test
Ja, funktioniert es denn auch?
Es hat den Anschein. Ich habe zwei Testprogramme spendiert, eines, periodic_task_scheduler-test.adb, zum Antesten der Anwendungsschnittstelle I und eines, periodic_worker_system-main.adb, zum Antesten der Anwendungsschnittstelle II.
Hier läuft das zweite Testprogramm mit drei periodischen Tasks, die nur dumme Ausgaben machen. Beim Moduswechsel vertauschen die Tasks 1 und 3 ihre Scheduling-Zeiten - man sieht die Wirkung ...
Das ultimative Testergebnis
Zur weiteren Analyse könnte ich mein großartiges Logs_To_Ram verwenden :-)
Quellcode
Hinweis: Die Zeilennummern stimmen nicht unbedingt mit denen in den obigen Code-Ausschnitten überein.
• Der Quellcode, gezippt Mein GNAT-Projekt zum Weitermachen
Ada-Compiler
• GNAT-Ada-Compiler von AdaCore
Literatur
Das gute, alte Buch
John Barnes
Programming in Ada 2005
Addison-Wesley, 2006
Alan Burns, Andy Wellings
Concurrent and Real-Time Programming in Ada 2005
Cambridge University Press, 2007
Dank an die beiden Herren!
Norman H. Cohen
Ada as a second language
- based on Ada 95
The McGraw-Hill Companies, 1996