2014-10-19 8 views
30

Ich schaute in einige C-Code ausWas macht __asm__ __volatile__ in C?

http://www.mcs.anl.gov/~kazutomo/rdtsc.html

Sie Sachen wie "Inline" verwenden, "asm" usw. wie folgt aus:

code1:

static __inline__ tick gettick (void) { 
    unsigned a, d; 
    __asm__ __volatile__("rdtsc": "=a" (a), "=d" (d)); 
    return (((tick)a) | (((tick)d) << 32)); 
} 

code2:

volatile int __attribute__((noinline)) foo2 (int a0, int a1) { 
    __asm__ __volatile__ (""); 
} 

Ich fragte mich, was macht der Code1 und Code2?

+1

https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html (sonst ignoriere das '__' überall,'__inline__' ist einfach'inline'. –

Antwort

47

Der Modifikator __volatile__ an einem __asm__ Block zwingt den Compiler-Optimierer, den Code unverändert auszuführen.Ohne es könnte der Optimierer denken, dass es entweder direkt entfernt oder aus einer Schleife herausgehoben und zwischengespeichert werden kann.

Dies ist nützlich für die rdtsc Anweisung wie folgt:

__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d)) 

Dieser Vorgang dauert keine Abhängigkeiten, so dass der Compiler könnte den Wert annehmen kann zwischengespeichert werden. Volatile wird verwendet, um einen neuen Zeitstempel zu lesen.

Wenn allein, so benutzten:

__asm__ __volatile__ ("") 

Es wird nicht wirklich etwas auszuführen. Sie können dies erweitern, obwohl, eine Compiler-Speicherbarriere zu erhalten, die keine Anweisungen Speicherzugriff Nachbestellung ermöglicht:

__asm__ __volatile__ ("":::"memory") 

Die rdtsc Anweisung ein gutes Beispiel für die flüchtig ist. rdtsc wird normalerweise verwendet, wenn Sie wissen möchten, wie lange einige Anweisungen zur Ausführung benötigen. Stellen Sie sich vor, einige Code wie diesen, wo Sie Zeit wollen r1 und r2 ‚s Ausführung:

__asm__ ("rdtsc": "=a" (a0), "=d" (d0)) 
r1 = x1 + y1; 
__asm__ ("rdtsc": "=a" (a1), "=d" (d1)) 
r2 = x2 + y2; 
__asm__ ("rdtsc": "=a" (a2), "=d" (d2)) 

Hier ist der Compiler tatsächlich erlaubt den Zeitstempel cachen und gültige Ausgabe zeigen könnte, dass jede Zeile hat genau 0 Takte auszuführen . Natürlich ist dies nicht das, was Sie wollen, so dass Sie __volatile__ einführen verhindern Caching:

__asm__ __volatile__("rdtsc": "=a" (a0), "=d" (d0)) 
r1 = x1 + y1; 
__asm__ __volatile__("rdtsc": "=a" (a1), "=d" (d1)) 
r2 = x2 + y2; 
__asm__ __volatile__("rdtsc": "=a" (a2), "=d" (d2)) 

Jetzt werden Sie einen neuen Zeitstempel jedes Mal, aber es hat immer noch ein Problem, dass sowohl die Compiler und die CPU erlaubt um alle diese Aussagen neu zu ordnen. Es könnte am Ende die asm-Blöcke ausführen, nachdem r1 und r2 bereits berechnet wurden. Um dies zu umgehen, sollten Sie einige Hindernisse hinzufügen, die Serialisierung erzwingen:

__asm__ __volatile__("mfence;rdtsc": "=a" (a0), "=d" (d0) :: "memory") 
r1 = x1 + y1; 
__asm__ __volatile__("mfence;rdtsc": "=a" (a1), "=d" (d1) :: "memory") 
r2 = x2 + y2; 
__asm__ __volatile__("mfence;rdtsc": "=a" (a2), "=d" (d2) :: "memory") 

Notiere die mfence Anweisung hier, die eine CPU-Seite Barriere erzwingt, und das „Gedächtnis“ Bezeichnern in dem flüchtigen Block, der einen Compiler erzwingt Zeitsperre. Bei modernen CPUs können Sie mfence:rdtsc durch rdtscp für etwas effizienter ersetzen.

+0

also mit leeren block, es ist art von instruktionsbarriere? –

+2

Beachten Sie, dass der Compiler nur die statische Code-Reihenfolge steuern kann, die generiert wird, und vermeiden Sie das Verschieben von Material über diese Barriere zur Kompilierungszeit, aber es kann nicht die tatsächliche Ausführungsreihenfolge innerhalb der CPU steuern, die es noch ändern kann (die CPU nicht Kenntnis über das flüchtige Attribut oder den leeren Codeblock). Mit 'rdtsc' kann dies möglicherweise einige Ungenauigkeiten verursachen. – Leeor

+0

@Leeor In der Tat, daher "Compile-Time-Barriere". –

2

asm ist für die Einbeziehung nativen Assembly-Code in den C-Quellcode. Z.B.

int a = 2; 
asm("mov a, 3"); 
printf("%i", a); // will print 3 

Compiler haben verschiedene Varianten davon. __asm__ sollte auch sein, vielleicht mit einigen Compiler-spezifischen Unterschieden.

volatile bedeutet, dass die Variable von außen geändert werden kann (auch nicht durch das C-Programm). Zum Beispiel bei der Programmierung eines Mikrocontrollers in dem die Speicheradresse 0x0000x1234 teilgerätespezifische Schnittstelle zugeordnet ist (das heißt, wenn für den GameBoy Codierung, Knöpfe/Bildschirm/etc auf diese Weise zugegriffen wird.)

volatile std::uint8_t* const button1 = 0x00001111; 

diesem deaktivierten Compiler Optimierungen, die verlassen Sie sich auf *button1 nicht ändern, wenn sie durch den Code geändert werden.

Es wird auch in Multi-Thread-Programmierung verwendet (heute nicht mehr benötigt?), Wo eine Variable von einem anderen Thread geändert werden kann.

inline ist ein Hinweis für den Compiler zu "Inline" -Aufrufen an eine Funktion.

inline int f(int a) { 
    return a + 1 
} 

int a; 
int b = f(a); 

Dies sollte nicht in einen Funktionsaufruf an f aber in int b = a + 1 kompiliert werden. Als ob f wo ein Makro ist. Compiler führen diese Optimierung meistens automatisch durch, abhängig von der Verwendung der Funktionen/Inhalte. in diesem Beispiel könnte eine spezifischere Bedeutung haben.

Ähnlich __attribute__((noinline)) (GCC-spezifische Syntax) verhindert, dass eine Funktion inline wird.

+0

Thx !! Und was ist das? Vorteil von noinline? – user3692521

+1

Und was macht der Assembler-Code in Code2? – user3692521

+0

Ich denke, es stellt nur sicher, dass Aufruf von 'foo2' in einen Funktionsaufruf zu einer leeren Funktion mit zwei ganzzahligen Argumenten übersetzt wird und eine Ganzzahl in der Baugruppe zurückgibt. Diese Funktion könnte dann in dem generierten Assembly-Code implementiert werden – tmlen

-1

Das Attribut __asm__ gibt den Namen an, der im Assemblercode für die Funktion oder Variable verwendet werden soll.

Die __volatile__ Qualifier, verwendet im Allgemeinen in Real-Time-Computing von Embedded-Systemen, behandelt ein Problem mit Compiler Tests des status register für die ERROR oder READY Bit verursacht Probleme bei der Optimierung. __volatile__ wurde eingeführt, um dem Compiler mitzuteilen, dass das Objekt schnellen Änderungen unterliegt, und um jede Referenz des Objekts zu einer echten Referenz zu zwingen.

+0

Nicht wirklich, es ist für alles mit Nebeneffekten, die Sie nicht mit Operanden-Constraints beschreiben können/können, zB wenn Sie wollen, dass es noch passiert auch wenn alle Ausgangsoperanden unbenutzt sind –

+0

Ist das nicht das, was jede Referenz des Objekts zu einer echten Referenz zwingt? Der Grund, warum ich ein wenig verwirrt bin von dem "nicht wirklich" ist die Beschreibung wurde fast wörtlich genommen Referenz-Dokumentation wie im Oktober 2014. Ich werde sehen, ob ich die Cite ausgraben kann. –

+0

Ich war größtenteils nicht damit einverstanden zu sagen, dass es nur relevant für RTC ist. Es geht nicht um "schnelle" Veränderung, nur was alles haben kann Nebenwirkungen. Dass "jede Referenz eine echte Referenz" ist, klingt wie eine Beschreibung des Qualifikationsmerkmals "flüchtiger" Typ (z. B. "volatile int"), nicht GNU C 'asm volatile'. Mit Inline Asm gibt es kein "das Objekt". –