Est-ce que l’exemple de la page homme de membarrier est sans intérêt dans x86?

Peut-être que ce n’est que moi, mais l’exemple de la page man 2 pour membarrier semble inutile.

Fondamentalement, membarrier() est une barrière de mémoire asynchrone qui, étant donné deux morceaux de code de coordination (appelons alors chemin rapide et chemin lent ), vous permet de déplacer tout le coût matériel de la barrière vers le chemin lent et de quitter le chemin rapide uniquement avec une barrière de compilateur 1 . Il existe plusieurs manières différentes pour réaliser le comportement de la membarrier , par exemple envoyer un IPI à chaque processeur concerné ou attendre que le code s’exécutant sur chaque processeur soit dé-programmé – mais les détails exacts de l’implémentation ne sont pas importants ici.

Maintenant, voici l’exemple de transformation donné dans la page de manuel :

Code d’origine

 static volatile int a, b; static void fast_path(void) { int read_a, read_b; read_b = b; asm volatile ("mfence" : : : "memory"); read_a = a; /* read_b == 1 implies read_a == 1. */ if (read_b == 1 && read_a == 0) abort(); } static void slow_path(void) { a = 1; asm volatile ("mfence" : : : "memory"); b = 1; } 

Code transformé

(certains appels syscall et initplate-up omis)

 static volatile int a, b; static void fast_path(void) { int read_a, read_b; read_b = b; asm volatile ("" : : : "memory"); read_a = a; /* read_b == 1 implies read_a == 1. */ if (read_b == 1 && read_a == 0) abort(); } static void slow_path(void) { a = 1; membarrier(MEMBARRIER_CMD_SHARED, 0); b = 1; } 

Ici, le slow_path effectue deux écritures ( a , puis b ) séparées par une barrière, et le fast_path effectue deux lectures ( b , puis a ) également séparées par une barrière.

Le modèle de mémoire x86 n’autorise toutefois pas le réapprovisionnement en charge ou en magasin! Donc, pour autant que je membarrier() , membarrier() n’est pas nécessaire du tout dans ces scénarios et la mfence la mfence dans le code d’origine n’était pas nécessaire. Il semble que de simples obstacles au compilateur auraient suffi aux deux endroits 2 .

Un exemple qui a vraiment du sens, IMO, devrait avoir un magasin suivi par un chargement, séparé par une barrière dans la voie rapide .

Est-ce que je manque quelque chose?


1 Une barrière de compilateur empêche le compilateur de déplacer des charges ou des magasins (et, selon l’implémentation, peut forcer certaines valeurs de registre à la mémoire), mais n’émet aucun type d’opération atomique ou de barrière de mémoire. ralentissement de la magnitude inhérent à ces instructions.

2 Bien sûr, sur des plates-formes plus faibles, où la réorganisation du chargement peut avoir lieu, l’exemple pourrait avoir un sens, mais l’exemple est explicitement x86 et membarrier() n’est implémenté que sur x86.

Vous avez raison. Sur x86, cette utilisation particulière de membarrier() est totalement inutile. En fait, cet exemple exact est le tout premier exemple donné par Intel SDM pour illustrer les règles de commande de la mémoire x86:

Intel SDM Vol. 3 §8.2.3.2 Ni les charges ni les magasins ne sont réorganisés avec des opérations similaires entrer la description de l'image ici

Je soumets un correctif à cette page de manuel pour utiliser un exemple de Dekker à la place. Voir https://lkml.org/lkml/2017/9/18/779

Au fait, l’appel système membarrier n’est pas spécifique à x86, mais plutôt à la plupart des architectures Linux.

Merci pour les commentaires!

Mathieu