Was macht __asm__ __volatile__ ()
und welche Bedeutung hat "memory"
für ARM Architektur?
asm volatile("" ::: "memory");
erstellt eine Speicherbarriere auf Compilerebene, die das Optimierungsprogramm dazu zwingt, die Speicherzugriffe über die Barriere hinweg nicht neu anzuordnen.
Wenn Sie beispielsweise in einer bestimmten Reihenfolge auf eine Adresse zugreifen müssen (wahrscheinlich, weil dieser Speicherbereich tatsächlich von einem anderen Gerät als von einem Speicher gesichert wird), müssen Sie dies dem Compiler mitteilen, da dies sonst möglicherweise nur Ihre Schritte optimiert Der Effizienz zuliebe.
Angenommen, Sie müssen in diesem Szenario einen Wert in der Adresse erhöhen, etwas lesen und einen anderen Wert in einer benachbarten Adresse erhöhen.
int c(int *d, int *e) {
int r;
d[0] += 1;
r = e[0];
d[1] += 1;
return r;
}
Das Problem ist, dass der Compiler (in diesem Fall gcc
) Ihren Speicherzugriff neu ordnen kann, um eine bessere Leistung zu erzielen, wenn Sie danach fragen (-O
). Wahrscheinlich führt dies zu einer Abfolge von Anweisungen wie der folgenden:
00000000 <c>:
0: 4603 mov r3, r0
2: c805 ldmia r0, {r0, r2}
4: 3001 adds r0, #1
6: 3201 adds r2, #1
8: 6018 str r0, [r3, #0]
a: 6808 ldr r0, [r1, #0]
c: 605a str r2, [r3, #4]
e: 4770 bx lr
Die obigen Werte für d[0]
Und d[1]
Werden gleichzeitig geladen. Nehmen wir an, dies ist etwas, das Sie vermeiden möchten. Dann müssen Sie den Compiler anweisen, die Speicherzugriffe nicht neu zu ordnen und asm volatile("" ::: "memory")
zu verwenden.
int c(int *d, int *e) {
int r;
d[0] += 1;
r = e[0];
asm volatile("" ::: "memory");
d[1] += 1;
return r;
}
So erhalten Sie Ihre Anweisungssequenz so, wie Sie es möchten:
00000000 <c>:
0: 6802 ldr r2, [r0, #0]
2: 4603 mov r3, r0
4: 3201 adds r2, #1
6: 6002 str r2, [r0, #0]
8: 6808 ldr r0, [r1, #0]
a: 685a ldr r2, [r3, #4]
c: 3201 adds r2, #1
e: 605a str r2, [r3, #4]
10: 4770 bx lr
12: bf00 nop
Es ist zu beachten, dass dies nur eine Speicherbarriere für die Kompilierungszeit darstellt, um zu vermeiden, dass der Compiler Speicherzugriffe neu anordnet, da keine zusätzlichen Anweisungen auf Hardware-Ebene zum Entleeren von Speichern oder zum Warten auf den Abschluss des Ladens oder Speicherns erforderlich sind. CPUs können Speicherzugriffe weiterhin neu anordnen, wenn sie über die Architekturfunktionen verfügen und die Speicheradressen vom Typ normal
anstelle von strongly ordered
Oder device
( ref ) sind.
Diese Sequenz ist eine Barriere für die Planung des Compilerspeicherzugriffs, wie in dem von Udo referenzierten Artikel erwähnt. Dieses ist GCC-spezifisch - andere Compiler haben andere Möglichkeiten, sie zu beschreiben, einige davon mit expliziteren (und weniger esoterischen) Anweisungen.
__asm__
ist eine gcc-Erweiterung, die die Eingabe von Anweisungen in Assemblersprache in Ihrem C-Code ermöglicht. Hier wird sie verwendet, um Nebeneffekte anzugeben, die den Compiler daran hindern, bestimmte Arten von Optimierungen durchzuführen (die in diesem Fall möglicherweise enden) falschen Code generieren).
__volatile__
ist erforderlich, um sicherzustellen, dass die asm - Anweisung selbst nicht mit anderen flüchtigen Zugriffen neu geordnet wird (eine Garantie in der Sprache C).
memory
ist eine Anweisung an GCC, die besagt, dass die Inline-ASM-Sequenz Nebenwirkungen auf den globalen Speicher hat und daher nicht nur Auswirkungen auf lokale Variablen berücksichtigt werden müssen.
Die Bedeutung wird hier erklärt:
http://en.wikipedia.org/wiki/Memory_ordering
Grundsätzlich bedeutet dies, dass der Assembly-Code dort ausgeführt wird, wo Sie ihn erwarten. Sie weist den Compiler an, die Anweisungen in der Umgebung nicht neu anzuordnen. Das ist es, was codiert wird, bevor dieses Stück Code ausgeführt wird, und was danach codiert wird, wird danach ausgeführt.