Ich sehe mehrere mögliche Probleme mit diesen kritischen Abschnitten. Es gibt Vorbehalte und Lösungen für all diese Probleme, aber als Zusammenfassung:
- Es hindert den Compiler nicht daran, Code aus Optimierungs- oder anderen zufälligen Gründen über diese Makros zu verschieben.
- Sie speichern und stellen einige Teile des Prozessorstatus wieder her, den der Compiler erwartet (sofern nicht anders angegeben).
- Es hindert nichts daran, dass ein Interrupt in der Mitte der Sequenz auftritt und den Status zwischen dem Zeitpunkt des Lesens und dem Zeitpunkt des Schreibens ändert.
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
und MSR
in ARM_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:
#define ARM_INT_KEY_TYPE unsigned int
#define ARM_INT_LOCK(key_) \
asm volatile(\
"mrs %[key], cpsr\n\t"\
"ands %[key], %[key], #0xC0\n\t"\
"cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
"tst %[key], #0x40\n\t"\
"beq 0f\n\t"\
"cpsie f\n\t"\
"0: tst %[key], #0x80\n\t"\
"beq 1f\n\t"\
"cpsie i\n\t"
"1:\n\t" :: [key]"r" (key_) : "memory", "cc")
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ützt cpsie
und cpsid
.
Ich habe ands
anstelle von nur and
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
und 1
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, mit ARM_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 :
#define ARM_INT_UNLOCK(key_) asm volatile (\
"cbz %[key], 0f\n\t"\
"cpsie if\n\t"\
"0:\n\t" :: [key]"r" (key_) : "memory", "cc")
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 beiden ARM_INT_LOCK
undARM_INT_UNLOCK