Dipl. Phys. Helmut Weber In Umarbeitung ! (4.12.2018)





Achtung:

Alle Beiträge sind Copyright (C) 2018 Dipl. Phys. Helmut Weber

Jedes Kopieren - egal, in welcher Form - bedarf der schriftlichen Erlaubnis.





Sprungziele in dieser Seite:

State-Machine                 Erklärung: Multitasking mit einer State-Machine

Ein Scheduler in C          Erklärung: Ein Scheduler in C beruhend auf __LINE

CoopOS                           Erklärung: Ein kooperativer Scheduler

Präambel


Nach 4500 Jahren sind die Pyramiden von Gizeh immer noch aussergewöhnlich beeindruckende Bauten.

Die Mondlandung ist ein ähnliches Beispiel. Hier gibt es zwar eine Menge Aufzeichnungen, aber trotzdem hält sich hartnäckig das Gerücht, as sei nur eine Fake gewesen.  Die Ausrüstung sei viel zu primitiv gewesen, um dieses Projekt durchzuführen.

Auch im Bereich der Software kann man ähnliches beobachte: Farbige Spiele auf einem Apple II in der Geschwindigkeit - bei der komplizierten Grafik ohne Coprozessor - das kann nach heutigen Maßstäben gar nicht funtionieren.

Und es ging doch!

Es kommt mir so vor, als wenn durch immer höhere Abstraktion manche alte, aber grundlegende Techniken verloren gehen.

Dazu gehört das kooperative Multitasking. Überflüssig meinen viele, da es durch RTOS, Linux preempt_RT usw. ersetzt wurden.

Das stimmt so nicht. Kooperatives Multitasking ist immer noch - oft zusätzlich eingesetzt - ein wichtiger Bestandteil moderner Software-Entwicklung!

Donald Knuth, The Art of Programming, Vol1, Coroutines  (1967)            beschreibt es sehr ausführlich!


Einleitung

Obwohl Physiker, entwickle ich seit seit Jahrzehnten Software .

Dabei habe ich mich immer besonders für die hardwarenahe Softwareentwicklung interessiert:

Das schnelle Auslesen von Sensoren und das schnelle Reagieren der Aktoren spielte dabei immer eine herausragende Rolle.

Dabei geht es immer um

ECHTZEIT  = REALTIME

Im einfachsten Fall geht es dabei nicht um die Nutzungs eine RTOS und damit festgelegte timings, sonder schlicht um die schnellstmögliche Ausführung von Aufgaben, um dann anschließend zu messen, wie lange bestimmte Vorgänge maximal benötigen.

while(1)

{

     Lese Sensor

     Werte Sensor aus

     Wenn Sensorwert > Grenze

     {

          Löse Alarm aus

      }

}


Natürlich kam recht bald die Notwendigkeit auf, mehrere Sensoren zu überwachen.

Die einfachste Form ist:


while(1)

{

     Lese Sensor1

     Werte Sensor1 aus

     Wenn Sensorwert1 > Grenze1

     {

          Löse Alarm1 aus

      }



     Lese Sensor2

     Werte Sensor2 aus

     Wenn Sensorwert2 > Grenze2

     {

          Löse Alarm2 aus

      }

}


Aber bereits bei diesen 2 Aufgaben ergibt sich das Problem:

Was passiert, wenn der Alarm vom Sensor1 ausgelöst wird, der 10 Sekunden lang ein Signal senden muss, mit der Abfrage von Sensor2 ?







State-Machine

Zum Anfang




Es wurde eine Art von Multitasking benötigt!

Die beiden Aufgaben mussten in 2 Tasks bearbeitet werden können, die unabhängig voneinander bearbeitet werden müssen!

while(1)

{

             DoSensor1();
             DoSensor2();
         }


Die Funktionen DoSensor1() und DoSensor2 dürfen auch im Alarm-Fall nicht lange blockieren:


DoSensor1()  {

static int state, alarmcount;

           if (state==0) {

      Lese Sensor1

      state=1;

      return;

   }

  

   if (state==1) {

       Werte Sensor1 aus

       state=2;

       return;

   }


   if (state==2) {

       if (Sensorwert1 > Grenze1 ) {

          state=3;

          return;

       else {

          state=0;

          return;

       }

   }

   if (state==3) {

       alarmcount++;

       if (alarmcount<10000 ) {

            setAlarmPin;

            return;

      }

      else {

            alarmcount=0;

                   state=0;

                   return;
      }

  }

}


Was ist damit gewonnen?

Sensor1() und Sensor2() speichern jeweils ihren Zustand, bevor sie zurückkehren! Beim nächsten Aufruf machen sie dann gezielt dort weiter, wo es angebracht ist. Jeder Aufruf ist sehr kurz - auch im Alarmfall!


Natürlich müssen solche Werte wie  alarmcount<10000 schlicht ausprobiert werden, um die gewünschten Zeiten zu erhalten.

Viel wesentlicher ist aber:

Das Prinzip ist unabhängi vom Prozessor einsetzbar

Das Prinzip ist sogar unabhängig von der Programmiersprache

Das Prinzip benötigt kein Betriessystem






Ein Scheduler in C

Zum Anfang


Jedes Multitasking-System hat einen Scheduler, der die einzelnen Tasks startet. Bei einem RTOS wird dieser Scheduler zyklisch aufgerufen - meist 100 - 1000 mal pro Sekunde.

Hier ein Beispiel für einen extrem simplen Scheduler(main) und Tasks, die freiwillig zu Scheduler zurückkehren:

#include <stdio.h>

#define taskBegin() static int lin = 0;  switch(lin){ case 0: 
#define taskEnd() }
#define taskSwitch() do { lin = __LINE__; return ; case __LINE__: ; } while (0)

void Task1() {
  taskBegin();
  // Initialisierung des Tasks

  while(1) {
    printf("Task1 - 1\n");
    taskSwitch();
    printf("Task1 - 2\n");
    taskSwitch();
    printf("Task1 - 3\n");
    taskSwitch();
  }
  taskEnd();
}


void Task2() {
  taskBegin();
  while(1) {
    printf("Task2 - 1\n");
    taskSwitch();
    printf("Task2 - 2\n");
    taskSwitch();
    printf("Task2 - 3\n");
    taskSwitch();
  }
  taskEnd();
}

int main() {
  while(1) {
    Task1();
    Task2();
  }
}

/*
 * Ausgabe:
Task1 - 1
Task2 - 1
Task1 - 2
Task2 - 2
Task1 - 3
Task2 - 3
Task1 - 1
Task2 - 1
Task1 - 2
Task2 - 2
Task1 - 3
Task2 - 3
...
*/

Wer die #define im Programm auflöst, der sieht  sehr schnell, dass es sich um eine o.g. State-Machine  handelt.

Hier wird die Variable __LINE ausgenutzt, über die jeder C-Compiler seit den Anfängen verfügt. Deshalb läuft dieses Programm auf jedem Computer, der über einen C-Compiler verfügt.

Vorteile:

Die Taskwechsel laufen extrem schnell. Auf modernen Computern sind (printf natürlich) viele Millionen Taskwechsel pro Sekunde möglich.

Die Tasks (und der Scheduler) brauchen keine Semaphoren o.ä., um auf globale Variablen zuzugreifen, dasie nicht beim Zugriff unterbrochen werden können. Die Tasks können sich so gegenseitig steuern :

if (SignalFromTask1) ...





Kooperatives Scheduling (CoopOS)


Zum Anfang

Dringend benötigt wird noch irgendeine Art der Zeiterfassung. Bei Linux kann man per Betriessystem eine Zeiterfassung mit Nano-Sekunden Granularität durchführen.

Denn es sollen ja nicht immer alle Tasks ausgeführt werden, sonder nur die, die genau zu diesen Zeitpunkt "READY" sind.

Dann wird eine Task-Struktur eingeführt:  (vereinfacht)

#define MAXTASKS  20        // Max. Number of Tasks

struct task {

    char state;                          // RUNNING, READY, STOPPED, DELAYED

    uint64_t lastcalled;             // time of last function-call

    uint64_t delay;                    // time to delay task

    void (*func)();                     // function attached to this task

} Task[MAXTASKS];   


Der Scheduler zyklisch alle Tasks durch, und startet den ersten, den er "READY" findet.

Innerhalb der Tasks kann statt "taskSwitch" eine "taskDelay(uint64_t time) aufgerufen werden, um den Task auf state==DELAYED zu setzen.


Dieses System nenne ich CoopOS. Es ist skalierbar und funktioniert in der einfachsten Form mit einem atTiny, und in voll ausgebauter Version beinhaltet es alle Features eines RTOS, wie Ausgaben im Hintergrund, reguliertem Zugriff auf Resourcen, Signals usw.

In einfachster Form kann es selbst in Timer-Interrupts eingebunden werden, von dem aus der Scheduler zyklisch mit hoher Präzision eingebunden werden kann.

Es lassen sich jederzeit sehr genaue Histogramme über das zeitliche Verhalten des Schedulers und der Taskabsschnitte erstellen.

CoopOS benötigt keine Taskswitches mit Umschalten von CPU-Registern, Stackbereichen, Memory-Bereichen usw. Deshalb ist es so extrem schnell. Bei Systemen mit zusätzlichem Linux oder RTOS sieht es etwas anders aus, das hier die Betriebssysteme jederzeit unangekündigt und in diesem Sinne nicht kooperativ das CoopOS unterbrechen können.

Bei Linux preempt_RT ist es möglich, einzelne CPUs (Mehrkernprozessoren) vom Betriebssystem weitgehend freizuhalten. Es bleibt die Fragestellung, ob CoopOS in solchen Systemen noch sinnvoll einsetzbar ist.


Die weiteren Test werden das überprüfen !