Ich portiere einen älteren Code von einem ARM926-Kern auf CortexA9. Dieser Code ist barmetallisch und enthält keine benutzerdefinierten Betriebssystem- oder Standardbibliotheken. Ich habe einen Fehler, der anscheinend mit einer Rennsituation zusammenhängt, die durch eine kritische Unterteilung des Codes verhindert werden sollte.
Ich möchte ein Feedback zu meiner Vorgehensweise, um festzustellen, ob meine kritischen Abschnitte für diese CPU möglicherweise nicht korrekt implementiert sind. Ich benutze GCC. Ich vermute, es liegt ein subtiler Fehler vor.
Gibt es auch eine OpenSource-Bibliothek, die diese Arten von Grundelementen für ARM enthält (oder sogar eine gute, leichte Spinlock- / Semephore-Bibliothek)?
#define ARM_INT_KEY_TYPE unsigned int
#define ARM_INT_LOCK(key_) \
asm volatile(\
"mrs %[key], cpsr\n\t"\
"orr r1, %[key], #0xC0\n\t"\
"msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))
Der Code wird wie folgt verwendet:
/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);
<access registers, shared globals, etc...>
ARM_INT_UNLOCK(key);
Die Idee des "Schlüssels" besteht darin, geschachtelte kritische Abschnitte zuzulassen, die am Anfang und Ende von Funktionen verwendet werden, um wiedereintrittsfähige Funktionen zu erstellen.
Vielen Dank!
quelle
ldrex
undstrex
richtig machen müssen. Auf der folgenden Webseite erfahren Sie, wie Sie einen Spinlock verwendenldrex
undstrex
implementieren.Antworten:
Der schwierigste Teil bei der Behandlung eines kritischen Abschnitts ohne Betriebssystem besteht darin, nicht den Mutex zu erstellen, sondern herauszufinden, was passieren soll, wenn Code eine Ressource verwenden möchte, die derzeit nicht verfügbar ist. Die Anweisungen load-exclusive und conditional-store-exclusive machen es ziemlich einfach, eine "Swap" -Funktion zu erstellen, die bei einem Zeiger auf eine Ganzzahl einen neuen Wert atomar speichert, aber das zurückgibt, was die Ganzzahl, auf die gezeigt wurde, enthielt:
Bei einer Funktion wie der obigen kann man leicht einen Mutex über etwas wie eingeben
In Ermangelung eines Betriebssystems liegt die Hauptschwierigkeit häufig im Code "konnte kein Mutex erhalten". Wenn eine Unterbrechung auftritt, während eine durch Mutex geschützte Ressource belegt ist, muss möglicherweise der Unterbrechungsbehandlungscode ein Flag setzen und einige Informationen speichern, um anzugeben, was er tun möchte, und anschließend über einen main-ähnlichen Code verfügen, der den Code erhält Mutex-Prüfung, wann immer der Mutex freigegeben wird, um festzustellen, ob ein Interrupt etwas tun wollte, während der Mutex gehalten wurde, und in diesem Fall die Aktion für den Interrupt auszuführen.
Obwohl es möglich ist, Probleme mit Interrupts zu vermeiden, die durch Mutex geschützte Ressourcen verwenden möchten, indem Interrupts einfach deaktiviert werden (und in der Tat kann das Deaktivieren von Interrupts die Notwendigkeit anderer Mutex-Typen beseitigen), ist es im Allgemeinen wünschenswert, das Deaktivieren von Interrupts nicht länger als nötig zu vermeiden.
Ein nützlicher Kompromiss kann darin bestehen, ein Flag wie oben beschrieben zu verwenden, aber den Hauptzeilencode, der die Mutex-Deaktivierungs-Interrupts auslösen soll, zu verwenden und das oben genannte Flag unmittelbar vorher zu überprüfen (Interrupts nach dem Auslösen des Mutex wieder zu aktivieren). Ein solcher Ansatz erfordert nicht, dass Interrupts sehr lange deaktiviert bleiben, schützt jedoch vor der Möglichkeit, dass zwischen dem Zeitpunkt, zu dem das Flag angezeigt wird, und dem Zeitpunkt, zu dem das Flag angezeigt wird, die Gefahr besteht, dass der Hauptzeilencode das Flag des Interrupts nach dem Freigeben des Mutex testet Handelt es dagegen, wird es möglicherweise von einem anderen Code beeinträchtigt, der den Mutex erfasst und freigibt und auf das Interrupt-Flag einwirkt. Wenn der Hauptcode das Interrupt-Flag nach dem Freigeben des Mutex nicht testet,
In jedem Fall ist es am wichtigsten, ein Mittel zu haben, mit dem Code, der versucht, eine durch Mutex geschützte Ressource zu verwenden, wenn sie nicht verfügbar ist, den Versuch wiederholen kann, sobald die Ressource freigegeben ist.
quelle
Dies ist eine schwierige Methode, um kritische Abschnitte zu erstellen. Interrupts deaktivieren. Es funktioniert möglicherweise nicht, wenn Ihr System Datenfehler hat / behandelt. Dies erhöht auch die Interrupt-Latenz. Die Linux-Datei irqflags.h enthält einige Makros, die damit umgehen. Die
cpsie
undcpsid
Anweisungen können nützlich sein; Sie speichern jedoch keinen Status und ermöglichen keine Verschachtelung.cps
benutzt kein Register.Für die Cortex-A- Serie
ldrex/strex
sind sie effizienter und können einen Mutex für den kritischen Abschnitt bilden, oder sie können mit sperrfreien Algorithmen verwendet werden, um den kritischen Abschnitt zu entfernen.In gewissem Sinne
ldrex/strex
scheint das ein ARMv5 zu seinswp
. Ihre praktische Umsetzung ist jedoch sehr viel komplexer. Sie benötigen einen funktionierenden Cache und der Zielspeicherldrex/strex
muss sich im Cache befinden. Die ARM-Dokumentation zumldrex/strex
ist ziemlich nebulös, da Mechanismen auf Nicht-Cortex-A-CPUs funktionieren sollen. Für den Cortex-A ist der Mechanismus zum Synchronisieren des lokalen CPU-Cache mit anderen CPUs derselbe, der zum Implementieren derldrex/strex
Anweisungen verwendet wird. Bei der Cortex-A-Serie entspricht das Reservegranual (Größe desldrex/strex
reservierten Speichers) einer Cache-Zeile. Sie müssen den Speicher auch an der Cache-Zeile ausrichten, wenn Sie mehrere Werte ändern möchten, z. B. bei einer doppelt verknüpften Liste.Sie müssen sicherstellen, dass die Sequenz niemals vorab gelesen werden kann . Andernfalls erhalten Sie möglicherweise zwei Schlüsselvariablen mit aktivierten Interrupts, und die Freigabe der Sperre ist falsch. Sie können die
swp
Anweisung mit dem Schlüsselspeicher verwenden, um die Konsistenz auf dem ARMv5 sicherzustellen. Diese Anweisung wird jedoch auf dem Cortex-A nicht mehr empfohlen,ldrex/strex
da sie für Systeme mit mehreren CPUs besser funktioniert.All dies hängt von der Art der Planung Ihres Systems ab. Es hört sich so an, als hätten Sie nur Hauptleitungen und Interrupts. Die Grundelemente für kritische Abschnitte müssen häufig über einige Hooks für den Scheduler verfügen, je nachdem, auf welcher Ebene (System / Benutzerbereich / usw.) der kritische Abschnitt ausgeführt werden soll.
Dies ist auf tragbare Weise schwierig zu schreiben. Das heißt, solche Bibliotheken können für bestimmte Versionen von ARM-CPUs und für bestimmte Betriebssysteme existieren.
quelle
Ich sehe mehrere mögliche Probleme mit diesen kritischen Abschnitten. Es gibt Vorbehalte und Lösungen für all diese Probleme, aber als Zusammenfassung:
Zunächst benötigen Sie auf jeden Fall einige Compiler-Speicherbarrieren . GCC implementiert diese als Clobbers . Im Grunde ist dies eine Möglichkeit, dem Compiler mitzuteilen, "Nein, Sie können Speicherzugriffe nicht über diese Inline-Assembly verschieben, da dies das Ergebnis der Speicherzugriffe beeinflussen kann." Insbesondere benötigen Sie sowohl für das Start- als auch für das Endmakro sowohl Clobbers
"memory"
als auch"cc"
Clobbers. Dadurch wird verhindert, dass andere Dinge (wie Funktionsaufrufe) auch relativ zur Inline-Assembly neu angeordnet werden, da der Compiler weiß, dass sie möglicherweise über Speicherzugriffe verfügen. Ich habe gesehen, dass GCC for ARM den Status in den Zustandscoderegistern in der Inline-Assembly mit"memory"
Clobbern hält, also brauchst du den"cc"
Clobber definitiv .Zweitens speichern und stellen diese kritischen Abschnitte viel mehr wieder her als nur, ob Interrupts aktiviert sind. Insbesondere wird der größte Teil des CPSR (Current Program Status Register) gespeichert und wiederhergestellt (der Link bezieht sich auf Cortex-R4, da ich kein nettes Diagramm für einen A9 gefunden habe, es aber identisch sein sollte). Es gibt subtile Einschränkungen, um welche Teile des Staates tatsächlich geändert werden können, aber es ist hier mehr als notwendig.
Dazu gehören unter anderem die Bedingungscodes (in denen die Ergebnisse von Anweisungen wie
cmp
gespeichert werden, damit nachfolgende bedingte Anweisungen auf das Ergebnis einwirken können). Der Compiler wird dadurch definitiv verwirrt. Dies ist mit dem"cc"
oben erwähnten Clobber leicht lösbar . Dies führt jedoch dazu, dass Code jedes Mal fehlschlägt, sodass es nicht so klingt, als würden Sie Probleme damit sehen. Etwas wie eine tickende Zeitbombe, könnte der Compiler in diesem modifizierenden zufälligen anderen Code dazu führen, dass er etwas anderes macht, was dadurch kaputt geht.Dadurch wird auch versucht, die IT-Bits zu speichern / wiederherzustellen, die zur Implementierung der Thumb-bedingten Ausführung verwendet werden . Beachten Sie, dass dies keine Rolle spielt, wenn Sie niemals Thumb-Code ausführen. Ich habe nie herausgefunden, wie die Inline-Assembly von GCC mit den IT-Bits umgeht, abgesehen von der Schlussfolgerung, dass dies nicht der Fall ist. Der Compiler darf also niemals eine Inline-Assembly in einen IT-Block einfügen und erwartet immer, dass die Assembly außerhalb eines IT-Blocks endet. Ich habe noch nie gesehen, dass GCC Code generiert hat, der gegen diese Annahmen verstößt, und ich habe einige recht komplizierte Inline-Assemblierungen mit intensiver Optimierung durchgeführt, daher bin ich mir ziemlich sicher, dass sie zutreffen. Das heißt, es wird wahrscheinlich nicht wirklich versucht, die IT-Bits zu ändern. In diesem Fall ist alles in Ordnung. Der Versuch, diese Bits zu ändern, wird als "architektonisch unvorhersehbar" eingestuft.Es könnte also alle Arten von schlechten Dingen tun, wird aber wahrscheinlich überhaupt nichts tun.
Die letzte Kategorie von Bits, die gespeichert / wiederhergestellt werden (abgesehen von denjenigen, die Interrupts tatsächlich deaktivieren), sind die Modusbits. Diese werden sich wahrscheinlich nicht ändern, daher spielt es wahrscheinlich keine Rolle, aber wenn Sie einen Code haben, der absichtlich den Modus ändert, können diese Interrupt-Abschnitte Probleme verursachen. Der Wechsel zwischen privilegiertem und Benutzermodus ist der einzige Fall, den ich erwarten würde.
Drittens gibt es nichts , einen Interrupt zu verhindern , dass zu ändern andere Teile CPSR zwischen der
MRS
undMSR
inARM_INT_LOCK
. Solche Änderungen können überschrieben werden. In den meisten vernünftigen Systemen ändern asynchrone Interrupts nicht den Status des Codes, den sie unterbrechen (einschließlich CPSR). Wenn dies der Fall ist, ist es sehr schwierig zu überlegen, welcher Code verwendet wird. Es ist jedoch möglich (das Ändern des FIQ-Deaktivierungsbits erscheint mir am wahrscheinlichsten), daher sollten Sie überlegen, ob Ihr System dies tut.So würde ich diese in einer Weise implementieren, die alle potenziellen Probleme angeht, auf die ich hingewiesen habe:
Stellen Sie sicher , mit zu kompilieren ,
-mcpu=cortex-a9
weil zumindest einige GCC - Versionen (wie bei mir) standardmäßig auf einem älteren ARM CPU , die nicht unterstütztcpsie
undcpsid
.Ich habe
ands
anstelle von nurand
in verwendet,ARM_INT_LOCK
damit es eine 16-Bit-Anweisung ist, wenn dies in Thumb-Code verwendet wird. Der"cc"
Clobber ist sowieso notwendig, es ist also streng genommen ein Vorteil in Bezug auf Leistung / Codegröße.0
und1
sind lokale Bezeichnungen als Referenz.Diese sollten genauso verwendbar sein wie Ihre Versionen. Das
ARM_INT_LOCK
ist genauso schnell / klein wie dein Original. Unglücklicherweise konnte ich mir keine Möglichkeit einfallen lassen, mitARM_INT_UNLOCK
so wenigen Anweisungen sicher zu sein.Wenn Ihr System Einschränkungen hat, wenn IRQs und FIQs deaktiviert sind, kann dies vereinfacht werden. Wenn sie zum Beispiel immer zusammen deaktiviert sind, können Sie eins
cbz
+cpsie if
wie folgt kombinieren :Wenn Sie sich überhaupt nicht für FIQs interessieren, können Sie sie auch ganz deaktivieren oder aktivieren.
Wenn Sie wissen, dass nichts anderes jemals eines der anderen Statusbits in CPSR zwischen dem Sperren und Entsperren ändert, können Sie auch mit etwas weitermachen, das Ihrem ursprünglichen Code sehr ähnlich ist, außer mit beiden
"memory"
und"cc"
Clobbers in beidenARM_INT_LOCK
undARM_INT_UNLOCK
quelle
Für relativ einfache kritische Abschnitte können Sie LDREX- und STREX-Anweisungen verwenden.
/programming/51795537/critical-sections-in-arm http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204f/Cihbghef.html
quelle