
In einem
früheren Artikel haben wir uns mit den verschiedenen vom RTOS unterstützten Planungsarten und den damit verbundenen Funktionen in Nucleus SE befasst. In diesem Artikel werden zusätzliche Planungsoptionen in Nucleus SE und der Prozess zum Speichern und Wiederherstellen des Kontexts erläutert.
Frühere Artikel in der Reihe:
Artikel 9. Scheduler: ImplementierungArtikel 8. Nucleus SE: Internes Design und BereitstellungArtikel 7. Nucleus SE: EinführungArtikel 6. Andere RTOS-DiensteArtikel 5. Aufgabeninteraktion und SynchronisationArtikel 4. Aufgaben, Kontextwechsel und InterruptsArtikel 3. Aufgaben und PlanungArtikel 2. RTOS: Struktur und Echtzeitmodus
Artikel 1. RTOS: Einführung.
Optionale Funktionen
Während der Entwicklung von Nucleus SE habe ich die maximale Anzahl von Funktionen optional gemacht, was Speicher und / oder Zeit spart.
Aufgaben aussetzen
Wie bereits im
Artikel "Scheduler: Implementierung" erwähnt , unterstützt Nucleus SE verschiedene Optionen zum Anhalten von Aufgaben. Diese Funktion ist jedoch optional und wird durch das Symbol
NUSE_SUSPEND_ENABLE in
nuse_config.h enthalten . Bei
TRUE wird die Datenstruktur als
NUSE_Task_Status [] definiert . Diese Art der Aussetzung gilt für alle Aufgaben. Das Array ist vom Typ
U8 , wobei 2 Halbbytes separat verwendet werden. Die unteren 4 Bits enthalten den Status der Aufgabe:
NUSE_READY, NUSE_PURE_SUSPEND ,
NUSE_SLEEP_SUSPEND ,
NUSE_MAILBOX_SUSPEND usw. Wenn eine Aufgabe durch einen API-Aufruf angehalten wird (z. B.
NUSE_MAILBOX_SUSPEND ), enthalten die hohen 4 Bits den Index des Objekts, für das die Aufgabe angehalten wird. Diese Informationen werden verwendet, wenn die Ressource verfügbar wird. Um die API aufzurufen, müssen Sie herausfinden, welche der angehaltenen Aufgaben fortgesetzt werden müssen.
Um die Task-Suspendierung durchzuführen, werden zwei Scheduler-Funktionen verwendet:
NUSE_Suspend_Task () und
NUSE_Wake_Task () .
Der
NUSE_Suspend_Task () - Code lautet wie folgt:

Die Funktion speichert den neuen Status der Aufgabe (alle 8 Bits), der als Parameter suspend_code erhalten wird. Wenn Sie das Sperren aktivieren (siehe "API-Aufrufe sperren" weiter unten), wird der
NUSE_SUCCESS- Rückkehrcode
gespeichert . Als nächstes wird
NUSE_Reschedule () aufgerufen, um die Steuerung auf die nächste Aufgabe zu übertragen.
Der Code
NUSE_Wake_Task () ist recht einfach:

Der Status der Aufgabe wird auf
NUSE_READY gesetzt . Wenn der Prioritätsplaner nicht verwendet wird, belegt die aktuelle Aufgabe weiterhin den Prozessor, bis die Zeit zum Freigeben der Ressource gekommen ist. Wenn der Prioritätsplaner verwendet wird, wird
NUSE_Reschedule () mit dem Aufgabenindex als Hinweis auf den Abschluss aufgerufen, da die Aufgabe möglicherweise eine höhere Priorität hat und sofort ausgeführt werden muss.
API-Aufrufe sperren
Nucleus RTOS unterstützt eine Reihe von API-Aufrufen, mit denen ein Entwickler eine Aufgabe anhalten (blockieren) kann, wenn keine Ressourcen verfügbar sind. Die Aufgabe wird fortgesetzt, wenn wieder Ressourcen verfügbar sind. Dieser Mechanismus ist auch in Nucleus SE implementiert und gilt für eine Reihe von Kernelobjekten: Eine Aufgabe kann in einem Speicherabschnitt, in einer Ereignisgruppe, einem Postfach, einer Warteschlange, einem Kanal oder einem Semaphor gesperrt werden. Wie die meisten Tools in Nucleus SE ist es jedoch optional und wird durch das Symbol
NUSE_BLOCKING_ENABLE in
nuse_config.h definiert . Wenn
TRUE festgelegt ist , wird das Array
NUSE_Task_Blocking_Return [] definiert, das den Rückkehrcode für jede Task enthält.
Dies kann
NUSE_SUCCESS oder der Code
NUSE_MAILBOX_WAS_RESET sein , der angibt, dass das Objekt zurückgesetzt wurde, als die Aufgabe gesperrt wurde. Wenn die Sperre aktiviert ist, wird der entsprechende Code mithilfe der bedingten Kompilierung in die API-Funktionen aufgenommen.
Scheduler-Zähler
Nucleus RTOS berechnet, wie oft eine Aufgabe seit ihrer Erstellung und ihrem letzten Zurücksetzen geplant wurde. Diese Funktion ist auch in Nucleus SE implementiert, ist jedoch optional und wird durch das Symbol
NUSE_SCHEDULE_COUNT_SUPPORT in
nuse_config.h definiert . Bei
TRUE wird ein Array von
NUSE_Task_Schedule_Count [] vom Typ
U16 erstellt , in dem der Zähler jeder Aufgabe in der Anwendung
gespeichert wird .
Ausgangszustand der Aufgabe
Wenn eine Aufgabe in Nucleus RTOS erstellt wird, können Sie ihren Status auswählen: Bereit oder angehalten. In Nucleus SE sind standardmäßig alle Aufgaben beim Start bereit. Mit der Option, die mit dem Symbol
NUSE_INITIAL_TASK_STATE_SUPPORT in
nuse_config.h ausgewählt wurde, können Sie den
Startstatus auswählen. Das Array
NUSE_Task_Initial_State [] ist in
nuse_config.c definiert und erfordert die Initialisierung von
NUSE_READY oder
NUSE_PURE_SUSPEND für jede Aufgabe in der Anwendung.
Kontext speichern
Die Idee, den Kontext einer Aufgabe mit einem beliebigen Schedulertyp außer RTC (Run to Completion) beizubehalten, wurde in Artikel 3 „Aufgaben und Zeitplanung“ vorgestellt. Wie bereits erwähnt, gibt es verschiedene Möglichkeiten, den Kontext aufrechtzuerhalten. Da Nucleus SE nicht für 32-Bit-Prozessoren ausgelegt ist, habe ich mich für die Verwendung von Tabellen und nicht von Stapeln entschieden, um den Kontext aufrechtzuerhalten.
Ein zweidimensionales Array vom
ADDR- Typ
NUSE_Task_Context [] [] wird verwendet, um den Kontext für alle Aufgaben in der Anwendung zu speichern. Die Zeilen sind
NUSE_TASK_NUMBER (die Anzahl der Aufgaben in der Anwendung), die Spalten sind
NUSE_REGISTERS (die Anzahl der Register, die gespeichert werden müssen; hängt vom Prozessor ab und ist in
nuse_types.h festgelegt) .
Das Beibehalten des Kontexts und das Wiederherstellen von Code hängt natürlich vom Prozessor ab. Dies ist der einzige Nucleus SE-Code, der an ein bestimmtes Gerät (und eine bestimmte Entwicklungsumgebung) gebunden ist. Ich werde ein Beispiel für den Speicher- / Wiederherstellungscode für den ColdFire-Prozessor geben. Obwohl diese Auswahl aufgrund eines veralteten Prozessors seltsam erscheint, ist sein Assembler leichter zu lesen als die Assembler der meisten modernen Prozessoren. Der Code ist einfach genug, um als Grundlage für die Erstellung eines Kontextwechsels für andere Prozessoren verwendet zu werden:

Wenn eine Kontextumschaltung erforderlich ist, wird dieser Code in NUSE_Context_Swap aufgerufen. Es werden zwei Variablen verwendet:
NUSE_Task_Active , der Index der aktuellen Aufgabe, dessen Kontext beibehalten werden muss;
NUSE_Task_Next , der Index der Aufgabe, deren Kontext Sie laden möchten (siehe Abschnitt Globale Daten).
Der Kontexterhaltungsprozess funktioniert wie folgt:
- Die Register A0 und D0 werden vorübergehend auf dem Stapel gespeichert.
- A0 ist so konfiguriert, dass es auf ein Array von Kontextblöcken verweist. NUSE_Task_Context [] [] ;
- D0 wird mit NUSE_Task_Active geladen und mit 72 multipliziert (ColdFire verfügt über 18 Register, für deren Speicherung 72 Byte erforderlich sind).
- dann wird D0 zu A0 hinzugefügt, das nun auf einen Kontextblock für die aktuelle Aufgabe zeigt;
- dann werden die Register im Kontextblock gespeichert; zuerst A0 und D0 (vom Stapel), dann D1-D7 und A1-A6 , dann SR und PC (vom Stapel sehen wir uns eine schnell eingeleitete Kontextumschaltung an), und am Ende wird der Stapelzeiger gespeichert.
Der Kontextladevorgang ist dieselbe Abfolge von Aktionen in umgekehrter Reihenfolge:
- A0 ist so konfiguriert, dass es auf ein Array von Kontextblöcken verweist. NUSE_Task_Context [] [] ;
- D0 wird mit NUSE_Task_Active geladen, inkrementiert und mit 72 multipliziert.
- dann wird D0 zu A0 hinzugefügt, das nun auf den Kontextblock für die neue Aufgabe zeigt (da das Laden des Kontexts im umgekehrten Prozess des Speicherns der Sequenz erfolgen sollte, wird zuerst der Stapelzeiger benötigt);
- dann werden die Register aus dem Kontextblock wiederhergestellt; Zuerst werden der Stapelzeiger, dann PC und SR auf den Stapel geschoben , dann werden D1-D7 und A1-A6 geladen und am Ende von D0 und A0 .
Die Schwierigkeit bei der Implementierung der Kontextumschaltung besteht darin, dass der Zugriff auf das Statusregister für viele Prozessoren schwierig ist (für ColdFire ist dies
SR ). Eine übliche Lösung ist eine Unterbrechung, d. H. Eine Programmunterbrechung oder eine bedingte Verzweigungsunterbrechung, die bewirkt, dass der
SR zusammen mit dem
PC auf den Stapel geladen wird. So funktioniert Nucleus SE unter ColdFire. Das Makro
NUSE_CONTEXT_SWAP () wird in
nuse_types.h festgelegt , das sich auf
Folgendes erstreckt:
asm ("trap # 0");Das Folgende ist der Initialisierungscode (
NUSE_Init_Task () in
nuse_init.c ) für Kontextblöcke:

Auf diese Weise erfolgt die Initialisierung des Stapelzeigers, des
PCs und des
SR . Die ersten beiden haben vom Benutzer in
nuse_config.c festgelegte Werte. Der Wert von
SR ist als das Zeichen
NUSE_STATUS_REGISTER in
nuse_types.h definiert . Für ColdFire ist dieser Wert
0x40002000 .
Globale Daten
Der Nucleus SE-Scheduler benötigt nur sehr wenig Speicher zum Speichern von Daten, verwendet jedoch natürlich Datenstrukturen, die mit Aufgaben verknüpft sind. Dies wird in den folgenden Artikeln ausführlich erläutert.
RAM-Daten
Der Scheduler verwendet die im ROM befindlichen Daten nicht und 2 bis 5 globale Variablen werden im RAM abgelegt (alle sind in
nuse_globals.c festgelegt ), je nachdem, welcher Scheduler verwendet wird:
- NUSE_Task_Active - eine Variable vom Typ U8, die den Index der aktuellen Aufgabe enthält;
- NUSE_Task_State - eine Variable vom Typ U8, die einen Wert enthält, der den Status des aktuell ausgeführten Codes angibt. Dies kann eine Task, ein Interrupt-Handler oder ein Startcode sein. mögliche Werte sind: NUSE_TASK_CONTEXT , NUSE_STARTUP_CONTEXT , NUSE_NISR_CONTEXT und NUSE_MISR_CONTEXT ;
- NUSE_Task_Saved_State - eine Variable vom Typ U8, die zum Schutz des Werts von NUSE_Task_State in einem verwalteten Interrupt verwendet wird.
- NUSE_Task_Next - eine Variable vom Typ U8, die den Index der nächsten Task enthält, die für alle Scheduler außer RTC geplant werden sollte;
- NUSE_Time_Slice_Ticks - eine Variable vom Typ U16, die einen Zähler für Zeitscheiben enthält ; Wird nur mit dem TS-Scheduler verwendet.
Scheduler-Daten-Footprint
Der Nucleus SE-Scheduler verwendet keine ROM-Daten. Die genaue Menge der RAM-Daten hängt vom verwendeten Scheduler ab:
- für RTC - 2 Bytes ( NUSE_Task_Active und NUSE_Task_State );
- für RR und Priorität - 4 Bytes ( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State und NUSE_Task_Next );
- für TS - 6 Bytes ( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State , NUSE_Task_Next und NUSE_Time_Slice_Ticks ).
Implementierung anderer Planungsmechanismen
Trotz der Tatsache, dass Nucleus SE eine Auswahl von 4 Schedulern bietet, die die meisten Fälle abdecken, können Sie mit der offenen Architektur Möglichkeiten für andere Fälle implementieren.
Zeitscheiben mit Hintergrundaufgabe
Wie bereits in
Artikel 3, „Aufgaben und Zeitplanung“ beschrieben, weist
der einfache Zeitscheiben-Zeitplaner Einschränkungen auf, da er die maximale Zeit begrenzt, die ein Prozessor für eine Aufgabe verwenden kann. Eine komplexere Option wäre das Hinzufügen von Unterstützung für die Hintergrundaufgabe. Eine solche Aufgabe kann für jeden für angehaltene Aufgaben zugewiesenen Steckplatz geplant und ausgeführt werden, wenn der Steckplatz teilweise freigegeben ist. Mit diesem Ansatz können Sie Aufgaben in regelmäßigen Abständen und mit einem vorhergesagten Prozentsatz der zu erledigenden Prozessorkernzeit planen.
Priorität und Round Robin (RR)
In den meisten Echtzeitkernen unterstützt der Prioritätsplaner im Gegensatz zu Nucleus SE, bei dem jede Aufgabe eine eindeutige Ebene hat, mehrere Aufgaben auf jeder Prioritätsstufe. Ich habe die letztere Option bevorzugt, da sie die Datenstrukturen und damit den Scheduler-Code erheblich vereinfacht. Zur Unterstützung komplexerer Architekturen wären zahlreiche ROM- und RAM-Tabellen erforderlich.
Über den Autor: Colin Walls ist seit über dreißig Jahren in der Elektronikindustrie tätig und widmet sich die meiste Zeit der Firmware. Heute ist er Firmware-Ingenieur bei Mentor Embedded (einer Abteilung von Mentor Graphics). Colin Walls spricht häufig auf Konferenzen und Seminaren, Autor zahlreicher technischer Artikel und zweier Bücher über Firmware. Lebt in Großbritannien.
Colins professioneller
Blog , E-Mail: colin_walls@mentor.com.
Über die Übersetzung: Diese Artikelserie schien insofern interessant zu sein, als der Autor trotz der an einigen Stellen veralteten beschriebenen Ansätze den schlecht ausgebildeten Leser in die Funktionen des Echtzeit-Betriebssystems einführt. Ich selbst gehöre zu dem Team von Entwicklern des
russischen RTOS , das wir
kostenlos machen wollen , und ich hoffe, dass der Zyklus für unerfahrene Entwickler nützlich sein wird.