futex
Section: System Calls (2)
Updated: 5 février 2023
Index
Return to Main Contents
NOM
futex – Verrouillage rapide en mode utilisateur
BIBLIOTHÈQUE
Bibliothèque C standard (libc, -lc)
SYNOPSIS
#include <linux/futex.h> /* Définition des constantes FUTEX_* */
#include <sys/syscall.h> /* Définition des constantes SYS_* */
#include <unistd.h>
long syscall(SYS_futex, uint32_t *uaddr, int futex_op, uint32_t val,
const struct timespec *timeout, /* ou : uint32_t val2 */
uint32_t *uaddr2, uint32_t val3);
Remarque : la glibc ne fournit pas de fonction autour de futex(),
nécessitant l'utilisation de syscall(2).
DESCRIPTION
L'appel système futex() offre une méthode pour attendre qu'une condition
soit vraie. On l'utilise en général comme construction de blocage dans le
contexte de la synchronisation de la mémoire partagée. Quand on utilise des
futex, la majorité des opérations de synchronisation s'effectue dans
l'espace utilisateur. Un programme de l'espace utilisateur n'utilise l'appel
système futex() que lorsqu'il est probable qu'il doive se bloquer plus
longtemps avant que la condition ne soit vraie. D'autres opérations
futex() peuvent être utilisées pour réveiller des processus ou des
threads qui attendent une condition en particulier.
Un futex est une valeur 32 bits — désignée ci-dessous comme « mot futex » —dont l'adresse est fournie à l'appel système futex() (les futex
ont une taille de 32 bits sur toutes les plateformes, y compris les systèmes
64 bits). Toutes les opérations futex sont pilotées par cette valeur. Afin
de partager un futex entre des processus, le futex est placé dans une zone
de la mémoire partagée créée en utilisant (par exemple) mmap(2) ou
shmat(2) (ainsi, le mot futex peut avoir plusieurs adresses virtuelles
dans différents processus, mais ces adresses se rapportent toutes au même
emplacement de la mémoire physique). Dans un programme multithreadé, il
suffit de mettre le mot futex dans une variable globale partagée par tous
les threads.
Lors de l'exécution d'une opération futex qui demande le blocage d'un
thread, le noyau ne le bloquera que si le mot futex a une valeur fournie par
le thread appelant (en tant qu'un des paramètres de l'appel futex())
correspondant à celle prévue du mot futex. Le chargement de la valeur du mot
futex, la comparaison de cette valeur avec celle attendue et le blocage
s'effectueront de manière atomique et seront entièrement organisés par
rapport aux opérations qui sont effectuées en parallèle par d'autres threads
sur le même mot futex. Ainsi, le mot futex est utilisé pour relier la
synchronisation de l'espace utilisateur et l'implémentation du blocage par
le noyau. Tout comme une opération compare-and-exchange atomique qui modifie
potentiellement la mémoire partagée, le blocage par futex est une opération
compare-and-block atomique.
Une utilisation des futex consiste à implémenter des verrous. L'état du
verrou (c'est-à-dire acquis ou non acquis) peut se représenter comme un
drapeau auquel on a un accès atomique en mémoire partagée. En absence de
conflit (uncontended case), un thread peut accéder et modifier l'état du
verrou avec des instructions atomiques, par exemple le passer de manière
atomique de l'état non acquis à acquis, en utilisant une instruction
compare-and-exchange atomique (de telles instructions s'effectuent
entièrement dans l'espace utilisateur et le noyau ne conserve aucune
information sur l'état du verrou). D'un autre côté, un thread peut être
incapable d'acquérir un verrou parce qu'il est déjà acquis par un autre
thread. Il peut alors passer l'attribut du verrou en tant que mot futex, et
la valeur représentant l'état acquis en tant que valeur attendue pour
l'opération d'attente de futex(). Cette opération futex() bloquera si
et seulement si le verrou est encore acquis (c'est-à-dire si la valeur du
mot futex correspond toujours à « l'état acquis »). Lorsque le verrou est
relâché, le thread doit d'abord réinitialiser l'état du verrou sur non
acquis puis exécuter une opération futex qui réveille les threads bloqués
par le drapeau de verrou utilisé en tant que mot futex (cela peut être mieux
optimisé pour éviter les réveils inutiles). Voir futex(7) pour plus de
détails sur la manière d'utiliser les futex.
Outre la fonctionnalité de base du futex consistant à attendre et à
réveiller, d'autres opérations futex visent à gérer des cas d'utilisation
plus complexes.
Remarquez qu'aucune initialisation ou destruction explicite n'est nécessaire
pour utiliser les futex ; le noyau ne garde un futex (c'est-à-dire un
artefact d'implémentation interne au noyau) que pendant que les opérations
telles que FUTEX_WAIT, décrite ci-dessous, s'effectuent sur un mot futex
en particulier.
Arguments
Le paramètre uaddr pointe vers un mot futex. Sur toutes les plateformes,
les futex sont des entiers de quatre octets qui doivent être alignés sur une
limite de quatre octets. L'opération à effectuer sur le futex est indiquée
dans le paramètre de futex_op ; val est une valeur dont la
signification et l'objectif dépendent de futex_op.
Les autres paramètres (timeout, uaddr2 et val3) ne sont nécessaires
que pour certaines opérations futex décrites ci-dessous. Si un de ces
arguments n'est pas nécessaire, il est ignoré.
Pour plusieurs opérations de blocage, le paramètre timeout est un
pointeur vers une structure timespec qui indique la durée maximale de
l'opération. Toutefois, contrairement au prototype décrit ci-dessus, pour
certaines opérations, les quatre octets les moins significatifs de ce
paramètre sont utilisés comme un entier dont la signification est déterminée
par l'opération. Pour ces opérations, le noyau diffuse la valeur timeout
d'abord à unsigned long, puis à uint32_t, et dans le reste de cette
page, ce paramètre est désigné par val2 quand il est interprété de cette
manière.
Lorsqu'il est nécessaire, le paramètre uaddr2 est un pointeur vers un
deuxième mot futex utilisé par l'opération.
L'interprétation du paramètre de l'entier final, val3, dépend de
l'opération.
Opérations futex
Le paramètre futex_op est en deux parties : une commande qui indique
l'opération à effectuer et un bit ORed avec zéro ou plusieurs options qui
changent le comportement de l'opération. Les options qui peuvent être
incluses dans futex_op sont les suivantes :
- FUTEX_PRIVATE_FLAG (depuis Linux 2.6.22)
-
Ce bit d'option peut être utilisé avec toutes les opérations futex. Il dit
au noyau que le futex est un processus privé non partagé avec d'autres
processus (c'est-à-dire qu'il n'est utilisé que pour la synchronisation
entre les threads du même processus). Cela permet au noyau d'effectuer des
optimisations de performance supplémentaires.
-
Par commodité, <linux/futex.h> définit un ensemble de constantes
dont le suffixe est _PRIVATE et qui sont équivalentes à toutes les
opérations listées ci-dessous mais avec l'attribut FUTEX_PRIVATE_FLAG
ORed dans la valeur de la constante. On trouve ainsi FUTEX_WAIT_PRIVATE,
FUTEX_WAKE_PRIVATE et ainsi de suite.
- FUTEX_CLOCK_REALTIME (depuis Linux 2.6.28)
-
Ce bit d'option ne peut être utilisé qu'avec les opérations
FUTEX_WAIT_BITSET, FUTEX_WAIT_REQUEUE_PI (depuis Linux 4.5),
FUTEX_WAIT (depuis Linux 4.5) et FUTEX_LOCK_PI2 (depuis Linux 5.14).
-
Si cette option est positionnée, le noyau mesure le timeout par rapport à
l'horloge CLOCK_REALTIME.
-
Si cette option n'est pas positionnée, le noyau mesure le timeout par
rapport à l'horloge CLOCK_MONOTONIC.
L'opération indiquée dans futex_op prend une de ces valeurs :
- FUTEX_WAIT (depuis Linux 2.6.0)
-
Cette option teste que la valeur du mot futex vers laquelle pointe l'adresse
uaddr contient toujours la valeur val attendue, et si tel est le cas,
elle s'endort jusqu'à une opération FUTEX_WAKE sur le mot futex. Le
chargement de la valeur du mot futex est un accès en mémoire atomique
(c'est-à-dire qu'il utilise des instructions machine atomiques de
l'architecture concernée). Ce chargement, la comparaison avec la valeur
attendue et la mise en sommeil s'effectuent de manière atomique et sont
totalement organisés selon les autres opérations futex sur le même mot
futex. Si le thread commence à dormir, il est considéré comme en attente de
ce mot futex. Si la valeur futex ne correspond pas à val, l'appel échoue
immédiatement avec l'erreur EAGAIN.
-
Le but de la comparaison avec la valeur attendue est d'empêcher des réveils
perdus. Si un autre thread a changé la valeur du mot futex après que le
thread a décidé de se bloquer en se fondant sur la valeur d'avant, et si
l'autre thread a effectué une opération FUTEX_WAKE (ou un réveil
équivalent) après le changement de cette valeur et avant cette opération
FUTEX_WAIT, le thread appelant observera cette valeur et ne commencera
pas à dormir.
-
Si le timeout n'est pas NULL, la structure vers laquelle il pointe
indique un délai d'attente (cet intervalle sera arrondi à la valeur
supérieure à partir de la granularité de l'horloge système et il est garanti
de ne pas expirer en avance). Le délai est mesuré par défaut par rapport à
l'horloge CLOCK_MONOTONIC mais depuis Linux 4.5, l'horloge
CLOCK_REALTIME peut être choisie en indiquant FUTEX_CLOCK_REALTIME
dans futex_op. Si le timeout est NULL, l'appel se bloque indéfiniment.
-
Remarque : pour FUTEX_WAIT, le timeout est interprété comme une
valeur relative. Cela diffère des autres opérations futex où le
timeout est interprété comme une valeur absolue. Pour obtenir
l'équivalent de FUTEX_WAIT, avec un délai absolu, utilisez
FUTEX_WAIT_BITSET en indiquant val3 comme FUTEX_BITSET_MATCH_ANY.
-
Les paramètres uaddr2 et val3 sont ignorés.
- FUTEX_WAKE (depuis Linux 2.6.0)
-
Cette opération réveille jusqu'à val éléments en attente (comme dans
FUTEX_WAIT) sur le mot futex à l'adresse uaddr. Généralement, val
est indiqué soit sous la forme de 1 (réveil d'un seul élément en attente)
soit avec INT_MAX (réveil de tous les éléments en attente). Vous n'avez
aucune garantie quant aux éléments qui sont réveillés (par exemple un
élément en attente dont la priorité d'ordonnancement élevée n'est pas
garanti de se réveiller avant un autre d'une priorité plus basse).
-
Les paramètres timeout, uaddr2 et val3 sont ignorés.
- FUTEX_FD (de Linux 2.6.0 jusqu'à Linux 2.6.25 inclus)
-
Cette opération crée un descripteur de fichier associé au futex sur
uaddr. L'appelant doit fermer le descripteur de fichier renvoyé après
l'avoir utilisé. Quand un autre processus ou un autre thread effectue un
FUTEX_WAKE sur le mot futex, le descripteur de fichier indique qu'il est
accessible en lecture avec select(2), poll(2), et epoll(7)
-
Le descripteur de fichier peut être utilisé pour avoir des notifications
asynchrones, si val n'est pas nul, puis, quand un autre processus ou un
autre thread exécute FUTEX_WAKE, l'appelant recevra le numéro du signal
passé à val.
-
Les paramètres timeout, uaddr2 et val3 sont ignorés.
-
Parce qu'il était de façon inhérente sujet à des situations de concurrence,
FUTEX_FD a été supprimé de Linux 2.6.26 et les suivants.
- FUTEX_REQUEUE (depuis Linux 2.6.0)
-
Cette opération effectue la même chose que FUTEX_CMP_REQUEUE (voir
ci-dessous), sauf qu'elle ne vérifie rien en utilisant la valeur dans
val3 (le paramètre val3 est ignoré).
- FUTEX_CMP_REQUEUE (depuis Linux 2.6.7)
-
Cette opération vérifie d'abord si l'emplacement uaddr contient toujours
la valeur val3. Si tel n'est pas le cas, l'opération échoue avec l'erreur
EAGAIN. Si tel est le cas, l'opération réveille un maximum de val
éléments en attente du futex sur uaddr. S'il y a plus de val éléments
en attente, les autres sont supprimés de la file d'attente du futex source
sur uaddr et ajoutés à la file d'attente du futex cible sur uaddr2. Le
paramètre val2 indique une limite supérieure du nombre d'éléments remis
en attente dans le futex sur uaddr2.
-
Le chargement à partir de uaddr est un accès atomique en mémoire
(c'est-à-dire qu'il utilise les instructions machine atomiques de
l'architecture concernée). Ce chargement, la comparaison avec val3 et la
remise en attente d'éléments s'effectuent de manière atomique et sont
totalement organisées par rapport aux autres opérations sur le même mot
futex.
-
Les valeurs classiques qu'on indique à val sont 0 ou 1 (indiquer
INT_MAX n'est pas utile car cela rendrait l'opération
FUTEX_CMP_REQUEUE équivalente à FUTEX_WAKE). La valeur limite indiquée
avec val2 est généralement 1 ou INT_MAX (indiquer 0 en paramètre
n'est pas utile car cela rendrait l'opération FUTEX_CMP_REQUEUE
équivalente à FUTEX_WAIT).
-
L'opération FUTEX_CMP_REQUEUE a été ajoutée pour remplacer l'ancienne
FUTEX_REQUEUE. La différence est que la vérification de la valeur sur
uaddr peut être utilisée pour s'assurer que la remise en attente ne se
produit que sous certaines conditions, ce qui évite les conflits de mémoire
(race conditions) dans certains cas d'utilisation.
-
FUTEX_REQUEUE et FUTEX_CMP_REQUEUE peuvent être utilisées pour éviter
des réveils en troupeau (thundering herd) qui peuvent survenir quand on
utilise FUTEX_WAKE dans des cas où tous les éléments en attente qu'on
réveille doivent acquérir un autre futex. Imaginons le scénario suivant où
plusieurs threads attendent en B, une file d'attente implémentée en
utilisant un futex :
-
lock(A)
while (!check_value(V)) {
unlock(A);
block_on(B);
lock(A);
};
unlock(A);
-
Si un thread qui se réveille utilisait FUTEX_WAKE, tous les éléments
attendant en B se réveilleraient et essaieraient d'acquérir le verrou
A. Cependant, réveiller tous ces threads de cette manière serait vain car
tous les threads, sauf un, se bloqueraient immédiatement à nouveau via le
verrou A. Au contraire, une remise dans la file d'attente ne réveille qu'un
élément et déplace les autres sur le verrou A et quand celui réveillé
déverrouille A, le suivant peut continuer.
- FUTEX_WAKE_OP (depuis Linux 2.6.14)
-
Cette opération a été ajoutée pour prendre en charge certains cas
d'utilisation de l'espace utilisateur où plus d'un futex à la fois doit être
géré. L'exemple le plus frappant est l'implémentation de
pthread_cond_signal(3), qui nécessite des opérations sur deux futex, une
pour implémenter le mutex, l'autre pour utiliser dans l'implémentation de la
file d'attente associée à la variable conditionnelle. FUTEX_WAKE_OP
permet d'implémenter de tels cas sans augmenter le nombre de conflits et de
changement de contexte.
-
L'opération FUTEX_ WAKE_OP revient à exécuter le code suivant de manière
atomique et complètement organisé en fonction des opérations futex sur un
des deux mots futex fournis :
-
uint32_t oldval = *(uint32_t *) uaddr2;
*(uint32_t *) uaddr2 = oldval op oparg;
futex(uaddr, FUTEX_WAKE, val, 0, 0, 0);
if (oldval cmp cmparg)
futex(uaddr2, FUTEX_WAKE, val2, 0, 0, 0);
-
En d'autres termes, FUTEX_WAKE_OP fait ce qui suit :
-
- •
-
sauvegarde la valeur d'origine du mot futex sur uaddr2 et effectue une
opération pour modifier la valeur du futex sur uaddr2 ; il s'agit d'un
accès en mémoire read-modify-write atomique (c'est-à-dire d'une utilisation
des instructions machine atomiques liées à l'architecture concernée)
- •
-
réveille un maximum de val éléments en attente sur le futex pour le mot
futex sur uaddr ;
- •
-
et selon les résultats d'un test de la valeur d'origine du mot futex sur
uaddr2, réveille un maximum de val2 éléments en attente du mot futex
sur le futex sur uaddr2.
-
L'opération et la comparaison qui doivent être effectuées sont encodées dans
les bits du paramètre val3. Visuellement, l'encodage est :
-
+---+---+-----------+-----------+
|op |cmp| oparg | cmparg |
+---+---+-----------+-----------+
4 4 12 12 <== # of bits
-
Exprimé en code, l'encodage est :
-
#define FUTEX_OP(op, oparg, cmp, cmparg) \
(((op & 0xf) << 28) | \
((cmp & 0xf) << 24) | \
((oparg & 0xfff) << 12) | \
(cmparg & 0xfff))
-
Dans ce qui précède, op et cmp sont chacun des codes listés
ci-dessous. Les composants oparg et cmparg sont des valeurs numériques
littérales, sauf les remarques ci-dessous.
-
Le composant op prend une de ces valeurs :
-
FUTEX_OP_SET 0 /* uaddr2 = oparg; */
FUTEX_OP_ADD 1 /* uaddr2 += oparg; */
FUTEX_OP_OR 2 /* uaddr2 |= oparg; */
FUTEX_OP_ANDN 3 /* uaddr2 &= ~oparg; */
FUTEX_OP_XOR 4 /* uaddr2 ha= oparg; */
-
En outre, comparer bit à bit (ORing) la valeur suivante dans op a pour
conséquence que (1~<<~oparg) sera utilisé en tant qu'opérande :
-
FUTEX_OP_ARG_SHIFT 8 /* Utiliser (1 << oparg) comme opérande */
-
Le champ cmp prend une de ces valeurs :
-
FUTEX_OP_CMP_EQ 0 /* si (oldval == cmparg) réveiller */
FUTEX_OP_CMP_NE 1 /* si (oldval != cmparg) réveiller */
FUTEX_OP_CMP_LT 2 /* si (oldval < cmparg) réveiller */
FUTEX_OP_CMP_LE 3 /* si (oldval <= cmparg) réveiller */
FUTEX_OP_CMP_GT 4 /* si (oldval > cmparg) réveiller */
FUTEX_OP_CMP_GE 5 /* si (oldval >= cmparg) réveiller */
-
Le code de retour de FUTEX_WAKE_OP est la somme du nombre d'éléments en
attente réveillés par le futex uaddr et du nombre d'éléments en attente
réveillés sur le futex uaddr2.
- FUTEX_WAIT_BITSET (depuis Linux 2.6.25)
-
Cette opération est équivalente à FUTEX_WAIT, sauf que val3 est
utilisé pour fournir un masque de bit de 32 bits au noyau. Ce masque, où au
moins un bit doit être positionné, est stocké dans la partie interne du
noyau de l'élément en attente. Voir la description de FUTEX_WAKE_BITSET
pour plus de détails.
-
Si timeout n'est pas NULL, la structure vers laquelle il pointe indique
un délai absolu de l'opération d'attente. Si timeout est NULL,
l'opération peut se bloquer indéfiniment.
-
L'argument uaddr2 est ignoré.
- FUTEX_WAKE_BITSET (depuis Linux 2.6.25)
-
Cette opération est identique à FUTEX_WAKE, sauf que le paramètre val3
est utilisé pour fournir un masque de bit de 32 bits au noyau. Ce masque, où
au moins un bit doit être positionné, est utilisé pour choisir les éléments
en attente qui doivent être réveillés. Le choix se fait par une comparaison
bit à bit AND du masque de bit « wait » (à savoir la valeur de val3) et
par un masque de bit stocké dans la partie interne de l'élément en attente
(le masque de bit « wait » positionné en utilisant
FUTEX_WAIT_BITSET). Tous les éléments en attente pour lesquels le AND est
positif sont réveillés ; les autres restent endormis.
-
L'effet de FUTEX_WAIT_BITSET et de FUTEX_WAKE_BITSET est de permettre
un réveil sélectif parmi les éléments en attente bloqués sur le même
futex. Cependant, remarquez que selon le cas, l'utilisation de cette
fonction de mélange de masques de bit sur un futex peut être moins efficace
que le fait d'avoir plusieurs futex, car elle a besoin que le noyau vérifie
tous les éléments en attente sur un futex, y compris ceux non concernés par
le réveil (à savoir qu'ils n'ont pas de bit pertinent positionné dans leur
masque de bit « wait »).
-
La constante FUTEX_BITSET_MATCH_ANY, qui correspond à tous les
positionnements 32 bits du masque, peut être utilisé en tant que val3 de
FUTEX_WAIT_BITSET et de FUTEX_WAKE_BITSET. En dehors des différences
dans la gestion du paramètre timeout, l'opération FUTEX_WAIT est
équivalente à FUTEX_WAIT_BITSET où val3 est indiqué en tant que
FUTEX_BITSET_MATCH_ANY ; c'est-à-dire permettre le réveil par n'importe
quel élément en attente). L’opération FUTEX_WAKE est équivalente à
FUTEX_WAKE_BITSET où val3 est indiqué en tant que
FUTEX_BITSET_MATCH_ANY ; c'est-à-dire, réveiller n’importe quel élément
en attente.
-
Les arguments uaddr2 et timeout sont ignorés.
Futex et héritage de priorité
Linux prend en charge l'héritage de priorité (priority inheritance, PI) des
futex, afin de gérer des problèmes d'inversion des priorités qu'on peut
rencontrer avec des verrous futex normaux. L'inversion des priorités est un
problème qui survient quand une tâche de haute priorité est bloquée en
attente d'acquérir un verrou que possède une tâche de basse priorité issue
du processeur. Du coup, la tâche de priorité basse ne va pas relâcher le
verrou et celle de haute priorité reste bloquée.
L'héritage de priorité est un mécanisme pour gérer le problème d'inversion
des priorités. Avec ce mécanisme, quand une tâche à haute priorité est
bloquée par un verrou possédé par une tâche à basse priorité, la priorité de
la seconde est temporairement amenée au même niveau que celle à haute
priorité, de sorte qu'elle ne soit pas doublée par une tâche de niveau
intermédiaire et qu'elle puisse ainsi avancer pour relâcher le verrou. Pour
fonctionner, l'héritage de priorité doit être transitif, ce qui signifie que
si une tâche à haute priorité bloque sur le verrou d'une tâche à priorité
intermédiaire (et ainsi de suite sur des chaînes de la taille de votre
choix), les deux tâches (ou plus généralement toutes les tâches de la chaîne
de verrous) voient leur niveau de priorité amené à celui de la tâche à haute
priorité.
Du point de vue de l'espace utilisateur, le futex a conscience d'un PI en
acceptant une réglementation (décrite ci-dessous) entre l'espace utilisateur
et le noyau sur la valeur du mot futex, couplé à l'utilisation d'opérations
futex PI décrites ci-dessous (contrairement aux autres opérations futex
décrites ci-dessus, celles PI-futex sont conçues pour l'implémentation de
mécanismes IPC très spécifiques).
Les opérations PI-futex décrites ci-dessous diffèrent des autres opérations
dans le sens où elles imposent des règles dans l'utilisation de la valeur du
mot futex :
- •
-
Si le verrou n'est pas acquis, la valeur du mot futex doit être 0.
- •
-
Si le verrou est acquis, la valeur du mot futex doit être l'ID du thread
(TID ; voir gettid(2)) du thread propriétaire.
- •
-
Si le verrou a un propriétaire et s'il y a des threads en concurrence pour
le verrou, le bit FUTEX_WAITERS doit être positionné dans la valeur du
mot futex ; autrement dit, cette valeur est :
-
FUTEX_WAITERS | TID
-
(Remarquez que cela n'est pas possible pour un mot futex PI d'être sans
propriétaire ni FUTEX_WAITERS défini).
Avec cette règle, une application de l'espace utilisateur peut acquérir un
verrou non acquis ou en relâcher un en utilisant des instructions atomiques
dans l'espace utilisateur (comme une opération compare-and-swap telle que
cmpxchg sur l'architecture x86). L'acquisition d'un verrou consiste
simplement dans l'utilisation de compare-and-swap pour positionner la valeur
du mot futex de manière atomique sur le TID de l'appelant si sa valeur
précédente était 0. Relâcher un verrou exige d'utiliser compare-and-swap
pour positionner la valeur du mot futex sur 0 si la valeur précédente
était le TID prévu.
Si un futex est déjà acquis (c'est-à-dire qu'il a une valeur positive), les
éléments en attente doivent utiliser l'opération FUTEX_LOCK_PI pour
acquérir le verrou. Si d'autres threads attendent le verrou, le bit
FUTEX_WAITERS est défini dans la valeur du futex ; dans ce cas le
détenteur du verrou doit utiliser l'opération FUTEX_UNLOCK_PI pour
relâcher le verrou.
Dans le cas où les appelants sont bloqués dans le noyau (c'est-à-dire qu'ils
doivent effectuer un appel futex()), ils traitent directement avec ce
qu'on appelle un RT-mutex, un mécanisme de verrouillage du noyau qui
implémente la sémantique de l'héritage de priorité requis. Après que le
RT-mutex est acquis, la valeur futex est mise à jour en fonction, avant que
le thread appelant ne renvoie vers l'espace utilisateur.
Il est important de remarquer que le noyau mettra à jour la valeur du mot
futex avant de renvoyer vers l'espace utilisateur (cela enlève la
possibilité pour la valeur d'un mot futex de se terminer dans un état non
valable, par exemple en ayant un propriétaire mais en ayant la valeur 0,
ou en ayant des éléments en attente mais aucun bit FUTEX_WAITERS
positionné).
Si un futex a un RT-mutex associé dans le noyau (c'est-à-dire qu'il y a des
éléments en attente bloqués) et si le propriétaire du futex/RT-mutex meurt
de manière inattendue, le noyau nettoie le RT-mutex et passe la main au
prochain élément en attente. Cela implique, en retour, que la valeur dans
l'espace utilisateur soit mise à jour en fonction. Pour dire que c'est
nécessaire, le noyau positionne le bit FUTEX_OWNER_DIED dans le mot futex
ainsi que dans l'ID du thread du nouveau propriétaire. L'espace utilisateur
peut détecter cette situation par la présence du bit FUTEX_OWNER_DIED et
il est alors responsable pour nettoyer l'espace laissé par le propriétaire
mort.
Les PI futex sont utilisés en indiquant une des valeurs listées ci-dessous
dans futex_op. Remarquez que les opérations de PI futex doivent être
utilisées par paires et sont soumises à des exigences supplémentaires :
- •
-
FUTEX_LOCK_PI, FUTEX_LOCK_PI2 et FUTEX_TRYLOCK_PI vont de pair avec
FUTEX_UNLOCK_PI. FUTEX_UNLOCK_PI ne doit être appelé que sur un futex
appartenant au thread appelant, tel que défini par les règles de la valeur,
sans quoi on obtient l'erreur EPERM.
- •
-
FUTEX_WAIT_REQUEUE_PI va de pair avec FUTEX_CMP_REQUEUE_PI. Elles
doivent s'effectuer depuis un futex non-PI vers un PI futex distinct (sans
quoi on obtient l'erreur EINVAL). De plus, val (le nombre d'éléments
en attente à réveiller) doit être de 1 (sans quoi on obtient l'erreur
EINVAL).
Les opérations PI futex sont comme suit :
- FUTEX_LOCK_PI (depuis Linux 2.6.18)
-
Cette opération est utilisée après avoir essayé sans succès d'acquérir un
verrou en utilisant une instruction atomique en mode utilisateur, car le mot
futex a une valeur positive – en particulier parce qu'il contenait le TID
(spécifique à l’espace de noms PID) du verrou propriétaire.
-
L'opération vérifie la valeur du mot futex sur l'adresse uaddr. Si la
valeur est de 0, le noyau essaie de positionner de manière atomique la
valeur du futex sur le TID de l'appelant. Si la valeur du mot futex est
positive, le noyau positionne de manière atomique le bit FUTEX_WAITERS,
qui signale au propriétaire du futex qu'il ne peut pas déverrouiller le
futex dans l'espace utilisateur de manière atomique, en positionnant la
valeur du futex à 0. Après cela, le noyau :
-
- (1)
-
Essaie de trouver le thread associé au TID du propriétaire.
- (2)
-
Crée ou réutilise l'état du noyau sur la base du propriétaire (s'il s'agit
du premier élément en attente, il n'existe pas d'état du noyau pour ce
futex, donc il est créé en verrouillant le RT-mutex et le propriétaire du
futex devient propriétaire du RT-mutex). Si des éléments en attente
existent, l'état existant est réutilisé.
- (3)
-
Rattache l'élément en attente au futex (c'est-à-dire que l'élément est mis
dans la file d'attente du RT-futex).
-
S'il existe plus d'un élément en attente, la mise dans la file d'un élément
se fait par ordre de priorité descendant (pour des informations sur l'ordre
des priorités, voir les points sur l'ordonnancement SCHED_DEADLINE,
SCHED_FIFO et SCHED_RR dans sched(7)). Le propriétaire hérite soit
de la bande passante de processeur de l'élément en attente (si l'élément est
programmé sous la règle SCHED_DEADLINE ou SCHED_FIFO), soit de la
priorité de l'élément en attente (s'il est programmé sous la règle
SCHED_RR ou SCHED_FIFO). Cet héritage suit la chaîne de verrous dans
les cas de verrous imbriqués et il effectue la détection des verrous morts
(deadlocks).
-
Le paramètre timeout fournit un délai de tentative de verrouillage. Si
timeout est positif, la structure vers laquelle il pointe indique un
délai absolu mesuré en fonction de l'horloge CLOCK_REALTIME. Si
timeout est NULL, l'opération se bloquera indéfiniment.
-
Les paramètres uaddr2, val et val3 sont ignorés.
- FUTEX_LOCK_PI2 (depuis Linux 5.14)
-
Cette opération est la même que FUTEX_LOCK_PI, sauf que l'horloge par
rapport à laquelle timeout est mesuré peut être sélectionnée. Par défaut,
le délai (absolu) indiqué dans timeout est mesuré par rapport à l'horloge
CLOCK_MONOTONIC mais si l'attribut FUTEX_CLOCK_REALTIME est indiqué
dans futex_op, le délai est mesuré par rapport à l'horloge
CLOCK_REALTIME.
- FUTEX_TRYLOCK_PI (depuis Linux 2.6.18)
-
L'opération essaie d'acquérir le verrou sur uaddr. Elle est appelée quand
l'acquisition atomique dans l'espace utilisateur n'a pas réussi parce que le
mot futex ne valait pas 0.
-
Du fait que le noyau accède à plus d'informations d'état que l'espace
utilisateur, l'acquisition du verrou pourrait réussir si elle est effectuée
par le noyau dans les cas où le mot futex (c'est-à-dire les informations
d'état accessibles dans l'espace utilisateur) contient un état stable
(FUTEX_WAITERS et/ou FUTEX_OWNER_DIED). Cela peut arriver quand le
propriétaire du futex est mort. L'espace utilisateur ne peut pas gérer cette
condition de manière "race-free", mais le noyau peut corriger cela et
acquérir le futex.
-
Les paramètres uaddr2, val, timeout et val3 sont ignorés.
- FUTEX_UNLOCK_PI (depuis Linux 2.6.18)
-
Cette opération réveille l'élément ayant la plus haute priorité et attendant
un FUTEX_LOCK_PI ou un FUTEX_LOCK_PI2 à l'adresse indiquée par le
paramètre uaddr.
-
Cela est appelé quand la valeur dans l'espace utilisateur sur uaddr ne
peut pas être passée à 0 de manière atomique depuis un TID (du
propriétaire).
-
Les paramètres uaddr2, val, timeout et val3 sont ignorés.
- FUTEX_CMP_REQUEUE_PI (depuis Linux 2.6.31)
-
Cette opération est une variante PI-aware de FUTEX_CMP_REQUEUE. Elle
remet en attente des éléments bloqués avec FUTEX_WAIT_REQUEUE_PI sur
uaddr à partir d'un futex source non-PI (uaddr) vers un futex cible PI
(uaddr2).
-
Comme avec FUTEX_CMP_REQUEUE, cette opération réveille un maximum de
val éléments qui attendent le futex sur uaddr. Toutefois, pour
FUTEX_CMP_REQUEUE_PI, val doit valoir 1 (puisque son but principal
est d'éviter l’effet de troupeau (thundering herd). Les autres éléments sont
supprimés de la file d'attente du futex source sur uaddr et ajoutés sur
celle du futex cible sur uaddr2.
-
Les paramètres val2 et val3 ont le même objectif qu'avec
FUTEX_CMP_REQUEUE.
- FUTEX_WAIT_REQUEUE_PI (depuis Linux 2.6.31)
-
Attendre un futex non-PI sur uaddr et se mettre potentiellement en
attente (avec une opération FUTEX_CMP_REQUEUE_PI dans une autre tâche),
d'un futex PI sur uaddr2. L'opération d'attente sur uaddr est la même
que pour FUTEX_WAIT.
-
L'élément peut être retiré de la file d'attente sur uaddr sans être
transféré sur uaddr2 à l’aide d’une opération FUTEX_WAKE dans une
autre tâche. Dans ce cas, l'opération FUTEX_WAIT_REQUEUE_PI échoue avec
l'erreur EAGAIN.
-
Si timeout n'est pas NULL, la structure vers laquelle il pointe indique
un délai absolu de l'opération d'attente. Si timeout est NULL,
l'opération peut se bloquer indéfiniment.
-
L'argument val3 est ignoré.
-
FUTEX_WAIT_REQUEUE_PI et FUTEX_CMP_REQUEUE_PI ont été ajoutés pour
gérer un cas d'utilisation bien particulier : la prise en charge des
variables conditionnelles de threads POSIX ayant connaissance de l'héritage
de priorité. L'idée est que ces opérations devraient toujours aller par
paires, afin de garantir que l'espace utilisateur et le noyau restent
toujours synchronisés. Ainsi, dans l'opération FUTEX_WAIT_REQUEUE_PI,
l'application dans l'espace utilisateur pré-indique la cible de la remise en
attente qui va se faire dans l'opération FUTEX_CMP_REQUEUE_PI.
VALEUR RENVOYÉE
En cas d'erreur (en supposant que futex() a été appelé à l’aide de
syscall(2)), toutes les opérations renvoient -1 et positionnent
errno pour indiquer l'erreur.
En cas de succès, le code de retour dépend de l'opération, comme décrit dans
la liste suivante :
- FUTEX_WAIT
-
Renvoie 0 si l'appelant a été réveillé. Remarquez qu'un réveil peut
également résulter de l'utilisation de motifs d'utilisation classiques de
futex dans du code non lié qui a pu utiliser l'emplacement mémoire du mot
futex (par exemple des implémentations classiques basées sur futex de mutex
Pthreads peuvent provoquer cela dans certaines conditions). Donc, les
appelants devraient toujours, à titre conservatoire, supposer qu'un code de
retour 0 peut signifier un faux réveil, et donc utiliser la valeur du mot
futex (à savoir le schéma de synchronisation de l'espace utilisateur) pour
décider de rester bloqués ou pas.
- FUTEX_WAKE
-
Renvoie le nombre de processus en attente qui ont été réveillés.
- FUTEX_FD
-
Renvoie le nouveau descripteur de fichier associé au futex.
- FUTEX_REQUEUE
-
Renvoie le nombre de processus en attente qui ont été réveillés.
- FUTEX_CMP_REQUEUE
-
Renvoie le nombre total d'éléments en attente réveillés ou remis dans la
file du futex pour le mot futex sur uaddr2. Si cette valeur est
supérieure à val, la différence devient le nombre d'éléments en attente
remis dans la file du futex pour le mot futex sur uaddr2.
- FUTEX_WAKE_OP
-
Renvoie le nombre total d'éléments en attente réveillés. Il s'agit de la
somme des éléments réveillés sur les deux futex pour les mots futex sur
uaddr et uaddr2.
- FUTEX_WAIT_BITSET
-
Renvoie 0 si l'appelant a été réveillé. Voir FUTEX_WAIT sur la manière
d'interpréter cela correctement en pratique.
- FUTEX_WAKE_BITSET
-
Renvoie le nombre de processus en attente qui ont été réveillés.
- FUTEX_LOCK_PI
-
Renvoie 0 si le futex a appliqué le verrou avec succès.
- FUTEX_LOCK_PI2
-
Renvoie 0 si le futex a appliqué le verrou avec succès.
- FUTEX_TRYLOCK_PI
-
Renvoie 0 si le futex a appliqué le verrou avec succès.
- FUTEX_UNLOCK_PI
-
Renvoie 0 si le futex a correctement enlevé le verrou.
- FUTEX_CMP_REQUEUE_PI
-
Renvoie le nombre total d'éléments en attente réveillés ou remis dans la
file du futex pour le mot futex sur uaddr2. Si cette valeur est
supérieure à val, la différence devient le nombre d'éléments en attente
remis dans la file du futex pour le mot futex sur uaddr2.
- FUTEX_WAIT_REQUEUE_PI
-
Renvoie 0 si l'appelant a été mis dans la file d'attente avec succès au
futex pour le mot futex sur uaddr2.
ERREURS
- EACCES
-
Pas d'accès en lecture à la mémoire d'un mot futex.
- EAGAIN
-
(FUTEX_WAIT, FUTEX_WAIT_BITSET, FUTEX_WAIT_REQUEUE_PI) La valeur
vers laquelle pointait uaddr n'était pas égale à la valeur val
attendue au moment de l'appel.
-
Remarque : sur Linux, les noms symboliques EAGAIN et EWOULDBLOCK
(les deux apparaissent dans différents endroits du code futex du noyau) ont
la même valeur.
- EAGAIN
-
(FUTEX_CMP_REQUEUE, FUTEX_CMP_REQUEUE_PI) La valeur vers laquelle
pointait uaddr n'était pas égale à la valeur val3 attendue.
- EAGAIN
-
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI,
FUTEX_CMP_REQUEUE_PI) L'ID du thread propriétaire du futex sur uaddr
(pour FUTEX_CMP_REQUEUE_PI : uaddr2) est sur le point de se terminer,
mais il n'a pas encore géré le nettoyage de l'état interne. Réessayez.
- EDEADLK
-
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI,
FUTEX_CMP_REQUEUE_PI) Le mot futex sur uaddr est déjà verrouillé par
l'appelant.
- EDEADLK
-
(FUTEX_CMP_REQUEUE_PI) Pendant qu'il remettait en attente un élément du
PI futex pour le mot futex sur uaddr2, le noyau a détecté un verrou mort
(deadlock).
- EFAULT
-
Le paramètre d'un pointeur nécessaire (c'est-à-dire uaddr, uaddr2 ou
timeout) ne pointait pas vers une adresse valable de l'espace
utilisateur.
- EINTR
-
Une opération FUTEX_WAIT ou FUTEX_WAIT_BITSET a été interrompue par un
signal (voir signal(7)). Dans Linux 2.6.22, cette erreur pouvait aussi
être renvoyée pour un faux réveil ; depuis Linux 2.6.22, cela n'arrive plus.
- EINVAL
-
L'opération dans futex_op fait partie de celles qui utilisent un délai,
mais le paramètre timeout fourni n'était pas valable (tv_sec valait
moins de 0 ou tv_nsec ne valait pas moins de 1 000 000 000).
- EINVAL
-
L'opération indiquée dans futex_op utilise uaddr et/ou uaddr2 mais
l'un d'eux ne pointe pas vers un objet valable — c'est-à-dire, l'adresse
n'est pas alignée sur quatre octets.
- EINVAL
-
(FUTEX_WAIT_BITSET, FUTEX_WAKE_BITSET) Le masque de bit fourni dans
val3 vaut zéro.
- EINVAL
-
(FUTEX_CMP_REQUEUE_PI) uaddr est égal à uaddr2 (c'est-à-dire
qu'une remise en attente a été tentée sur le même futex).
- EINVAL
-
(FUTEX_FD) Le numéro du signal fourni dans val n'est pas valable.
- EINVAL
-
(FUTEX_WAKE, FUTEX_WAKE_OP, FUTEX_WAKE_BITSET, FUTEX_REQUEUE,
FUTEX_CMP_REQUEUE) Le noyau a détecté une incohérence entre l'état de
l'espace utilisateur sur uaddr et l'état du noyau — c'est-à-dire qu'il a
détecté un élément qui attend dans FUTEX_LOCK_PI ou FUTEX_LOCK_PI2 sur
uaddr.
- EINVAL
-
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI,
FUTEX_UNLOCK_PI) Le noyau a détecté une incohérence entre l'état de
l'espace utilisateur sur uaddr et l'état du noyau. Cela indique soit une
corruption d'état, soit que le noyau a trouvé un élément en attente sur
uaddr qui attend aussi à l'aide de FUTEX_WAIT ou de
FUTEX_WAIT_BITSET.
- EINVAL
-
(FUTEX_CMP_REQUEUE_PI) Le noyau a détecté une incohérence entre l'état de
l'espace utilisateur sur uaddr et l'état du noyau ; c'est-à-dire qu'il a
détecté un élément qui attend via FUTEX_WAIT ou FUTEX_WAIT_BITSET sur
uaddr2.
- EINVAL
-
(FUTEX_CMP_REQUEUE_PI) Le noyau a détecté une incohérence entre l'état de
l'espace utilisateur sur uaddr et l'état du noyau ; c'est-à-dire qu'il a
détecté un élément qui attend à l'aide de FUTEX_WAIT ou de
FUTEX_WAIT_BITESET sur uaddr.
- EINVAL
-
(FUTEX_CMP_REQUEUE_PI) Le noyau a détecté une incohérence entre l'état de
l'espace utilisateur sur uaddr et l'état du noyau ; c'est-à-dire qu'il a
détecté un élément qui attend à l'aide de FUTEX_LOCK_PI ou de
FUTEX_LOCK_PI2 (au lieu de FUTEX_WAIT_REQUEUE_PI).
- EINVAL
-
(FUTEX_CMP_REQUEUE_PI) Tentative de remise dans la file d'un élément en
attente vers un futex différent de celui indiqué avec l'appel
FUTEX_WAIT_REQUEUE_PI correspondant pour cet élément.
- EINVAL
-
(FUTEX_CMP_REQUEUE_PI) Le paramètre val ne vaut pas 1.
- EINVAL
-
Argument incorrect.
- ENFILE
-
(FUTEX_FD) La limite du nombre total de fichiers ouverts sur le système a
été atteinte.
- ENOMEM
-
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI,
FUTEX_CMP_REQUEUE_PI) Le noyau n'a pas pu allouer de la mémoire pour
conserver les informations d'état.
- ENOSYS
-
Opération non valable indiquée dans futex_op.
- ENOSYS
-
L'option FUTEX_CLOCK_REALTIME était indiquée dans futex_op, mais
l'opération qui l'accompagne n'est ni FUTEX_WAIT, ni
FUTEX_WAIT_BITSET, ni FUTEX_WAIT_REQUEUE_PI, ni FUTEX_LOCK_PI2.
- ENOSYS
-
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI,
FUTEX_UNLOCK_PI, FUTEX_CMP_REQUEUE_PI, FUTEX_WAIT_REQUEUE_PI) Une
vérification pendant l'exécution a déterminé que l'opération n'est pas
disponible. Les opérations PI-futex ne sont pas implémentées sur toutes les
architectures et ne sont pas prises en charge sur certaines variantes de
processeur.
- EPERM
-
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI,
FUTEX_CMP_REQUEUE_PI) L'appelant n'est pas autorisé à se rattacher au
futex sur uaddr (pour FUTEX_CMP_REQUEUE_PI : le futex sur uaddr2)
(cela peut venir d'une corruption de l'état dans l'espace utilisateur).
- EPERM
-
(FUTEX_UNLOCK_PI) Le verrou représenté par le mot futex n'appartient pas
à l'appelant.
- ESRCH
-
(FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI,
FUTEX_CMP_REQUEUE_PI) L'ID du thread dans le mot futex sur uaddr
n'existe pas.
- ESRCH
-
(FUTEX_CMP_REQUEUE_PI) L'ID du thread dans le mot futex sur uaddr2
n'existe pas.
- ETIMEDOUT
-
L'opération de futex_op a utilisé un délai indiqué dans timeout et le
délai a expiré avant la fin de l'opération.
VERSIONS
Les futex ont d'abord été disponibles dans une version stable du noyau avec
Linux 2.6.0.
La prise en charge initiale des futex a été ajoutée dans Linux 2.5.7 mais
avec une sémantique différente de celle décrite ci-dessus. Un appel
système à 4 paramètres avec la sémantique décrite dans cette page a été
ajouté dans Linux 2.5.40. Dans Linux 2.5.70, un cinquième paramètre a été
ajouté. Un sixième paramètre a été ajouté dans Linux 2.6.7.
STANDARDS
Cet appel système est spécifique à Linux.
NOTES
Plusieurs abstractions programmatiques de haut niveau sont implémentées avec
des futex, notamment les mécanismes POSIX de sémaphore et de synchronisation
de threads (mutex, variables conditionnelles, verrous en lecture/écriture et
barrières).
EXEMPLES
Le programme ci-dessous montre l'utilisation des futex dans un programme où
un processus parent et un processus enfant utilisent une paire de futex
située dans un tableau anonyme partagé pour synchroniser l'accès à une
ressource partagée : le terminal. Les deux processus écrivent chacun un
message nloops (un paramètre en ligne de commande qui vaut 5 par défaut
s'il est absent) sur le terminal et ils utilisent un protocole de
synchronisation pour garantir qu'ils alternent dans l'écriture des
messages. Pendant l'exécution de ce programme, nous voyons un affichage
comme suit :
$ ./futex_demo
Parent (18534) 0
Child (18535) 0
Parent (18534) 1
Child (18535) 1
Parent (18534) 2
Child (18535) 2
Parent (18534) 3
Child (18535) 3
Parent (18534) 4
Child (18535) 4
Source du programme
/* futex_demo.c
Utilisation: futex_demo [nloops]
(Par défaut : 5)
Montrer l'utilisation des futex dans un programme où le parent et
l'enfant utilisent une paire de futex située dans un tableau anonyme
partagé pour synchroniser l'accès à une ressource partagée : le
terminal. Les processus écrivent chacun des messages 'num-loops'
sur le terminal et ils utilisent un protocole de synchronisation qui
garantit qu'ils alternent l'écriture des messages.
*/
#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <linux/futex.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
static uint32_t *futex1, *futex2, *iaddr;
static int
futex(uint32_t *uaddr, int futex_op, uint32_t val,
const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3)
{
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr2, val3);
}
/* Acquérir le futex vers lequel pointe 'futexp' : attendre que sa
valeur passe à 1 puis positionner la valeur sur 0. */
static void
fwait(uint32_t *futexp)
{
long s;
const uint32_t one = 1;
/* atomic_compare_exchange_strong(ptr, oldval, newval)
fait atomiquement comme :
if (*ptr == *oldval)
*ptr = newval;
Il renvoie true si le test a montré true et *ptr a été mis à jour. */
while (1) {
/* Le futex est-il disponible ? */
if (atomic_compare_exchange_strong(futexp, &one, 0))
break; /* Oui */
/* Le futex n'est pas disponible ; attendre */
s = futex(futexp, FUTEX_WAIT, 0, NULL, NULL, 0);
if (s == -1 && errno != EAGAIN)
err(EXIT_FAILURE, "futex-FUTEX_WAIT");
}
}
/* Relâcher le futex vers lequel pointe 'futexp' : si le futex a
actuellement la valeur 0, positionner la valeur à 1 et réveiller tous les
futex en attente pour que si le pair est bloqué dans fwait(), ça puisse
continuer. */
static void
fpost(uint32_t *futexp)
{
long s;
const uint32_t zero = 0;
/* atomic_compare_exchange_strong() a été décrit
dans les commentaires ci-dessus. */
if (atomic_compare_exchange_strong(futexp, &zero, 1)) {
s = futex(futexp, FUTEX_WAKE, 1, NULL, NULL, 0);
if (s == -1)
err(EXIT_FAILURE, "futex-FUTEX_WAKE");
}
}
int
main(int argc, char *argv[])
{
pid_t childPid;
unsigned int nloops;
setbuf(stdout, NULL);
nloops = (argc > 1) ? atoi(argv[1]) : 5;
/* Créer un tableau anonyme partagé qui gardera les futex.
Comme les futex vont être partagés entre les processus, nous
utilisons donc les opérations futex « shared » (donc pas celles
dont le suffixe est "_PRIVATE") */
iaddr = mmap(NULL, sizeof(*iaddr) * 2, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (iaddr == MAP_FAILED)
err(EXIT_FAILURE, "mmap");
futex1 = &iaddr[0];
futex2 = &iaddr[1];
*futex1 = 0; /* État : indisponible */
*futex2 = 1; /* État : disponible */
/* Créer un processus enfant qui hérite du tableau anonyme
partagé. */
childPid = fork();
if (childPid == -1)
err(EXIT_FAILURE, "fork");
if (childPid == 0) { /* Child */
for (unsigned int j = 0; j < nloops; j++) {
fwait(futex1);
printf("Enfant (%jd) %d\n", (intmax_t) getpid(), j);
fpost(futex2);
}
exit(EXIT_SUCCESS);
}
/* Le parent se retrouve ici. */
for (unsigned int j = 0; j < nloops; j++) {
fwait(futex2);
printf("Parent (%jd) %d\n", (intmax_t) getpid(), j);
fpost(futex1);
}
wait(NULL);
exit(EXIT_SUCCESS);
}
VOIR AUSSI
get_robust_list(2), restart_syscall(2),
pthread_mutexattr_getprotocol(3), futex(7), sched(7)
Les fichiers suivants des sources du noyau :
- •
-
Documentation/pi-futex.txt
- •
-
Documentation/futex-requeue-pi.txt
- •
-
Documentation/locking/rt-mutex.txt
- •
-
Documentation/locking/rt-mutex-design.txt
- •
-
Documentation/robust-futex-ABI.txt
Franke, H., Russell, R., and Kirwood, M., 2002. Fuss, Futexes and Furwocks: Fast Userlevel Locking in Linux (à partir des actions d'Ottawa
Linux Symposium 2002),
Hart, D., 2009. A futex overview and update,
Hart, D. et Guniguntala, D., 2009. Requeue-PI: Making Glibc Condvars PI-Aware (à partir des comptes rendus de l'atelier Real-Time Linux 2009),
Drepper, U., 2011. Futexes Are Tricky,
La bibliothèque d'exemples de futex, futex-*.tar.bz2 à
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
-
- Arguments
-
- Opérations futex
-
- Futex et héritage de priorité
-
- VALEUR RENVOYÉE
-
- ERREURS
-
- VERSIONS
-
- STANDARDS
-
- NOTES
-
- EXEMPLES
-
- Source du programme
-
- VOIR AUSSI
-
- TRADUCTION
-
This document was created by
man2html,
using the manual pages.
Time: 19:43:01 GMT, May 22, 2024