membarrier(2) | System Calls Manual | membarrier(2) |
membarrier - Poser des barrières mémoire sur un ensemble de threads
Bibliothèque C standard (libc, -lc)
#include <linux/membarrier.h> /* Définition des constantes MEMBARRIER_* */ #include <sys/syscall.h> /* Définition des constantes SYS_* */ #include <unistd.h>
int syscall(SYS_membarrier, int cmd, unsigned int flags, int cpu_id);
Note : la glibc ne fournit pas de fonction d'enveloppe pour membarrier(), nécessitant l'utilisation de syscall(2).
L'appel système membarrier() aide à réduire le temps-système des instructions de barrières mémoire nécessaire pour organiser les accès à la mémoire sur des systèmes à plusieurs cœurs. Cependant, cet appel système est plus lourd qu'une barrière mémoire, donc l'utiliser efficacement « n'est pas » aussi simple que de remplacer une barrière mémoire par cet appel système, mais nécessite de comprendre les détails ci-dessous.
L'utilisation de barrières mémoire doit se faire en tenant compte du fait qu'elles doivent soit être associées avec leurs homologues, ou que le modèle de mémoire de l'architecture n'a pas besoin de barrières associées.
Dans certains cas, une face des barrières associées (qu'on appellera la « face rapide ») est sollicitée beaucoup plus souvent que l'autre (qu'on appellera la « face lente »). C'est le motif principal pour utiliser membarrier(). L'idée clé est de remplacer, pour ces barrières associées, les barrières mémoire de la face rapide par de simples barrières du compilateur, par exemple :
asm volatile ("" : : : "memory")
et de remplacer les barrières mémoire de la face lente par des appels à membarrier().
Cela ajoutera du temps-système à la face lente et en supprimera de la face rapide, d'où une augmentation globale de performances tant que la face lente est si peu utilisée que le temps-système d'appels membarrier() ne l’emporte pas sur le gain de performance de la face rapide.
Le paramètre cmd est l'un des suivants :
Le paramètre flags doit être indiqué en tant que 0, sauf si la commande est MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ, auquel cas flags peut être soit 0, soit MEMBARRIER_CMD_FLAG_CPU.
Le paramètre cpu_id est ignoré sauf si flags est MEMBARRIER_CMD_FLAG_CPU, auquel cas il doit indiquer le processeur ciblé par cette commande membarrier.
Tous les accès mémoire effectués dans l'organisation du programme à partir de chaque thread visé sont garantis d'être organisés par rapport à membarrier().
Si nous utilisons la sémantique barrier() pour représenter une barrière du compilateur qui force les accès mémoire à s'opérer dans l'ordre du programme le long des barrières, et smp_mb() pour représenter les barrières explicites de la mémoire qui forcent toute la mémoire à s'organiser le long de la barrière, nous obtenons le tableau d'organisation suivant pour chaque paire de barrier(), membarrier() et smp_mb(). L'organisation de la paire est détaillée ainsi (O : organisée, X : non organisée) :
barrier() | smp_mb() | membarrier() | |
barrier() | X | X | O |
smp_mb() | X | O | O |
membarrier() | O | O | O |
En cas de succès, l'opération MEMBARRIER_CMD_QUERY renvoie un masque de bits des commandes prises en charge, et les opérations MEMBARRIER_CMD_GLOBAL, MEMBARRIER_CMD_GLOBAL_EXPEDITED, MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, MEMBARRIER_CMD_PRIVATE_EXPEDITED, MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE et MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE renvoient 0. En cas d'erreur, -1 est renvoyé et errno est défini pour indiquer l'erreur.
Pour une commande donnée, quand flags est positionné sur 0, cet appel système est garanti de renvoyer toujours la même valeur jusqu'au redémarrage. Les appels suivants ayant les mêmes paramètres conduiront au même résultat. Donc, quand flags est positionné sur 0, une gestion des erreurs n'est nécessaire que pour le premier appel à membarrier().
L'appel système membarrier() a été ajouté dans Linux 4.3.
Avant Linux 5.10, le prototype de membarrier() était :
int membarrier(int cmd, int flags);
membarrier() est spécifique à Linux.
Une instruction de barrière mémoire fait partie du jeu d'instructions des architectures ayant des modèles de mémoire faiblement organisés. Elle organise les accès mémoire avant et après la barrière par rapport à celles correspondantes sur les autres cœurs. Par exemple, une barrière de charge peut organiser les charges avant et après elle par rapport aux stockages conservés dans les barrières de stockage.
L'organisation du programme est l'ordre dans lequel les instructions sont ordonnées dans le code d'assembleur du programme.
Parmi les exemples où membarrier() peut être utile, figurent les implémentations des bibliothèques Read-Copy-Update et des ramasse-miettes
Supposons une application multithreadée où « fast_path() » est exécutée très souvent et où « slow_path() » l'est rarement, alors le code suivant (x86) peut être transformé en utilisant membarrier() :
#include <stdlib.h> static volatile int a, b; static void fast_path(int *read_b) {
a = 1;
asm volatile ("mfence" : : : "memory");
*read_b = b; } static void slow_path(int *read_a) {
b = 1;
asm volatile ("mfence" : : : "memory");
*read_a = a; } int main(void) {
int read_a, read_b;
/*
* De vraies applications appelleraient fast_path() et slow_path()
* à partir de différents threads. Appel à partir de main()
* pour garder cet exemple court.
*/
slow_path(&read_a);
fast_path(&read_b);
/*
* read_b == 0 implique que read_a == 1 et
* read_a == 0 implique que read_b == 1.
*/
if (read_b == 0 && read_a == 0)
abort();
exit(EXIT_SUCCESS); }
Le code ci-dessus transformé pour utiliser membarrier() donne :
#define _GNU_SOURCE #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/syscall.h> #include <linux/membarrier.h> static volatile int a, b; static int membarrier(int cmd, unsigned int flags, int cpu_id) {
return syscall(__NR_membarrier, cmd, flags, cpu_id); } static int init_membarrier(void) {
int ret;
/* Vérifier que membarrier() est pris en charge. */
ret = membarrier(MEMBARRIER_CMD_QUERY, 0, 0);
if (ret < 0) {
perror("membarrier");
return -1;
}
if (!(ret & MEMBARRIER_CMD_GLOBAL)) {
fprintf(stderr,
"membarrier ne gère pas MEMBARRIER_CMD_GLOBAL\n");
return -1;
}
return 0; } static void fast_path(int *read_b) {
a = 1;
asm volatile ("" : : : "memory");
*read_b = b; } static void slow_path(int *read_a) {
b = 1;
membarrier(MEMBARRIER_CMD_GLOBAL, 0, 0);
*read_a = a; } int main(int argc, char *argv[]) {
int read_a, read_b;
if (init_membarrier())
exit(EXIT_FAILURE);
/*
* De vraies applications appelleraient fast_path() et slow_path()
* à partir de différents threads. Appel à partir de main()
* pour garder cet exemple court.
*/
slow_path(&read_a);
fast_path(&read_b);
/*
* read_b == 0 implique que read_a == 1 et
* read_a == 0 implique que read_b == 1.
*/
if (read_b == 0 && read_a == 0)
abort();
exit(EXIT_SUCCESS); }
La traduction française de cette page de manuel a été créée par Christophe Blaess <https://www.blaess.fr/christophe/>, Stéphan Rafin <stephan.rafin@laposte.net>, Thierry Vignaud <tvignaud@mandriva.com>, François Micaux, Alain Portal <aportal@univ-montp2.fr>, Jean-Philippe Guérard <fevrier@tigreraye.org>, Jean-Luc Coulon (f5ibh) <jean-luc.coulon@wanadoo.fr>, Julien Cristau <jcristau@debian.org>, Thomas Huriaux <thomas.huriaux@gmail.com>, Nicolas François <nicolas.francois@centraliens.net>, Florentin Duneau <fduneau@gmail.com>, Simon Paillard <simon.paillard@resel.enst-bretagne.fr>, Denis Barbier <barbier@debian.org>, David Prévot <david@tilapin.org> et Jean-Philippe MENGUAL <jpmengual@debian.org>
Cette traduction est une documentation libre ; veuillez vous reporter à la GNU General Public License version 3 concernant les conditions de copie et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.
Si vous découvrez un bogue dans la traduction de cette page de manuel, veuillez envoyer un message à debian-l10n-french@lists.debian.org.
15 décembre 2022 | Pages du manuel de Linux 6.03 |