Qu’est-ce que __asm__ __volatile__ ()
et quelle est la signification de la "memory"
pour l’architecture ARM?
asm volatile("" ::: "memory");
crée une barrière de mémoire au niveau du compilateur forçant l’optimiseur à ne pas réordonner les access à la mémoire à travers la barrière.
Par exemple, si vous avez besoin d’accéder à une adresse dans un ordre spécifique (probablement parce que cette zone mémoire est réellement sauvegardée par un périphérique différent de la mémoire), vous devez être capable de l’indiquer au compilateur. par souci d’efficacité.
Supposons que dans ce scénario, vous devez incrémenter une valeur dans l’adresse, lire quelque chose et incrémenter une autre valeur dans une adresse adjacente.
int c(int *d, int *e) { int r; d[0] += 1; r = e[0]; d[1] += 1; return r; }
Le problème est que le compilateur ( gcc
dans ce cas) peut réorganiser votre access mémoire pour obtenir de meilleures performances si vous le demandez ( -O
). Conduisant probablement à une séquence d’instructions comme ci-dessous:
00000000 : 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
Les valeurs ci-dessus pour d[0]
et d[1]
sont chargées en même temps. Supposons que ce soit quelque chose que vous voulez éviter, alors vous devez demander au compilateur de ne pas réordonner les access à la mémoire, c’est-à-dire utiliser asm volatile("" ::: "memory")
.
int c(int *d, int *e) { int r; d[0] += 1; r = e[0]; asm volatile("" ::: "memory"); d[1] += 1; return r; }
Ainsi, vous obtiendrez votre séquence d’instructions comme vous le souhaitez:
00000000 : 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
Il convient de noter qu’il ne s’agit que d’une barrière de mémoire de compilation pour éviter que le compilateur ne réorganise les access à la mémoire, car il ne contient aucune instruction supplémentaire pour vider les mémoires ou attendre que le chargement ou les magasins soient terminés. Les processeurs peuvent toujours réorganiser les access à la mémoire s’ils disposent des capacités architecturales et les adresses de mémoire sont de type normal
au lieu de strongly ordered
ou d’un device
( réf .).
Cette séquence est une barrière de planification de l’access à la mémoire du compilateur, comme indiqué dans l’article référencé par Udo. Celui-ci est spécifique à GCC – d’autres compilateurs ont d’autres façons de les décrire, certains avec des instructions plus explicites (et moins ésotériques).
__asm__
est une extension gcc permettant de saisir des instructions en langage assembleur nestedes dans votre code C – utilisées ici pour pouvoir spécifier des effets secondaires qui empêchent le compilateur d’effectuer certains types d’optimisations (qui pourraient dans ce cas générer code incorrect).
__volatile__
est nécessaire pour s’assurer que l’instruction asm elle-même n’est pas réordonnée avec d’autres access volatils (une garantie dans le langage C).
memory
est une instruction à GCC qui (en quelque sorte) dit que la séquence asm en ligne a des effets secondaires sur la mémoire globale et que, par conséquent, les effets sur les variables locales doivent non seulement être pris en compte.
Le sens est expliqué ici:
http://en.wikipedia.org/wiki/Memory_ordering
Fondamentalement, cela implique que le code d’assemblage sera exécuté à l’endroit prévu. Il indique au compilateur de ne pas réorganiser les instructions autour de lui. C’est ce qui est codé avant que ce morceau de code ne soit exécuté avant et ce qui est codé après sera exécuté après.
static inline unsigned long arch_local_irq_save(void) { unsigned long flags; asm volatile( " mrs %0, cpsr @ arch_local_irq_save\n" " cpsid i" //disabled irq : "=r" (flags) : : "memory", "cc"); return flags; }