Innerhalb des Treibers habe ich eine Funktion, um die Daten aus der internen Struktur in eine Struktur aus der Anwendung zu kopieren.
Kann dieser Vorgang durch einen Mikrocontroller-Interrupt-Trigger unterbrochen werden?
uint16_t getRawData(struct Data *Data_external)
{
if(Data_external == NULL)
{
return ERR_PARA;
}
else
{
*Data_external = Data_internal; // the copy process. Could this be interrupted?
}
return ERR_NONE;
}
Ja. So ziemlich alles in einer MCU kann durch eine Interrupt-Anfrage unterbrochen werden. Wenn der Interrupt-Handler abgeschlossen ist, wird der vorherige Code einfach fortgesetzt, sodass dies normalerweise kein Problem darstellt.
In einem Sonderfall können die Interrupt-Handler selbst durch Interrupts höherer Priorität (verschachtelte Interrupts) unterbrochen werden.
Wenn eine Reihe von Anweisungen nicht unterbrochen werden darf, müssen Sie einen kritischen Abschnitt implementieren (grundsätzlich Interrupts global deaktivieren, den Job erledigen, wieder aktivieren).
Denken Sie daran, dass je nach Architektur der Ziel-CPU eine einzelne C-Zeile zu vielen Assembleranweisungen kompiliert werden kann. Eine einfache i++
auf einem AVR wird zu mehreren Anweisungen kompiliert, wenn i
zum Beispiel uint32_t
.
Jede Operation, die nicht atomar ist, kann durch einen Interrupt gestört werden. Diese Art der Programmierung unterscheidet sich oft stark von den meisten anderen Programmierungen und kann für Menschen verwirrend sein, die sich nicht mit Prozessordesign oder Computerarchitektur befasst haben.
Sie können sich denken: "Das wird eigentlich nie passieren, wie lange dauert das Kopieren dieses Codes und wie wahrscheinlich ist eine Unterbrechung?" Aber bei den meisten eingebetteten Produktionsanwendungen wird es passieren, weil das Produkt jahrelang ohne Updates läuft.
Das andere Problem bei Strukturkopien wie dieser ist, dass sie, wenn sie passieren, außerordentlich schwer zu debuggen sind, weil sie nur passieren, wenn der Interrupt genau zum richtigen Zeitpunkt auftritt (was so wenig wie ein Zyklus sein kann).
Der springende Punkt bei Interrupts ist, dass sie die ganze Zeit auftreten können (und dies auch tun) und so konzipiert sind, dass sie keinen Einfluss auf jeden Code haben, der gerade ausgeführt wird, wenn sie auftreten. Alle Register werden gespeichert, und je nach CPU-Architektur kann ein völlig anderer Registersatz ausgetauscht werden, der Interrupt tut seine Sache, und dann werden die ursprünglichen Register wiederhergestellt und der Code läuft normal weiter.
Probleme können auftreten, wenn die Interrupt-Serviceroutine selbst versucht, auf Speicher zuzugreifen, auf den der laufende, unterbrochene Code zugreift. Noch subtilere Fehler können auftreten, wenn ein zeitkritischer I/O-Prozess unterbrochen wird. Diese Probleme sind bei älteren, einfacheren und weniger sicheren Architekturen üblich, bei denen möglicherweise nur eine geringe Trennung zwischen "Benutzer"- und "Supervisor/Kernel"-Moduscode besteht.
Diese Art von Problem kann schwer zu identifizieren und oft schwer zu reproduzieren sein, aber wenn sie einmal identifiziert sind, sind sie oft ziemlich trivial zu beheben, indem sie defensive Programmierung, Mutexe/Semaphore oder einfach durch Deaktivieren von Interrupts in kritischen Codeabschnitten verwenden.
Die allgemeine Problemklasse wurde ausführlich untersucht, und moderne Mehrkern-CPUs und sogar Multitasking-Betriebssysteme wären nicht möglich, wenn nicht bereits mehrere Lösungen erprobt und getestet worden wären.
Ich gehe einfach davon aus, dass Sie dies aus einem sehr guten Grund gefragt haben.
*Data_external = Data_internal;
Kann geteilt werden (mit Ausnahme einiger Grenzfälle, die hier wahrscheinlich nicht im Spiel sind).
Ich kenne Ihre CPU nicht, aber ich habe noch keine CPU gesehen, die das moralische Äquivalent von:
cli(); /* mask all interrupts */
*Data_external = Data_internal;
sti(); /* restore interrupt mask */
Jetzt kann es nicht auf eine einzelne Kern-CPU aufgeteilt werden, da nichts unterbrechen kann, während Interrupts ausgeschaltet sind. Ob das eine gute Idee ist oder nicht, hängt von vielen Dingen ab, die ich einfach nicht beurteilen kann.
Wenn Sie Multi-Core sind (und ich habe mich endlich daran erinnert, dass es eine eingebettete Multi-Core-CPU auf dem Markt gibt), tun Sie dies nicht. Es ist wertlos. Sie müssten die richtige Verriegelung entwickeln.
Der Code, wie Sie ihn präsentiert haben, kann tatsächlich unterbrochen werden. Bevor Sie jedoch anfangen, überall kritische Abschnitte zu erstellen, sollten Sie ein paar Dinge überprüfen:
Sie sagen, diese Funktion befindet sich "in einem Treiber". Sind Interrupts bereits deaktiviert, wenn diese Funktion aufgerufen wird? Oder wird es in einem Interrupt-Handler aufgerufen, der verhindert, dass andere Interrupts ausgelöst werden? Falls ja, kann der Betrieb tatsächlich nicht unterbrochen werden.
Wird Data_internal
jemals innerhalb eines Interrupt-Handlers zugegriffen? Wenn nicht, gibt es keinen Schaden, selbst wenn der Betrieb unterbrochen werden kann.
[Nicht genug Repräsentant zum Kommentieren]
Ein weiteres Problem mit dieser Art von Strukturkopie ist, dass es sich um eine flache Kopie handelt . Möglicherweise benötigen Sie stattdessen eine tiefe Kopie.
Eine flache Kopie könnte möglicherweise, aber wahrscheinlich nicht atomar sein, abhängig von der Maschinenarchitektur. Eine tiefe Kopie ist mit ziemlicher Sicherheit auf keiner Architektur atomar.
Eine für die Verwendung in eingebetteten Systemen geeignete Qualitätsimplementierung dokumentiert, wie volatile
-qualifizierte Lese- oder Schreibvorgänge verschiedener Typen ausreichend detailliert ausgeführt werden, um anzuzeigen, ob und wie sie durch Interrupts "geteilt" werden können, und auch, ob flüchtige Lese- und Schreibvorgänge sind oder nicht hinsichtlich nicht qualifizierter Lese- und Schreibvorgänge sequenziert. Während sich einige Implementierungen so verhalten können, als ob alle Lese- und Schreibvorgänge volatile
-qualifiziert wären, wird allgemein erwartet, dass Implementierungen frei sind, Sequenzen von nicht qualifizierten Lese- und Schreibvorgängen auf jede Art und Weise zu verarbeiten, die am effizientesten wäre, wenn es keine dazwischenliegenden volatile
Zugriffe gibt.
Auf einem typischen 32-Bit-Mikrocontroller Lese- und Schreibvorgänge von volatile
-qualifizierten Integer-Typen, die 32 Bit und kleiner sind; eine Zuweisung besteht aus einem atomaren Lesen, gefolgt von einem atomaren Schreiben. Wenn Sie sicherstellen möchten, dass eine 32-Bit-Struktur atomar kopiert wird, platzieren Sie sie in einer Vereinigung mit einem uint32_t
und lesen oder schreiben Sie dieses Element, um die Struktur als Ganzes zu lesen oder zu schreiben. Qualitativ hochwertige Implementierungen, die so konfiguriert sind, dass sie für die Verwendung in eingebetteten Systemen geeignet sind, ermöglichen die Verwendung von Unions auf diese Weise, ohne Rücksicht darauf, ob der Standard Implementierungen verlangen würde, die nicht für eine solche Verwendung vorgesehen sind, dies ebenfalls zu tun. Beachten Sie, dass sich gcc und clang nicht zuverlässig als hochwertige Implementierungen verhalten, die für eingebettete Systeme geeignet sind, es sei denn, verschiedene Optimierungen sind deaktiviert.
PlasmaHH
jonk
David Tweed
jonk
David Tweed
jonk
mbrig
jonk
jonk
Rev
jonk
Rev
jonk