Ist es möglich, den Kopiervorgang einer Struktur durch einen Interrupt in eingebettetem C zu unterbrechen?

8

Im Treiber habe ich eine Funktion zum Kopieren der Daten aus der internen Struktur in eine Struktur aus der Anwendung.

Kann dieser Prozess 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;
}
Stani
quelle
17
Es gibt nicht wirklich einen "Kopiervorgang", es gibt Anweisungen zum Kopieren von Bytes und wenn sie nicht atomar sind, können sie unterbrochen werden, abhängig von Ihrer Architektur, Ihrem Compiler, Ihren Einstellungen und Ihrer Ausrichtung
PlasmaHH
10
In C gibt es keine Garantie dafür, dass ein Strukturkopiervorgang nicht unterbrochen werden kann. Der Interrupt soll jedoch normalerweise zu dem Punkt im Code zurückkehren, an dem der Kopiervorgang abgeschlossen werden kann. Es spielt also wahrscheinlich keine Rolle, es sei denn, der Interrupt-Code hängt aus irgendeinem Grund davon ab (in den meisten Fällen unwahrscheinlich). Wenn diese Strukturen jedoch Teil eines oberen / unteren Interrupt-Treiberpaars sind, kann dies ein Problem sein, wenn die obere Ebene ist dabei, einen "Chunk" zu einem Puffer zu entfernen oder hinzuzufügen, wenn ein Interrupt stattfindet, der auch "Chunks" im selben Puffer verarbeiten muss.
Jonk
4
@ Jonk: Bitte beantworten Sie die Frage nicht in Kommentaren, da dies den normalen Überprüfungsprozess für Antworten umgeht, wie in Meta
Dave Tweed
6
@ DaveTweed Dann verlieren Sie und diese Seite meine Kommentare. Ich schreibe keine Antworten, es sei denn, ich habe die Zeit, umfassendere Antworten zu schreiben. Es ist meine einzige Arbeitsweise und ich werde sie nicht ändern. Wenn ich nach meiner Definition das Gefühl habe, genug Zeit für eine "richtige Antwort" zu haben, werde ich wie bisher fortfahren. Aber ich arbeite sehr hart an verschiedenen aktiven Projekten und habe NICHT die Zeit für meinen üblichen Grad an Antwortkontext und Inhalt. Entweder schreibe ich kurze Kommentare als Kommentare oder die Site verliert jeglichen Zugriff auf meine Zeit. Ihr Anruf. Meine Zeit wird zu meinen Bedingungen oder überhaupt nicht angegeben.
Jonk
4
@jonk: Es ist nichts Falsches daran, eine kurze Antwort zu schreiben, solange sie vollständig und in sich geschlossen ist, so wie es Ihr Kommentar ist. Ihr Engagement für langwierige Antworten liegt ganz bei Ihnen. Die Site hat Regeln und Prozesse, und ich versuche nur, Sie in die richtige Richtung zu bewegen. Letztendlich liegt es ganz bei Ihnen - Sie hätten 9 positive Stimmen (+90 Wiederholungen) für Ihre Antwort erhalten können ...
Dave Tweed

Antworten:

25

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 speziellen Fall 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 (Interrupts grundsätzlich global deaktivieren, den Job ausführen, erneut aktivieren).

Denken Sie daran, dass abhängig von der Architektur der Ziel-CPU eine einzelne Zeile von C zu vielen Montageanweisungen kompiliert werden kann. Ein einfacher i++auf einem AVR ist mit mehreren Anweisungen kompiliert , wenn ibeispielsweise ist uint32_t.

Filo
quelle
1
In Bezug auf Ihren letzten Absatz gehe ich davon aus, dass dies nur auf 16/8-Bit-Systemen zutrifft, oder? Ich weiß, dass du AVR sagst.
Harry Svensson
1
memcpy einer Struktur (wie in der Frage) besteht auch aus mehreren Anweisungen auf 32-Bit-ARM. Die Inkrementierung von uint64_t auf 32-Bit-ARM ist ebenfalls nicht atomar. Bitweise Operationen sind auch Lesen-Ändern-Schreiben (es sei denn, Bit-Banding wird verwendet).
Filo
4
@HarrySvensson no - auf jeder RMW-Architektur und die meisten uCs sind Read - Modify - Write - (die meisten RISC-Prozesse sind RMW). Daher generiert i ++ in vielen Fällen (außer wenn der Wert nur im Register gespeichert wird, was hier nicht der Fall ist) drei Anweisungen auch auf den modernen 32/64-Bit-ARM-uCs.
P__J__
Ah, hier gab es ein leichtes Missverständnis. Ich habe darüber nachgedacht, mich beim Ausführen von Inkrementen (Überlauf) um den Übertrag zu kümmern, und Sie beide sprechen davon, aus dem Speicher abzurufen und zurückzuschreiben. - Naja.
Harry Svensson
1
Im Allgemeinen: Alle Operationen an Typen, die breiter als die Datenbusbreite sind, sind "funky".
Filo
11

Jede Operation, die nicht atomar ist, kann durch einen Interrupt gestört werden. Diese Art der Programmierung unterscheidet sich häufig stark von den meisten anderen Programmen und kann für Personen verwirrend sein, die weder Prozessordesign noch Computerarchitektur studiert haben.

Sie denken sich vielleicht: "Dies wird eigentlich nie passieren, wie lange dauert das Kopieren dieses Codes und wie wahrscheinlich ist eine Unterbrechung?" Bei den meisten Embedded-Anwendungen für die Produktion ist dies jedoch der Fall, da das Produkt jahrelang ohne Updates läuft.

Das andere Problem bei solchen Strukturkopien ist, dass sie außerordentlich schwer zu debuggen sind, wenn sie auftreten, da sie nur dann auftreten, wenn der Interrupt genau zum richtigen Zeitpunkt auftritt (der nur einen Zyklus betragen kann).

Sam
quelle
10

Der springende Punkt bei Interrupts ist, dass sie jederzeit auftreten können (und dies auch tun) und so konzipiert sind, dass sie keinen Einfluss auf Code haben, der gerade ausgeführt wird, wenn sie auftreten. Alle Register werden gespeichert, und abhängig von der CPU-Architektur kann ein völlig anderer Registersatz ausgetauscht werden, der Interrupt erledigt seine Aufgabe, und dann werden die ursprünglichen Register wiederhergestellt und der Code läuft wie gewohnt weiter.

Probleme können auftreten, wenn die Interrupt-Serviceroutine selbst versucht, auf den Speicher zuzugreifen, auf den der laufende, unterbrochene Code zugreift. Noch subtilere Fehler können auftreten, wenn ein zeitkritischer E / A-Prozess unterbrochen wird. Diese Probleme treten häufig bei älteren, einfacheren und weniger sicheren Architekturen auf, bei denen möglicherweise nur eine geringe Trennung zwischen dem Moduscode "Benutzer" und "Supervisor / Kernel" 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, um sie mit defensiver Programmierung, Mutexen / Semaphoren oder einfach durch Deaktivieren von Interrupts in kritischen Codeabschnitten zu beheben.

Die allgemeine Klasse von Problemen 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.

Echelon
quelle
5

Ich gehe einfach davon aus, dass Sie dies aus einem sehr guten Grund gefragt haben.

*Data_external = Data_internal;

Kann aufgeteilt werden (mit Ausnahme einiger Randfälle, die hier wahrscheinlich nicht im Spiel sind).

Ich kenne Ihre CPU nicht, aber ich habe noch keine CPU gesehen, die nicht 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 unterbrochen werden kann, während die Interrupts ausgeschaltet sind. Ob dies eine gute Idee ist oder nicht, hängt von vielen Dingen ab, für deren Bewertung ich einfach nicht qualifiziert bin.

Wenn Sie Multi-Core sind (und ich erinnerte mich schließlich, dass es eine Multi-Core-Embedded-CPU auf dem Markt gibt), tun Sie dies nicht. Es ist wertlos. Sie müssten eine ordnungsgemäße Verriegelung entwickeln.

Joshua
quelle
Was sind Cli und Sti? von Google scheinen sie x86-Anweisungen zu sein?
Sam
@ Sam: Nun ja, das sind die Namen der Anweisungen in x86. Ich denke, sie haben die gleichen Namen im PDP-11, weil einige Codes, die dort entstanden sind, die gleichen Funktionsnamen für ein logisches Äquivalent im Benutzermodus hatten (Masken-Interprozess-Signale). Hier sind Anweisungen für Arduino: tldp.org/LDP/tlk/dd/interrupts.html
Joshua
3
Normalerweise würden Sie Interrupts deaktivieren, aber nur dann wieder aktivieren, wenn sie tatsächlich aktiviert wurden. Andernfalls hat Ihr Code den Nebeneffekt, dass Interrupts immer aktiviert werden, auch wenn sie vor dem Aufrufen des Codes deaktiviert wurden.
Tom Carpenter
3

Der Code, wie Sie ihn präsentiert haben, kann tatsächlich unterbrochen werden. Bevor Sie jedoch anfangen, überall kritische Abschnitte zu erstellen, sollten Sie einige 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? Wenn ja, kann der Vorgang tatsächlich nicht unterbrochen werden.

  • Wird Data_internaljemals in einem Interrupt-Handler zugegriffen? Wenn nicht, liegt kein Schaden vor, auch wenn der Betrieb unterbrochen werden kann.

Dmitry Grigoryev
quelle
1

[Nicht genug Vertreter, um einen Kommentar abzugeben]

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 kann je nach Maschinenarchitektur möglicherweise, aber wahrscheinlich nicht atomar sein. Eine tiefe Kopie ist mit ziemlicher Sicherheit in keiner Architektur atomar.

studog
quelle
Ein fairer Punkt; Eine tiefe Kopie ist jedoch eher atomar, da sie fast immer durch einen Zeigertausch erstellt wird. In diesem Fall wäre es jedoch definitiv nicht atomar, wenn es eine tiefe Kopie hätte sein sollen.
Joshua
0

Eine Qualitätsimplementierung, die für die Verwendung eingebetteter Systeme geeignet ist, dokumentiert, wie qualifizierte volatileLese- oder Schreibvorgänge verschiedener Typen ausreichend detailliert ausgeführt werden, um anzugeben, ob und wie sie durch Interrupts "aufgeteilt" werden können und ob flüchtige Lese- und Schreibvorgänge sind oder nicht sequenziert in Bezug auf nicht qualifizierte Lese- und Schreibvorgänge. Während sich einige Implementierungen möglicherweise so verhalten, als wären alle volatileLese- und Schreibvorgänge qualifiziert, wird von Implementierungen im Allgemeinen erwartet, dass sie Sequenzen nicht qualifizierter Lese- und Schreibvorgänge auf die Weise verarbeiten können, die am effizientesten wäre, wenn keine dazwischenliegenden volatileZugriffe vorliegen.

Liest und schreibt auf einem typischen 32-Bit-Mikrocontroller von volatilequalifizierten Ganzzahltypen, 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 a uint32_tund lesen oder schreiben Sie dieses Element, um die Struktur als Ganzes zu lesen oder zu schreiben. Qualitätsimplementierungen, die so konfiguriert sind, dass sie für die Verwendung eingebetteter Systeme geeignet sind, ermöglichen es Gewerkschaften, auf diese Weise eingesetzt zu werden, ohne zu berücksichtigen, ob der Standard Implementierungen erfordern würde, die nicht für eine solche Verwendung vorgesehen sind. Beachten Sie, dass sich gcc und clang nicht zuverlässig als Qualitätsimplementierungen verhalten, die für die Verwendung eingebetteter Systeme geeignet sind, es sei denn, verschiedene Optimierungen sind deaktiviert.

Superkatze
quelle