membarrier
Section: System Calls (2)
Updated: 15 décembre 2022
Index
Return to Main Contents
NOM
membarrier - Poser des barrières mémoire sur un ensemble de threads
BIBLIOTHÈQUE
Bibliothèque C standard (libc, -lc)
SYNOPSIS
#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).
DESCRIPTION
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 :
- MEMBARRIER_CMD_QUERY (depuis Linux 4.3)
-
Rechercher l'ensemble des commandes prises en charge. Le code de retour de
l'appel est un masque de bits des commandes prises en
charge. MEMBARRIER_CMD_QUERY, dont la valeur est 0, n'est pas inclus
dans ce masque de bits. Cette commande est toujours prise en charge (sur les
noyaux où membarrier() est fourni).
- MEMBARRIER_CMD_GLOBAL (depuis Linux 4.16)
-
S'assurer que tous les threads de tous les processus du système passent par
un état où tous les accès mémoire aux adresses de l'espace utilisateur
correspondent à l'organisation du programme entre l'entrée et le retour de
l'appel système membarrier(). Tous les threads du système sont visés par
cette commande.
- MEMBARRIER_CMD_GLOBAL_EXPEDITED (depuis Linux 4.16)
-
Mettre une barrière mémoire sur tous les threads en cours de tous les
processus qui se sont enregistrés précédemment avec
MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED.
-
Lors du retour de l'appel système, le thread appelant a la garantie que tous
les threads en cours sont passés par un état où tous les accès mémoire aux
adresses de l'espace utilisateur correspondent à l'organisation du programme
entre l'entrée et le retour de l'appel système (les threads non en cours
sont dans cet état de facto). Cette garantie n'est apportée qu'aux threads
des processus qui se sont précédemment enregistrés avec
MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED.
-
Étant donné que l'enregistrement concerne l'intention de recevoir des
barrières, il est possible d'appeler
MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED à partir d’un processus qui n’a
pas utilisé MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED.
-
Les commandes « accélérées » (expedited) se terminent plus vite que celles
non accélérées ; elles ne se bloquent jamais, mais elles causent aussi un
temps-système supplémentaire.
- MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED (depuis Linux 4.16)
-
Enregistrer l'intention du processus de recevoir des barrières mémoire
MEMBARRIER_CMD_GLOBAL_EXPEDITED.
- MEMBARRIER_CMD_PRIVATE_EXPEDITED (depuis Linux 4.14)
-
Poser une barrière mémoire sur chaque thread en cours appartenant au même
processus que le thread appelant.
-
Au retour de l'appel système, le thread appelant a la garantie que tous ses
homologues en cours passent par un état où tous les accès mémoire aux
adresses de l'espace utilisateur correspondent à l'ordre du programme entre
l'entrée et le retour de l'appel système (les threads non en cours sont dans
cet état de facto). Cette garantie n'est apportée qu'aux threads du même
processus que le thread appelant.
-
Les commandes « accélérées » (expedited) se terminent plus vite que celles
non accélérées ; elles ne se bloquent jamais, mais elles causent aussi un
temps-système supplémentaire.
-
Un processus doit enregistrer son intention d'utiliser la commande accélérée
privée avant de le faire.
- MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED (depuis Linux 4.14)
-
Enregistrer l'intention du processus d'utiliser
MEMBARRIER_CMD_PRIVATE_EXPEDITED.
- MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE (depuis Linux 4.16)
-
Outre les garanties d'organisation de la mémoire décrites dans
MEMBARRIER_CMD_PRIVATE_EXPEDITED, lors du retour de l'appel système, le
thread appelant a la garantie que tous ses homologues ont exécuté une
instruction de sérialisation du cœur. Cette garantie n'est apportée que pour
les threads du même processus que celui appelant.
-
Les commandes « accélérées » se terminent plus vite que celles non
accélérées, elles ne se bloquent jamais, mais demandent aussi un
temps-système supplémentaire.
-
Un processus doit enregistrer son intention d'utiliser la commande accélérée
privée de synchronisation de cœur avant de le faire.
- MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE (depuis Linux 4.16)
-
Enregistrer l'intention du processus d'utiliser
MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE.
- MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ (depuis Linux 5.10)
-
Assurer au thread appelant, pendant le retour de l'appel système, que tous
ses homologues en cours ont toutes les sections critiques rseq (restartable
sequence) en cours redémarrées si le paramètre flags vaut 0 ; s'il
vaut MEMBARRIER_CMD_FLAG_CPU, cette opération n'est effectuée que sur le
processeur indiqué par cpu_id. Cette garantie n'est apportée qu'aux
threads du même processus que le thread appelant.
-
RSEQ membarrier n'est disponible que sous la forme « private expedited ».
-
Un processus doit enregistrer son intention d'utiliser la commande accélérée
privée rseq avant de le faire.
- MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ (depuis Linux 5.10)
-
Enregistrer l'intention du processus d'utiliser
MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ.
- MEMBARRIER_CMD_SHARED (depuis Linux 4.3)
-
Il s'agit d'un alias pour MEMBARRIER_CMD_GLOBAL pour la
rétrocompatibilité de l'entête.
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
|
VALEUR RENVOYÉE
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().
ERREURS
- EINVAL
-
cmd n'est pas valable ou flags ne vaut pas zéro ou la commande
MEMBARRIER_CMD_GLOBAL est désactivée car le paramètre nohz_full du
processeur a été positionné ou les commandes
MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE et
MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE ne sont pas
implémentées par l'architecture.
- ENOSYS
-
L'appel système membarrier() n'est pas implémenté par ce noyau.
- EPERM
-
Le processus actuel n'était pas enregistré avant d'utiliser les commandes
accélérées privées.
VERSIONS
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);
STANDARDS
membarrier() est spécifique à Linux.
NOTES
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
EXEMPLES
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);
}
TRADUCTION
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 à
Index
- NOM
-
- BIBLIOTHÈQUE
-
- SYNOPSIS
-
- DESCRIPTION
-
- VALEUR RENVOYÉE
-
- ERREURS
-
- VERSIONS
-
- STANDARDS
-
- NOTES
-
- EXEMPLES
-
- TRADUCTION
-
This document was created by
man2html,
using the manual pages.
Time: 20:35:36 GMT, May 22, 2024