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
<stephan.rafin@laposte.net>, Thierry Vignaud
<tvignaud@mandriva.com>, François Micaux, Alain Portal
<aportal@univ-montp2.fr>, Jean-Philippe Guérard
<fevrier@tigreraye.org>, Jean-Luc Coulon (f5ibh)
<jean-luc.coulon@wanadoo.fr>, Julien Cristau
<jcristau@debian.org>, Thomas Huriaux
<thomas.huriaux@gmail.com>, Nicolas François
<nicolas.francois@centraliens.net>, Florentin Duneau
<fduneau@gmail.com>, Simon Paillard
<simon.paillard@resel.enst-bretagne.fr>, Denis Barbier
<barbier@debian.org>, David Prévot <david@tilapin.org> et
Jean-Philippe MENGUAL <jpmengual@debian.org>
Cette traduction est une documentation libre ; veuillez
vous reporter à la
GNU General
Public License version 3 concernant les conditions de copie et de
distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.
Si vous découvrez un bogue dans la traduction de cette page
de manuel, veuillez envoyer un message à
debian-l10n-french@lists.debian.org.