Scroll to navigation

futex(2) System Calls Manual futex(2)

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 op, ...);

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(2const) s'effectuent sur un mot futex en particulier.

Argument

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 op.

Opérations futex

Le paramètre 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 op sont les suivantes :

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.
Ce bit d'option ne peut être utilisé qu'avec les opérations FUTEX_WAIT_BITSET(2const), FUTEX_WAIT_REQUEUE_PI(2const) (depuis Linux 4.5), FUTEX_WAIT(2const) (depuis Linux 4.5) et FUTEX_LOCK_PI2(2const) (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 op prend une de ces valeurs :

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(2const) ou FUTEX_LOCK_PI2(2const) 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(2const) 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 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(2const), FUTEX_LOCK_PI2(2const) et FUTEX_TRYLOCK_PI(2const) vont de pair avec FUTEX_UNLOCK_PI(2const). FUTEX_UNLOCK_PI(2const) 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(2const) va de pair avec FUTEX_CMP_REQUEUE_PI(2const). Elles doivent s'effectuer depuis un futex non-PI vers un PI futex distinct (sans quoi on obtient l'erreur EINVAL). De plus, 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_WAIT_REQUEUE_PI(2const) et FUTEX_CMP_REQUEUE_PI(2const) 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(2const), 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(2const).

VALEUR RENVOYÉE

En cas d'erreur, la valeur de retour est -1 et errno est définie pour préciser l'erreur.

En cas de succès, le code de retour dépend de l'opération.

ERREURS

Pas d'accès en lecture à la mémoire d'un mot futex.
uaddr2 ne pointait pas vers une adresse valable de l'espace utilisateur.
uaddr2 ne pointe pas vers un objet valable — c'est-à-dire, l'adresse n'est pas alignée sur quatre octets.
Argument incorrect.
Opération non valable indiquée dans op.
L'option FUTEX_CLOCK_REALTIME était indiquée dans op, mais l'opération qui l'accompagne n'est ni FUTEX_WAIT_BITSET(2const), ni FUTEX_WAIT_REQUEUE_PI(2const), ni FUTEX_LOCK_PI2(2const).

NORMES

Linux.

HISTORIQUE

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.

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

Usage: futex_demo [nloops]
(Default: 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 op, uint32_t val,
const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) {
return syscall(SYS_futex, uaddr, 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. */
for (;;) {
/* 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; /* State: unavailable */
*futex2 = 1; /* State: available */
/* 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("Child (%jd) %u\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) %u\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/locking/pi-futex.rst
  • Documentation/locking/futex-requeue-pi.rst
  • Documentation/locking/rt-mutex.rst
  • Documentation/locking/rt-mutex-design.rst
  • Documentation/robust-futex-ABI.rst

Franke, H., Russell, R. Kirwood, M., 2002.
Fuss, Futexes and Furwocks: Fast Userlevel Locking in Linux (à partir des actes du Ottawa Linux Symposium 2002),

Hart, D., 2009. A futex overview and update.

Hart, D. and Guniguntala, D., 2009. Requeue-PI: Making glibc Condvars PI-Aware (à partir des actes l'atelier Real-Time Linux 2009).

Drepper, U., 2011. Futexes Are Tricky.

Bibliothèque d'exemple de futex https://mirrors.kernel.org/pub/linux/kernel/people/rusty/

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 à debian-l10n-french@lists.debian.org.

14 février 2026 Linux man-pages 6.20