Die ganze Wahrheit ĂŒber RTOS. Artikel 9. Scheduler: Implementierung


Die Grundprinzipien der Arbeit der RTOS-Planer wurden im Artikel „Aufgaben und Planung“ berĂŒcksichtigt. In diesem Artikel werden die Funktionen von Nucleus RTOS sowie die Funktionen von Nucleus SE ausfĂŒhrlicher beschrieben.


FrĂŒhere Artikel in der Reihe:
Artikel 8. Nucleus SE: Internes Design und Bereitstellung
Artikel 7. Nucleus SE: EinfĂŒhrung
Artikel 6. Andere RTOS-Dienste
Artikel 5. Aufgabeninteraktion und Synchronisation
Artikel 4. Aufgaben, Kontextwechsel und Interrupts
Artikel 3. Aufgaben und Planung
Artikel 2. RTOS: Struktur und Echtzeitmodus
Artikel 1. RTOS: EinfĂŒhrung.


Planung bei Nucleus RTOS


Da Nucleus RTOS ein vollwertiges, gut etabliertes kommerzielles RTOS ist, können wir davon ausgehen, dass der Scheduler gemĂ€ĂŸ den Anforderungen eines solchen Produkts entwickelt wurde. Dieses komplexe und flexible Betriebssystem bietet dem Entwickler eine breite Palette von Funktionen zur Lösung nahezu aller denkbaren Echtzeit-Programmieraufgaben.


Der Scheduler kann eine unbegrenzte Anzahl von Aufgaben unterstĂŒtzen (nur durch verfĂŒgbare Ressourcen begrenzt) und mit der PrioritĂ€tsverwaltung arbeiten. Der Aufgabe kann eine PrioritĂ€t von 0 bis 255 zugewiesen werden, wobei 0 die höchste PrioritĂ€t und 255 die niedrigste ist. Eine Aufgabe hat eine dynamische PrioritĂ€t, dh sie kann zur Laufzeit entweder von der Aufgabe selbst oder von einer anderen geĂ€ndert werden. Mehrere Aufgaben können derselben PrioritĂ€tsstufe zugewiesen werden. Im Extremfall können allen Aufgaben die gleiche PrioritĂ€t zugewiesen werden, wodurch eine Planungsrichtlinie nach dem Prinzip von Round Robin und Time-Slice implementiert werden kann.
Wenn mehrere Aufgaben mit derselben PrioritĂ€t vorhanden sind, werden sie mithilfe des Round Robin-Algorithmus in der Reihenfolge geplant, in der sie vorbereitet wurden. Die Aufgabe muss angehalten oder die Kontrolle ĂŒbertragen werden, damit die nĂ€chste Aufgabe beginnt. Aufgaben können auch Zeitintervalle zugewiesen werden, die eine kontrollierte Trennung der verfĂŒgbaren Prozessorzeit ermöglichen.


Die Aufgabenplanung ist zu 100% deterministisch, was von einem Àhnlichen Kern zu erwarten ist. Aufgaben können auch dynamisch erstellt und zerstört werden, was dank des Schedulers vom Benutzer unbemerkt bleibt.


Planung bei Nucleus SE


Ich habe alle Aspekte von Nucleus SE so entwickelt, dass sie im Allgemeinen mit Nucleus RTOS kompatibel sind, aber auch einfacher und effizienter in Bezug auf den Speicher. Der Scheduler ist keine Ausnahme. Es bietet viele Funktionen des Nucleus RTOS Scheduler, ist jedoch etwas eingeschrÀnkt. FlexibilitÀt wird durch Konfiguration wÀhrend der Montage erreicht.
Eine Nucleus SE-Anwendung kann maximal 16 Aufgaben (und mindestens eine) haben. Obwohl diese Anzahl theoretisch erhöht werden kann, ist die Effizienz der Algorithmen gefĂ€hrdet. Eine Reihe von Datenstrukturen basiert darauf, dass die Task-Indexnummer (von 0 bis 15) in einem Halbbyte (vier Bits) gespeichert wird, und sie mĂŒssen zusammen mit dem entsprechenden Code verarbeitet werden.


Um ein Gleichgewicht zwischen FlexibilitĂ€t und Einfachheit (und GrĂ¶ĂŸe) zu erreichen, bietet Nucleus SE anstelle eines Schedulers mit mehreren Funktionen eine von vier Arten von Schedulern zur Auswahl: Run to Component (RTC), Round Robin (RR), Time-Slice ( TS) und PrioritĂ€t. Der Scheduler wird zum Zeitpunkt der Montage statisch ausgewĂ€hlt. Details zu den einzelnen Schedulertypen werden unten im Abschnitt "Schedulertypen" beschrieben.


Wie jeder andere Aspekt von Nucleus SE sind Aufgaben statische Objekte. Sie werden wÀhrend der Konfiguration festgelegt und ihre PrioritÀt (Index) kann nicht geÀndert werden.


Nucleus SE-Planer


Wie oben erwÀhnt, bietet Nucleus SE eine von vier Arten von Schedulern zur Auswahl. Wie die meisten Aspekte der Nucleus SE-Konfiguration wird diese Auswahl durch Schreiben in nuse_config.h festgelegt. Der Parameter NUSE_SCHEDULER_TYPE muss entsprechend festgelegt werden, wie in diesem Fragment aus der Konfigurationsdatei gezeigt:



UnabhÀngig davon, welcher Scheduler ausgewÀhlt ist, wird sein Startcode unmittelbar nach der Initialisierung des Systems aufgerufen. VollstÀndige Informationen zur Initialisierung von Nucleus SE werden im nÀchsten Artikel vorgestellt.


Zum Abschlussplaner ausfĂŒhren


Der RTC-Scheduler ist die einfachste und am besten geeignete Lösung, wenn er die Anforderungen der Anwendung erfĂŒllt. Jede Aufgabe muss ihre Arbeit abschließen, bevor die RĂŒckgabefunktion ausgefĂŒhrt wird und der Scheduler die nĂ€chste Aufgabe ausfĂŒhren kann.


Es ist kein separater Stapel fĂŒr jede Aufgabe erforderlich. Der gesamte Code ist in C geschrieben, Assemblersprache ist nicht erforderlich. Unten finden Sie den gesamten RTC-Scheduler-Code.



Code ist nur eine Endlosschleife, die abwechselnd jede Aufgabe aufruft. Das Array NUSE_Task_Start_Address [] enthÀlt Zeiger auf die externe Funktion jeder Aufgabe. Das PF0-Makro ist eine einfache Konvertierung eines Void- Zeigers in einen Zeiger auf eine Void- Funktion ohne Parameter. Es soll die Lesbarkeit des Codes gewÀhrleisten.
Die bedingte Kompilierung wird verwendet, um die UnterstĂŒtzung zusĂ€tzlicher Funktionen zu aktivieren: NUSE_SUSPEND_ENABLE bestimmt, ob Aufgaben angehalten werden können; NUSE_SCHEDULE_COUNT_SUPPORT bestimmt, ob bei jeder geplanten Aufgabe ein ZĂ€hlerwert erforderlich ist. Weitere Informationen hierzu finden Sie im nĂ€chsten Artikel.


Scheduler Round Robin


Wenn etwas mehr FlexibilitĂ€t erforderlich ist als vom RTC-Scheduler bereitgestellt, ist der RR-Scheduler geeignet. Dadurch kann die Aufgabe die Kontrolle ĂŒbertragen oder anhalten und dann an derselben Stelle fortfahren. ZusĂ€tzlicher Overhead zusĂ€tzlich zur CodekomplexitĂ€t und NichtportabilitĂ€t besteht darin, dass fĂŒr jede Aufgabe ein eigener Stapel erforderlich ist.
Der Scheduler-Code besteht aus zwei Teilen. Die Startkomponente lautet wie folgt:



Wenn die UnterstĂŒtzung fĂŒr den Anfangszustand der Aufgabe aktiviert ist (mithilfe des Parameters NUSE_INITIAL_TASK_STATE_SUPPOR T, siehe "Parameter" im nĂ€chsten Artikel), beginnt die Planung mit der ersten abgeschlossenen Aufgabe. Andernfalls wird eine Aufgabe mit dem Index 0 verwendet. Der Kontext dieser Aufgabe wird dann mit NUSE_Context_Load () geladen. Weitere Informationen zum Speichern und Wiederherstellen eines Kontexts finden Sie im Abschnitt „Speichern des Kontexts“ im nĂ€chsten Artikel.


Der zweite Teil des Schedulers ist die Komponente „Neuplanung“:



Dieser Code wird aufgerufen, wenn die Task den Zentralprozessor freigibt oder pausiert.


Der Code wĂ€hlt die zu startende Aufgabe mit dem folgenden Index aus und platziert den Wert in NUSE_Task_Next, wobei berĂŒcksichtigt wird, ob die Aufgabenunterbrechung aktiviert ist oder nicht. Das Makro NUSE_CONTEXT_SWAP () wird dann verwendet, um die Kontextumschaltung unter Verwendung eines Software-Interrupts aufzurufen. Weitere Informationen zum Speichern und Wiederherstellen eines Kontexts finden Sie im Abschnitt „Speichern des Kontexts“ im nĂ€chsten Artikel.


PrioritÀtsplaner


Der Priority Scheduler in Nucleus SE bietet wie andere Optionen die erforderliche FunktionalitÀt und ist recht einfach. Infolgedessen hat jede Aufgabe eine eindeutige PrioritÀt. Es ist unmöglich, mehrere Aufgaben mit einer PrioritÀtsstufe zu haben. Die PrioritÀt wird durch den Index der Aufgabe bestimmt, wobei 0 die höchste PrioritÀtsstufe ist. Der Index der Aufgabe wird durch ihre Position im Array NUSE_Task_Start_Address [] bestimmt. Der nÀchste Artikel enthÀlt detailliertere Informationen zum Einrichten von Aufgaben.


Wie die RR- und TS-Scheduler besteht der Priority-Scheduler aus zwei Komponenten. Die Startkomponente des PrioritĂ€tsplaners ist dieselbe wie die RR- und TS-Planer, wie oben dargestellt. Die Umplanungskomponente unterscheidet sich geringfĂŒgig:



Es gibt keinen bedingten Code, der die Unterbrechung von Aufgaben deaktivieren könnte, da diese Funktion fĂŒr den PrioritĂ€tsplaner obligatorisch ist. Jede Alternative wĂ€re unlogisch. Die Funktion NUSE_Reschedule () akzeptiert einen Parameter, der "sagt", welche Aufgabe als nĂ€chstes geplant werden kann - new_task. Dieser Wert wird festgelegt, wenn eine Neuplanung aufgerufen wird, weil eine andere Aufgabe aufgerufen wird. Der Index dieser Aufgabe wird als Parameter ĂŒbergeben. Der Scheduler kann dann bestimmen, ob eine Kontextumschaltung durchgefĂŒhrt werden soll, indem der Wert von new_task mit dem Index der aktuellen Task (NUSE_Task_Active) verglichen wird . Wenn die Neuplanung das Ergebnis einer Aufgabenpause ist, wird der Parameter auf NUSE_NO_TASK gesetzt und der Scheduler sucht nach der Aufgabe mit der höchsten PrioritĂ€t.


AufgabenzustÀnde


In der Regel haben alle Betriebssysteme das Konzept, Aufgaben in einem bestimmten „Zustand“ zu finden. Details variieren je nach RTOS. In diesem Artikel werden wir uns ansehen, wie Nucleus RTOS und Nucleus SE TaskzustĂ€nde verwenden.


Nucleus RTOS-TaskzustÀnde


Nucleus RTOS unterstĂŒtzt 5 TaskzustĂ€nde.


  • AusfĂŒhrung: Die Aufgabe, die derzeit den Prozessor verwaltet. Offensichtlich kann nur eine Aufgabe diesen Zustand einnehmen.
  • Bereitschaft: Eine Aufgabe, die zur AusfĂŒhrung bereit ist (oder die AusfĂŒhrung fortzusetzen), bevor der Planer beschließt, sie zu starten. In der Regel hat eine Aufgabe eine niedrigere PrioritĂ€t als die ausgefĂŒhrte.
  • Suspendierung: die "schlafende" Aufgabe. Es wird bei der Planung erst berĂŒcksichtigt, wenn es aufwacht. In diesem Moment ist es „bereit“ und kann spĂ€ter fortgesetzt werden. Normalerweise befindet sich eine Aufgabe im Ruhezustand, weil sie auf etwas wartet: wenn die Ressource verfĂŒgbar wird, wenn der festgelegte Zeitraum ablĂ€uft oder wenn eine andere Aufgabe sie aufweckt.
  • Abbrechen: Die Aufgabe wurde "getötet". Sie wird bei der Planung erst berĂŒcksichtigt, wenn sie zurĂŒckgesetzt wird. Danach ist die Aufgabe „bereit“ oder „angehalten“.
  • Beenden: Die Aufgabe wird abgeschlossen und ihre externe Funktion verlassen, indem einfach die externe Einheit verlassen oder die return-Anweisung ausgefĂŒhrt wird. Sie wird bei der Planung erst berĂŒcksichtigt, wenn sie zurĂŒckgesetzt wird. Danach ist die Aufgabe „bereit“ oder „angehalten“.
Da Nucleus RTOS die dynamische Erstellung und Zerstörung von Objekten, einschließlich Aufgaben, unterstĂŒtzt, kann die Aufgabe auch in einem "entfernten" Zustand betrachtet werden. Sobald jedoch die Aufgabe gelöscht wird, existieren alle ihre Systemressourcen nicht mehr und die Aufgabe selbst existiert nicht mehr, kann sie keinen Status haben. Der Aufgabencode ist möglicherweise verfĂŒgbar, das Aufgabenobjekt muss jedoch erneut erstellt werden.

AufgabenzustÀnde in Nucleus SE


Das Taskstatusmodell in Nucleus SE ist etwas einfacher. Normalerweise gibt es nur drei ZustĂ€nde: AusfĂŒhrung, VerfĂŒgbarkeit und Pause. Der Status jeder Aufgabe wird in NUSE_Task_Status [] gespeichert, der Werte vom Typ NUSE_READY enthĂ€lt , obwohl er niemals einen Wert hat, der den AusfĂŒhrungsstatus widerspiegelt. Wenn die Task-Suspendierung nicht aktiviert ist (siehe "Optionen" im nĂ€chsten Artikel), sind nur zwei Task-Status möglich, und dieses Array fehlt.

Es gibt verschiedene Arten von Pausenaufgaben. Wenn eine Aufgabe explizit von sich selbst oder von einer anderen Aufgabe angehalten wird, wird dies als "reine Aussetzung" bezeichnet und durch den Status NUSE_PURE_SUSPEND dargestellt. Wenn der Status "Schlaf" aktiviert ist und die Aufgabe fĂŒr einen bestimmten Zeitraum angehalten wird, hat sie den Status
NUSE_SLEEP_SUSPEND . Wenn die Funktion zum Blockieren von API-Aufrufen aktiviert ist (ĂŒber NUSE_BLOCKING_ENABLE , siehe „Parameter“ im nĂ€chsten Artikel), kann die Aufgabe angehalten werden, bis die Ressource verfĂŒgbar wird. Jeder Objekttyp hat seinen eigenen Task-Suspendierungsstatus, z. B. in Form von NUSE_MAILBOX_SUSPEND. In Nucleus SE kann eine Aufgabe in einer Speicherpartition, einer Ereignisgruppe, einem Postfach, einer Warteschlange, einem Kanal oder einem Semaphor gesperrt werden.

Thread-Status


Bei der Erörterung des Aufgabenverhaltens werden die Wörter „Status“ und „Status“ normalerweise recht frei verwendet. Es gibt einen zusĂ€tzlichen Faktor, der bedingt als "Zustand des Flusses" bezeichnet werden kann. Dies ist die globale Variable NUSE_Thread_State, die einen Hinweis auf die Art des ausgefĂŒhrten Codes enthĂ€lt. Dies gilt fĂŒr das Verhalten vieler API-Aufrufe. Mögliche Werte:

  • NUSE_TASK_CONTEXT - Der API-Aufruf wurde von einer Task aus durchgefĂŒhrt.
  • NUSE_STARTUP_CONTEXT - Der API-Aufruf wurde aus dem Startcode ausgefĂŒhrt . Der Scheduler wurde noch nicht gestartet.
  • NUSE_NISR_CONTEXT und NUSE_MISR_CONTEXT - Der API-Aufruf wurde vom Interrupt-Handler ausgefĂŒhrt. Unterbrechungen in Nucleus SE werden im nĂ€chsten Artikel behandelt.

Der nÀchste Artikel beschreibt die erweiterten Scheduler-Funktionen in Nucleus SE sowie die Verwaltung des Kontexts.

Ü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

Source: https://habr.com/ru/post/de422615/


All Articles