Wie verwende ich den Watchdog-Timer in einem RTOS?

Lesezeit: 9 Minuten

Angenommen, ich habe einen kooperativen Scheduler in einer eingebetteten Umgebung. Ich habe viele Prozesse laufen. Ich möchte den Watchdog-Timer verwenden, damit ich erkennen kann, wann ein Prozess aus irgendeinem Grund aufgehört hat, sich zu verhalten, und den Prozessor zurücksetzen kann.

In einfacheren Anwendungen ohne RTOS würde ich immer den Watchdog von der Hauptschleife berühren und das war immer ausreichend. Hier gibt es jedoch viele Prozesse, die möglicherweise hängen bleiben könnten. Was ist eine saubere Methode, um den Watchdog-Timer regelmäßig zu berühren und gleichzeitig sicherzustellen, dass jeder Prozess in gutem Zustand ist?

Ich dachte, dass ich jedem Prozess eine Callback-Funktion zur Verfügung stellen könnte, damit eine andere Funktion, die alles überwacht, wissen kann, dass sie noch am Leben ist. Der Rückruf würde einen Parameter übergeben, der die eindeutige ID der Aufgabe wäre, damit der Aufseher feststellen könnte, wer zurückruft.

  • Nehmen wir etwa einen Watchdog, der Teil des RTOS ist, oder einen tatsächlichen Hardware-Watchdog-Timer, den das RTOS bedient?

    – Johannes U

    6. November 2012 um 13:09 Uhr

Ein gängiger Ansatz besteht darin, das Treten des Watchdogs an eine bestimmte Aufgabe zu delegieren (häufig entweder die höchste oder die niedrigste Priorität, Kompromisse / Motivationen für jeden Ansatz) und dann alle anderen Aufgaben bei dieser Aufgabe “einchecken” zu lassen.

Diesen Weg:

  • Wenn ein Interrupt hängt (100% CPU), wird die Kicker-Task nicht ausgeführt, Sie werden zurückgesetzt

  • Wenn die Kicker-Task aufgehängt ist, setzen Sie sie zurück

  • Wenn eine andere Aufgabe aufgehängt ist, sieht die Kicker-Aufgabe kein Einchecken, die Kicker-Aufgabe kickt WDG nicht, Sie werden zurückgesetzt

Nun sind natürlich Implementierungsdetails zu beachten. Manche Leute haben für jede Aufgabe ein eigenes dediziertes Bit (atomar) in einer globalen Variablen gesetzt; Die Kicker-Task überprüft diese Gruppe von Bit-Flags mit einer bestimmten Rate und löscht/setzt sie zurück, wenn alle eingecheckt haben (natürlich zusammen mit dem Kicken des WDG). Ich meide Globals wie die Pest und vermeide diesen Ansatz. RTOS-Ereignis-Flags bieten einen etwas ähnlichen Mechanismus, der eleganter ist.

Typischerweise entwerfe ich meine eingebetteten Systeme als ereignisgesteuerte Systeme. In diesem Fall blockiert jede Aufgabe an einer bestimmten Stelle – in einer Nachrichtenwarteschlange. Alle Tasks (und ISRs) kommunizieren miteinander, indem sie Ereignisse / Nachrichten senden. Auf diese Weise müssen Sie sich keine Sorgen machen, dass eine Aufgabe nicht eincheckt, weil sie auf einem Semaphor “ganz unten” blockiert ist (wenn das keinen Sinn ergibt, sorry, ohne viel mehr zu schreiben, kann ich es nicht besser erklären ).

Auch gibt es die Überlegung – melden sich Tasks “autonom” an oder antworten/antworten sie auf eine Anfrage aus dem Kicker-Task. Autonom – zum Beispiel erhält jede Aufgabe einmal pro Sekunde ein Ereignis in ihrer Warteschlange “Sag der Kicker-Aufgabe, dass du noch am Leben bist”. Reply-Request – einmal pro Sekunde (oder was auch immer) teilen Kicker-Aufgaben jedem (über Warteschlangen) “Zeit zum Einchecken” mit – und schließlich führt jede Aufgabe ihre Warteschlange aus, erhält die Anfrage und antwortet. Es gelten Überlegungen zu Aufgabenprioritäten, Warteschlangentheorie usw.

Es gibt 100 Möglichkeiten, diese Katze zu häuten, aber das Grundprinzip einer einzelnen Aufgabe, die dafür verantwortlich ist, die WDG zu treten und andere Aufgaben zur Kicker-Aufgabe zu führen, ist ziemlich normal.

Es gibt mindestens einen weiteren Aspekt zu berücksichtigen – außerhalb des Rahmens dieser Frage – und das ist der Umgang mit Interrupts. Die oben beschriebene Methode löst ein WDG-Reset aus, wenn ein ISR die CPU in Beschlag nimmt (gut), aber was ist mit dem entgegengesetzten Szenario – ein ISR wurde (leider) versehentlich und versehentlich deaktiviert. In vielen Szenarien wird dies nicht erkannt, und Ihr System kickt das WDG immer noch, aber ein Teil Ihres Systems ist lahmgelegt. Lustige Sachen, deshalb liebe ich eingebettete Entwicklung.

  • Ich denke, Ihr zu berücksichtigender Aspekt ist der Nachteil der Synchronisierung von Aufgaben nur mit Nachrichten zwischen Aufgaben. Wenn eine ISR für den Betrieb einer Task kritisch ist, sollte diese ISR dieser Task eine Nachricht senden (oder einen Semaphor oder einen anderen Mechanismus blockieren), die diese Task sperrt, bis die ISR läuft. Wenn die ISR anhält, kann die Aufgabe die Watchdog-Aufgabe nicht signalisieren und Sie fangen dieses Ereignis ab. Ich vermute, das ist das, was Sie als “ganz unten” bezeichnet haben, obwohl ich nicht sicher bin, warum Sie dafür plädieren, dies zu vermeiden.

    – iheanyi

    9. Dezember 2020 um 23:02 Uhr

  • Die andere Sache hier ist, nur weil eine Aufgabe mit einer ISR funktioniert, bedeutet das nicht, dass sie nur von der ISR blockiert werden sollte. Beispielsweise kann sich eine Kommunikationsprotokollaufgabe auf einen UART ISR für den Byte-Empfang verlassen. Die Aufgabe sollte jedoch das Warten auf den UART allein nicht blockieren. In diesem Fall sollte es sowohl einen UART als auch einen Timer (oder ein anderes Ereignis) blockieren, da ein korrekter Betrieb darin bestehen könnte, dass niemand in der Nähe ist, um zu sprechen. [1/2]

    – iheanyi

    9. Dezember 2020 um 23:10 Uhr

  • Andererseits kann eine Aufgabe, die Umgebungstemperaturmesswerte benötigt, die 5-10-mal pro Sekunde über ISR eingehen sollen, nur auf dieser ISR blockieren, da es keine gültige Bedingung gibt, bei der die SR nicht ausgeführt wird. Ja, Sie können eine fehlgeschlagene ISR im Fall des Kommunikationsprotokolls nicht erkennen, aber das liegt daran, dass Sie nicht in der Lage sind, eindeutig zwischen einer korrekt ausgeführten ISR ohne Kommunikation und einer ISR zu unterscheiden, die nicht ausgeführt wird. [2/2]

    – iheanyi

    9. Dezember 2020 um 23:12 Uhr


  • @iheanyi – Ihr erster Kommentar – ja, was ich meinte, ist, dass ich dazu neige, Code zu schreiben, der an einer Stelle blockiert – ganz oben in der Aufgabenschleife – und nicht an anderen Stellen blockiert (z. B. 3 Ebenen tief in der Funktion ruft eine Semaphore herein, die vielleicht nie kommt). Durch das Blockieren an einem einzigen Ort kann die Aufgabe auf “neue Dinge” reagieren, während sie darauf wartet, dass etwas beendet wird.

    – Dan

    11. Dezember 2020 um 3:07 Uhr

  • @iheanyi – Ich stimme zu, dass Aufgaben nicht nur auf einer ISR blockiert werden. Ich hoffe, Sie haben das, was ich geschrieben habe, nicht so interpretiert, dass dies gemeint ist. Was ich in meiner Erörterung von ISRs meinte, ist, dass mein Watchdog-Ansatz nur hängende Tasks abfängt, nicht hängende/tote ISRs. Aber ein Timer-Timeout würde trotzdem empfangen und verarbeitet werden. Ich bin mir nicht sicher, ob ein Timer immer die Lösung für UART RX ist, da die Kommunikation sporadisch und unvorhersehbar sein könnte. Wenn Sie mit geringer Leistung arbeiten, möchten Sie nicht viele Male aufwachen, nur um jedes Mal festzustellen, dass es nichts zu tun gibt. Ich denke, wir sind auf der gleichen Seite.

    – Dan

    11. Dezember 2020 um 3:12 Uhr

Ein Lösungsmuster:

  • Jeder Thread, der überprüft werden möchte, registriert seinen Callback explizit beim Watchdog-Thread, der eine Liste solcher Callbacks führt.
  • Wenn der Watchdog geplant wird, kann er die Liste der registrierten Tasks iterieren
  • Jeder Rückruf selbst wird iterativ aufgerufen, bis er einen fehlerfreien Zustand zurückgibt.
  • Am Ende der Liste wird der Hardware-Watchdog gekickt.

Auf diese Weise hält jeder Thread, der niemals einen fehlerfreien Zustand zurückgibt, die Watchdog-Aufgabe an, bis das Hardware-Watchdog-Timeout eintritt.

In einem preemptiven Betriebssystem wäre der Watchdog-Thread der niedrigste Prioritäts- oder Leerlauf-Thread. In einem kooperativen Planer sollte es zwischen Rückrufen nachgeben.

Das Design der Callback-Funktionen selbst hängt von der spezifischen Aufgabe und ihrem Verhalten und ihrer Periodizität ab. Jede Funktion kann an die Bedürfnisse und Besonderheiten der Aufgabe angepasst werden. Tasks mit hoher Periodizität könnten einfach einen Zähler inkrementieren, der auf Null gesetzt wird, wenn der Rückruf aufgerufen wird. Wenn der Zähler beim Eintritt Null ist, wurde die Task seit der letzten Watchdog-Prüfung nicht geplant. Aufgaben mit niedrigem oder aperiodischem Verhalten können ihre Planung mit einem Zeitstempel versehen, der Rückruf kann dann einen Fehler zurückgeben, wenn die Aufgabe nicht für einen bestimmten Zeitraum geplant wurde. Auf diese Weise können sowohl Tasks als auch Interrupt-Handler überwacht werden. Da es darüber hinaus in der Verantwortung eines Threads liegt, sich beim Watchdog zu registrieren, haben Sie möglicherweise einige Threads, die sich überhaupt nicht registrieren.

Jede Task sollte ihren eigenen simulierten Watchdog haben. Und der echte Watchdog wird nur dann von einer Echtzeit-Task mit hoher Priorität gespeist, wenn alle simulierten Watchdogs keinen Timeout haben.

dh:

void taskN_handler()
{
    watchdog *wd = watchdog_create(100); /* Create an simulated watchdog with timeout of 100 ms */
    /* Do init */
    while (task1_should_run)
    {
        watchdog_feed(wd); /* feed it */
        /* do stuff */
    }
    watchdog_destroy(wd); /* destroy when no longer necessary */
}

void watchdog_task_handler()
{
    int i;
    bool feed_flag = true;
    while(1)
    {
        /* Check if any simulated watchdog has timeout */
        for (i = 0; i < getNOfEnabledWatchdogs(); i++) 
        {
            if (watchogHasTimeout(i)) {
                   feed_flag = false;
                   break;
            }
         }

         if (feed_flag)
             WatchdogFeedTheHardware();

         task_sleep(10);
}

Jetzt kann man sagen, dass das System wirklich geschützt ist, es gibt keine Freezes, nicht einmal partielle Freezes und meistens keine ungewollten Watchdog-Trigger.

Benutzer-Avatar
Aki Suihkonen

Die traditionelle Methode besteht darin, einen Watchdog-Prozess mit der geringstmöglichen Priorität zu haben

PROCESS(watchdog, PRIORITY_LOWEST) { while(1){reset_timer(); sleep(1);} }

Und wo der eigentliche Hardware-Timer die CPU vielleicht alle 3 oder 5 Sekunden zurücksetzt.

Das Verfolgen einzelner Prozesse könnte durch umgekehrte Logik erreicht werden: Jeder Prozess würde einen Timer einrichten, dessen Rückruf dem Watchdog eine „Stopp“-Nachricht sendet. Dann müsste jeder Prozess das vorherige Timer-Ereignis abbrechen und irgendwo in der Schleife „Ereignis / Nachricht aus der Warteschlange empfangen“ ein neues einrichten.

PROCESS(watchdog, PRIORITY_LOWEST) {
    while(1) { 
       if (!messages_in_queue()) reset_timer();
       sleep(1);
    }
}
void wdg_callback(int event) { 
    msg = new Message();
    send(&msg, watchdog);
};
PROCESS(foo, PRIORITY_HIGH) {
     timer event=new Timer(1000, wdg_callback);
     while (1) {
        if (receive(msg, TIMEOUT)) {
           // handle msg       
        } else { // TIMEOUT expired 
           cancel_event(event);
           event = new Timer(1000,wdg_callback);
        }
     }
}

Andere Antworten haben Ihre Frage abgedeckt, ich möchte nur vorschlagen, dass Sie etwas in Ihrem alten Verfahren (ohne RTOS) hinzufügen. Kicken Sie den Watchdog nicht bedingungslos nur von main(), es ist möglich, dass einige ISR hängen bleiben, aber das System würde ohne Vorankündigung weiterarbeiten (das Problem, das Dan erwähnt hat, bezieht sich auch auf RTOS).

Was ich immer getan habe, war den Haupt- und den Timer-Interrupt so in Beziehung zu setzen, dass innerhalb des Timers ein Countdown für eine Variable durchgeführt wurde, bis sie Null war, und vom Haupt aus würde ich prüfen, ob sie Null war, und erst dann den Watchdog füttern . Natürlich nach dem Füttern die Variable auf den Anfangswert zurücksetzen. Einfach, wenn die Variable nicht mehr dekrementiert wird, erhalten Sie das Zurücksetzen. Wenn main aufhört, den Watchdog zu füttern, erhalten Sie den Reset.

Dieses Konzept ist einfach nur für bekannte periodische Ereignisse anzuwenden, aber es ist immer noch besser, als alles nur von der Hauptsache aus zu tun. Ein weiterer Vorteil besteht darin, dass verstümmelter Code den Watchdog wahrscheinlich nicht kicken wird, da Ihre Watchdog-Feed-Prozedur innerhalb der Hauptdatei in einer wilden Schleife geendet hat.

1179560cookie-checkWie verwende ich den Watchdog-Timer in einem RTOS?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy