web-dev-qa-db-fra.com

Que fait __asm__ __volatile__ en C?

J'ai regardé un code C de

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

Ils utilisent des trucs comme " inline ", " asm " etc. comme le Suivant:

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__ ("");
}

Je me demandais ce que font le code1 et le code2?

37
user3692521

Le modificateur __volatile__ Sur un bloc __asm__ Force l'optimiseur du compilateur à exécuter le code tel quel. Sans cela, l'optimiseur peut penser qu'il peut être soit supprimé complètement, soit retiré d'une boucle et mis en cache.

Ceci est utile pour l'instruction rdtsc comme ceci:

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

Cela ne prend aucune dépendance, le compilateur peut donc supposer que la valeur peut être mise en cache. Volatile est utilisé pour le forcer à lire un nouvel horodatage.

Lorsqu'il est utilisé seul, comme ceci:

__asm__ __volatile__ ("")

Il n'exécutera rien. Vous pouvez cependant l'étendre pour obtenir une barrière de mémoire au moment de la compilation qui ne permettra pas de réorganiser les instructions d'accès à la mémoire:

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

L'instruction rdtsc est un bon exemple pour volatile. rdtsc est généralement utilisé lorsque vous devez chronométrer le temps d'exécution de certaines instructions. Imaginez un code comme celui-ci, où vous voulez chronométrer l'exécution de r1 Et de r2:

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

Ici, le compilateur est réellement autorisé à mettre en cache l'horodatage, et une sortie valide peut montrer que chaque ligne a pris exactement 0 horloge pour s'exécuter. Évidemment, ce n'est pas ce que vous voulez, donc vous introduisez __volatile__ Pour empêcher la mise en cache:

__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))

Vous obtiendrez à présent un nouvel horodatage à chaque fois, mais le problème persiste: le compilateur et le processeur sont autorisés à réorganiser toutes ces instructions. Il pourrait finir par exécuter les blocs asm après que r1 et r2 aient déjà été calculés. Pour contourner ce problème, vous ajouteriez quelques barrières qui forcent la sérialisation:

__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")

Notez l'instruction mfence ici, qui applique une barrière côté processeur, et le spécificateur "mémoire" dans le bloc volatile qui applique une barrière au moment de la compilation. Sur les processeurs modernes, vous pouvez remplacer mfence:rdtsc Par rdtscp pour quelque chose de plus efficace.

57
Cory Nelson

asm sert à inclure du code assembleur natif dans le code source C. Par exemple.

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

Les compilateurs en ont différentes variantes. __asm__ Devrait être synonyme, peut-être avec quelques différences spécifiques au compilateur.

volatile signifie que la variable peut être modifiée de l'extérieur (alias pas par le programme C). Par exemple, lors de la programmation d'un microcontrôleur où l'adresse mémoire 0x0000x1234 Est mappée à une interface spécifique à l'appareil (c'est-à-dire lors du codage pour le GameBoy, les boutons/écran/etc. sont accessibles de cette façon.)

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

Cette optimisation du compilateur désactivé qui repose sur *button1 Ne change pas à moins d'être modifiée par le code.

Il est également utilisé dans la programmation multi-thread (qui n'est plus nécessaire aujourd'hui?) Où une variable peut être modifiée par un autre thread.

inline est une indication pour le compilateur d'appeler "en ligne" une fonction.

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

int a;
int b = f(a);

Cela ne doit pas être compilé dans un appel de fonction à f mais dans int b = a + 1. Comme si f où une macro. Les compilateurs effectuent généralement cette optimisation automatiquement en fonction de l'utilisation/du contenu de la fonction. __inline__ Dans cet exemple peut avoir une signification plus spécifique.

De même, __attribute__((noinline)) (syntaxe spécifique à GCC) empêche une fonction d'être alignée.

5
tmlen