futex – Verrouillage rapide en mode utilisateur
Bibliothèque C standard (
libc,
-lc)
#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).
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.
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.
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 ^= 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.
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.
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.
- 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.
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.
Cet appel système est spécifique à Linux.
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).
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
/* 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);
}
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),
http://kernel.org/doc/ols/2002/ols2002-pages-479-495.pdf
Hart, D., 2009.
A futex overview and update,
http://lwn.net/Articles/360699/
Hart, D. et Guniguntala, D., 2009.
Requeue-PI: Making Glibc Condvars
PI-Aware (à partir des comptes rendus de l'atelier Real-Time Linux
2009),
http://lwn.net/images/conf/rtlws11/papers/proc/p10.pdf
Drepper, U., 2011.
Futexes Are Tricky,
http://www.akkadia.org/drepper/futex.pdf
La bibliothèque d'exemples de futex, futex-*.tar.bz2 à
https://mirrors.kernel.org/pub/linux/kernel/people/rusty/
La traduction française de cette page de manuel a été
créée par Christophe Blaess
<
https://www.blaess.fr/christophe/>, Stéphan Rafin
<
[email protected]>, Thierry Vignaud
<
[email protected]>, François Micaux, Alain Portal
<
[email protected]>, Jean-Philippe Guérard
<
[email protected]>, Jean-Luc Coulon (f5ibh)
<
[email protected]>, Julien Cristau
<
[email protected]>, Thomas Huriaux <
[email protected]>,
Nicolas François <
[email protected]>, Florentin
Duneau <
[email protected]>, Simon Paillard
<
[email protected]>, Denis Barbier
<
[email protected]>, David Prévot <
[email protected]> et
Jean-Philippe MENGUAL <
[email protected]>
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 à
[email protected]