Provided by: manpages-fr-dev_4.26.0-1_all bug

NOM

       futex – Verrouillage rapide en mode utilisateur

BIBLIOTHÈQUE

       Bibliothèque C standard (libc, -lc)

SYNOPSIS

       #include <linux/futex.h>   /* Définition des constantes FUTEX_* */
       #include <sys/syscall.h>   /* Définition des constantes SYS_* */
       #include <unistd.h>

       long syscall(SYS_futex, uint32_t *uaddr, int futex_op, uint32_t val,
                    const struct timespec *timeout,   /* ou : uint32_t val2 */
                    uint32_t *uaddr2, uint32_t val3);

       Remarque  :  la  glibc  ne  fournit  pas  de  fonction  autour  de  futex(), nécessitant l'utilisation de
       syscall(2).

DESCRIPTION

       L'appel système futex() offre une méthode pour attendre qu'une condition  soit  vraie.  On  l'utilise  en
       général  comme  construction  de  blocage  dans le contexte de la synchronisation de la mémoire partagée.
       Quand on utilise des futex, la majorité  des  opérations  de  synchronisation  s'effectue  dans  l'espace
       utilisateur.  Un  programme  de  l'espace utilisateur n'utilise l'appel système futex() que lorsqu'il est
       probable qu'il doive se bloquer plus longtemps avant que la condition ne soit vraie. D'autres  opérations
       futex() peuvent être utilisées pour réveiller des processus ou des threads qui attendent une condition en
       particulier.

       Un  futex  est une valeur 32 bits — désignée ci-dessous comme « mot futex » —dont l'adresse est fournie à
       l'appel système futex() (les futex ont une taille de 32 bits sur toutes les plateformes,  y  compris  les
       systèmes  64 bits). Toutes les opérations futex sont pilotées par cette valeur. Afin de partager un futex
       entre des processus, le futex est placé dans une zone de la mémoire  partagée  créée  en  utilisant  (par
       exemple)  mmap(2)  ou  shmat(2)  (ainsi,  le  mot  futex  peut  avoir  plusieurs adresses virtuelles dans
       différents processus, mais ces adresses se rapportent toutes au même emplacement de la mémoire physique).
       Dans un programme multithreadé, il suffit de mettre le mot futex dans une variable globale  partagée  par
       tous les threads.

       Lors de l'exécution d'une opération futex qui demande le blocage d'un thread, le noyau ne le bloquera que
       si  le  mot  futex  a  une valeur fournie par le thread appelant (en tant qu'un des paramètres de l'appel
       futex()) correspondant à celle prévue du mot  futex.  Le  chargement  de  la  valeur  du  mot  futex,  la
       comparaison  de  cette  valeur  avec  celle  attendue et le blocage s'effectueront de manière atomique et
       seront entièrement organisés par rapport aux opérations qui sont effectuées  en  parallèle  par  d'autres
       threads sur le même mot futex. Ainsi, le mot futex est utilisé pour relier la synchronisation de l'espace
       utilisateur  et  l'implémentation  du blocage par le noyau. Tout comme une opération compare-and-exchange
       atomique qui modifie potentiellement la  mémoire  partagée,  le  blocage  par  futex  est  une  opération
       compare-and-block atomique.

       Une  utilisation  des  futex consiste à implémenter des verrous. L'état du verrou (c'est-à-dire acquis ou
       non acquis) peut se représenter comme un drapeau auquel on a un accès atomique en  mémoire  partagée.  En
       absence  de  conflit  (uncontended  case),  un  thread peut accéder et modifier l'état du verrou avec des
       instructions atomiques, par exemple le passer de manière atomique de  l'état  non  acquis  à  acquis,  en
       utilisant  une instruction compare-and-exchange atomique (de telles instructions s'effectuent entièrement
       dans l'espace utilisateur et le noyau ne conserve aucune information sur l'état du  verrou).  D'un  autre
       côté, un thread peut être incapable d'acquérir un verrou parce qu'il est déjà acquis par un autre thread.
       Il  peut alors passer l'attribut du verrou en tant que mot futex, et la valeur représentant l'état acquis
       en tant que valeur attendue pour l'opération d'attente de futex(). Cette opération futex() bloquera si et
       seulement si le verrou est encore acquis (c'est-à-dire si la valeur du mot futex  correspond  toujours  à
       «  l'état acquis »). Lorsque le verrou est relâché, le thread doit d'abord réinitialiser l'état du verrou
       sur non acquis puis exécuter une opération futex qui réveille les  threads  bloqués  par  le  drapeau  de
       verrou  utilisé  en  tant que mot futex (cela peut être mieux optimisé pour éviter les réveils inutiles).
       Voir futex(7) pour plus de détails sur la manière d'utiliser les futex.

       Outre la fonctionnalité de base du futex consistant à attendre et à réveiller, d'autres opérations  futex
       visent à gérer des cas d'utilisation plus complexes.

       Remarquez qu'aucune initialisation ou destruction explicite n'est nécessaire pour utiliser les futex ; le
       noyau  ne garde un futex (c'est-à-dire un artefact d'implémentation interne au noyau) que pendant que les
       opérations telles que FUTEX_WAIT, décrite ci-dessous, s'effectuent sur un mot futex en particulier.

   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 futex_op ; val est une valeur dont la signification et l'objectif
       dépendent de futex_op.

       Les autres paramètres (timeout, uaddr2 et val3) ne sont nécessaires que pour certaines  opérations  futex
       décrites ci-dessous. Si un de ces arguments n'est pas nécessaire, il est ignoré.

       Pour  plusieurs  opérations  de blocage, le paramètre timeout est un pointeur vers une structure timespec
       qui indique la durée maximale de l'opération. Toutefois, contrairement  au  prototype  décrit  ci-dessus,
       pour  certaines opérations, les quatre octets les moins significatifs de ce paramètre sont utilisés comme
       un entier dont la signification est déterminée par l'opération. Pour ces opérations, le noyau diffuse  la
       valeur timeout d'abord à unsigned long, puis à uint32_t, et dans le reste de cette page, ce paramètre est
       désigné par val2 quand il est interprété de cette manière.

       Lorsqu'il  est  nécessaire,  le  paramètre  uaddr2 est un pointeur vers un deuxième mot futex utilisé par
       l'opération.

       L'interprétation du paramètre de l'entier final, val3, dépend de l'opération.

   Opérations futex
       Le paramètre futex_op est en deux parties : une commande qui indique l'opération à effectuer  et  un  bit
       ORed  avec zéro ou plusieurs options qui changent le comportement de l'opération. Les options qui peuvent
       être incluses dans futex_op sont les suivantes :

       FUTEX_PRIVATE_FLAG (depuis Linux 2.6.22)
              Ce bit d'option peut être utilisé avec toutes les opérations futex. Il dit au noyau que  le  futex
              est  un  processus privé non partagé avec d'autres processus (c'est-à-dire qu'il n'est utilisé que
              pour la synchronisation entre les threads du même processus). Cela permet au noyau d'effectuer des
              optimisations de performance supplémentaires.

              Par commodité, <linux/futex.h> définit un ensemble de constantes dont le suffixe est  _PRIVATE  et
              qui   sont   équivalentes  à  toutes  les  opérations  listées  ci-dessous  mais  avec  l'attribut
              FUTEX_PRIVATE_FLAG ORed dans la valeur  de  la  constante.  On  trouve  ainsi  FUTEX_WAIT_PRIVATE,
              FUTEX_WAKE_PRIVATE et ainsi de suite.

       FUTEX_CLOCK_REALTIME (depuis Linux 2.6.28)
              Ce   bit   d'option   ne   peut   être   utilisé   qu'avec   les   opérations   FUTEX_WAIT_BITSET,
              FUTEX_WAIT_REQUEUE_PI (depuis Linux 4.5), FUTEX_WAIT (depuis Linux 4.5) et FUTEX_LOCK_PI2  (depuis
              Linux 5.14).

              Si   cette   option  est  positionnée,  le  noyau  mesure  le  timeout  par  rapport  à  l'horloge
              CLOCK_REALTIME.

              Si cette option n'est pas positionnée,  le  noyau  mesure  le  timeout  par  rapport  à  l'horloge
              CLOCK_MONOTONIC.

       L'opération indiquée dans futex_op prend une de ces valeurs :

       FUTEX_WAIT (depuis Linux 2.6.0)
              Cette  option  teste  que  la  valeur  du  mot futex vers laquelle pointe l'adresse uaddr contient
              toujours la valeur val attendue, et si tel  est  le  cas,  elle  s'endort  jusqu'à  une  opération
              FUTEX_WAKE  sur  le  mot  futex.  Le  chargement de la valeur du mot futex est un accès en mémoire
              atomique  (c'est-à-dire  qu'il  utilise  des  instructions  machine  atomiques  de  l'architecture
              concernée).  Ce  chargement,  la  comparaison  avec  la  valeur  attendue  et  la  mise en sommeil
              s'effectuent de manière atomique et sont totalement organisés selon les  autres  opérations  futex
              sur le même mot futex. Si le thread commence à dormir, il est considéré comme en attente de ce mot
              futex.  Si  la  valeur  futex  ne correspond pas à val, l'appel échoue immédiatement avec l'erreur
              EAGAIN.

              Le but de la comparaison avec la valeur attendue est d'empêcher des réveils perdus.  Si  un  autre
              thread  a  changé  la valeur du mot futex après que le thread a décidé de se bloquer en se fondant
              sur la valeur d'avant, et si l'autre thread a effectué une  opération  FUTEX_WAKE  (ou  un  réveil
              équivalent)  après  le  changement  de cette valeur et avant cette opération FUTEX_WAIT, le thread
              appelant observera cette valeur et ne commencera pas à dormir.

              Si le timeout n'est pas NULL, la structure vers laquelle il pointe indique un délai d'attente (cet
              intervalle sera arrondi à la valeur supérieure à partir de la granularité de l'horloge système  et
              il  est  garanti  de  ne  pas  expirer  en  avance).  Le délai est mesuré par défaut par rapport à
              l'horloge CLOCK_MONOTONIC mais depuis Linux 4.5, l'horloge CLOCK_REALTIME  peut  être  choisie  en
              indiquant  FUTEX_CLOCK_REALTIME  dans  futex_op.  Si  le  timeout  est  NULL,  l'appel  se  bloque
              indéfiniment.

              Remarque : pour FUTEX_WAIT, le timeout est interprété comme une valeur relative. Cela diffère  des
              autres  opérations  futex  où  le  timeout  est  interprété comme une valeur absolue. Pour obtenir
              l'équivalent de FUTEX_WAIT, avec un délai absolu, utilisez  FUTEX_WAIT_BITSET  en  indiquant  val3
              comme FUTEX_BITSET_MATCH_ANY.

              Les paramètres uaddr2 et val3 sont ignorés.

       FUTEX_WAKE (depuis Linux 2.6.0)
              Cette  opération réveille jusqu'à val éléments en attente (comme dans FUTEX_WAIT) sur le mot futex
              à l'adresse uaddr. Généralement, val est indiqué soit sous la forme de 1 (réveil d'un seul élément
              en attente) soit avec INT_MAX (réveil de  tous  les  éléments  en  attente).  Vous  n'avez  aucune
              garantie quant aux éléments qui sont réveillés (par exemple un élément en attente dont la priorité
              d'ordonnancement  élevée  n'est  pas  garanti  de  se réveiller avant un autre d'une priorité plus
              basse).

              Les paramètres timeout, uaddr2 et val3 sont ignorés.

       FUTEX_FD (de Linux 2.6.0 jusqu'à Linux 2.6.25 inclus)
              Cette opération crée un descripteur de fichier associé au futex sur uaddr. L'appelant doit  fermer
              le  descripteur  de  fichier  renvoyé  après l'avoir utilisé. Quand un autre processus ou un autre
              thread effectue un FUTEX_WAKE sur le mot futex,  le  descripteur  de  fichier  indique  qu'il  est
              accessible en lecture avec select(2), poll(2), et epoll(7)

              Le descripteur de fichier peut être utilisé pour avoir des notifications asynchrones, si val n'est
              pas  nul, puis, quand un autre processus ou un autre thread exécute FUTEX_WAKE, l'appelant recevra
              le numéro du signal passé à val.

              Les paramètres timeout, uaddr2 et val3 sont ignorés.

              Parce qu'il était de façon inhérente sujet  à  des  situations  de  concurrence,  FUTEX_FD  a  été
              supprimé de Linux 2.6.26 et les suivants.

       FUTEX_REQUEUE (depuis Linux 2.6.0)
              Cette  opération  effectue  la même chose que FUTEX_CMP_REQUEUE (voir ci-dessous), sauf qu'elle ne
              vérifie rien en utilisant la valeur dans val3 (le paramètre val3 est ignoré).

       FUTEX_CMP_REQUEUE (depuis Linux 2.6.7)
              Cette opération vérifie d'abord si l'emplacement uaddr contient toujours la valeur  val3.  Si  tel
              n'est pas le cas, l'opération échoue avec l'erreur EAGAIN. Si tel est le cas, l'opération réveille
              un  maximum  de  val  éléments  en  attente  du  futex sur uaddr. S'il y a plus de val éléments en
              attente, les autres sont supprimés de la file d'attente du futex source sur uaddr et ajoutés à  la
              file  d'attente  du  futex  cible  sur  uaddr2. Le paramètre val2 indique une limite supérieure du
              nombre d'éléments remis en attente dans le futex sur uaddr2.

              Le chargement à partir de uaddr est un accès atomique en mémoire (c'est-à-dire qu'il  utilise  les
              instructions  machine  atomiques  de l'architecture concernée). Ce chargement, la comparaison avec
              val3 et la remise en attente d'éléments  s'effectuent  de  manière  atomique  et  sont  totalement
              organisées par rapport aux autres opérations sur le même mot futex.

              Les  valeurs classiques qu'on indique à val sont 0 ou 1 (indiquer INT_MAX n'est pas utile car cela
              rendrait l'opération FUTEX_CMP_REQUEUE équivalente à FUTEX_WAKE). La valeur limite  indiquée  avec
              val2  est  généralement  1  ou  INT_MAX (indiquer 0 en paramètre n'est pas utile car cela rendrait
              l'opération FUTEX_CMP_REQUEUE équivalente à FUTEX_WAIT).

              L'opération FUTEX_CMP_REQUEUE a été ajoutée pour remplacer l'ancienne FUTEX_REQUEUE. La différence
              est que la vérification de la valeur sur uaddr peut être utilisée pour s'assurer que la remise  en
              attente  ne  se  produit que sous certaines conditions, ce qui évite les conflits de mémoire (race
              conditions) dans certains cas d'utilisation.

              FUTEX_REQUEUE et FUTEX_CMP_REQUEUE peuvent être utilisées pour  éviter  des  réveils  en  troupeau
              (thundering  herd)  qui  peuvent  survenir  quand  on  utilise FUTEX_WAKE dans des cas où tous les
              éléments en attente qu'on réveille doivent acquérir un autre futex. Imaginons le scénario  suivant
              où plusieurs threads attendent en B, une file d'attente implémentée en utilisant un futex :

                  lock(A)
                  while (!check_value(V)) {
                      unlock(A);
                      block_on(B);
                      lock(A);
                  };
                  unlock(A);

              Si  un  thread  qui  se  réveille  utilisait  FUTEX_WAKE,  tous  les  éléments  attendant  en B se
              réveilleraient et essaieraient d'acquérir le verrou A. Cependant, réveiller tous  ces  threads  de
              cette  manière  serait vain car tous les threads, sauf un, se bloqueraient immédiatement à nouveau
              via le verrou A. Au contraire, une remise dans la file d'attente  ne  réveille  qu'un  élément  et
              déplace  les  autres  sur  le  verrou  A  et  quand celui réveillé déverrouille A, le suivant peut
              continuer.

       FUTEX_WAKE_OP (depuis Linux 2.6.14)
              Cette opération a été ajoutée pour prendre  en  charge  certains  cas  d'utilisation  de  l'espace
              utilisateur  où  plus  d'un  futex  à  la  fois  doit  être  géré.  L'exemple le plus frappant est
              l'implémentation de pthread_cond_signal(3), qui nécessite des opérations sur deux futex, une  pour
              implémenter  le mutex, l'autre pour utiliser dans l'implémentation de la file d'attente associée à
              la variable conditionnelle. FUTEX_WAKE_OP permet d'implémenter  de  tels  cas  sans  augmenter  le
              nombre de conflits et de changement de contexte.

              L'opération  FUTEX_ WAKE_OP revient à exécuter le code suivant de manière atomique et complètement
              organisé en fonction des opérations futex sur un des deux mots futex fournis :

                  uint32_t oldval = *(uint32_t *) uaddr2;
                  *(uint32_t *) uaddr2 = oldval op oparg;
                  futex(uaddr, FUTEX_WAKE, val, 0, 0, 0);
                  if (oldval cmp cmparg)
                      futex(uaddr2, FUTEX_WAKE, val2, 0, 0, 0);

              En d'autres termes, FUTEX_WAKE_OP fait ce qui suit :

              -  sauvegarde la valeur d'origine du mot futex sur uaddr2 et effectue une opération pour  modifier
                 la  valeur  du  futex  sur  uaddr2 ; il s'agit d'un accès en mémoire read-modify-write atomique
                 (c'est-à-dire d'une utilisation des  instructions  machine  atomiques  liées  à  l'architecture
                 concernée)

              -  réveille un maximum de val éléments en attente sur le futex pour le mot futex sur uaddr ;

              -  et  selon  les  résultats d'un test de la valeur d'origine du mot futex sur uaddr2, réveille un
                 maximum de val2 éléments en attente du mot futex sur le futex sur uaddr2.

              L'opération et la comparaison qui doivent être effectuées sont encodées dans les bits du paramètre
              val3. Visuellement, l'encodage est :

                  +---+---+-----------+-----------+
                  |op |cmp|   oparg   |  cmparg   |
                  +---+---+-----------+-----------+
                    4   4       12          12    <== # of bits

              Exprimé en code, l'encodage est :

                  #define FUTEX_OP(op, oparg, cmp, cmparg) \
                                  (((op & 0xf) << 28) | \
                                  ((cmp & 0xf) << 24) | \
                                  ((oparg & 0xfff) << 12) | \
                                  (cmparg & 0xfff))

              Dans ce qui précède, op et cmp sont chacun des codes listés ci-dessous. Les  composants  oparg  et
              cmparg sont des valeurs numériques littérales, sauf les remarques ci-dessous.

              Le composant op prend une de ces valeurs :

                  FUTEX_OP_SET        0  /* uaddr2 = oparg; */
                  FUTEX_OP_ADD        1  /* uaddr2 += oparg; */
                  FUTEX_OP_OR         2  /* uaddr2 |= oparg; */
                  FUTEX_OP_ANDN       3  /* uaddr2 &= ~oparg; */
                  FUTEX_OP_XOR        4  /* uaddr2 ^= 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_BITSETval3 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_BITSETval3 est indiqué en tant que FUTEX_BITSET_MATCH_ANY ; c'est-à-dire, réveiller
              n’importe quel élément en attente.

              Les arguments uaddr2 et timeout sont ignorés.

   Futex et héritage de priorité
       Linux  prend  en  charge  l'héritage  de priorité (priority inheritance, PI) des futex, afin de gérer des
       problèmes d'inversion des priorités qu'on peut rencontrer avec des verrous futex normaux. L'inversion des
       priorités est un problème qui survient  quand  une  tâche  de  haute  priorité  est  bloquée  en  attente
       d'acquérir  un  verrou  que possède une tâche de basse priorité issue du processeur. Du coup, la tâche de
       priorité basse ne va pas relâcher le verrou et celle de haute priorité reste bloquée.

       L'héritage de priorité est un mécanisme pour  gérer  le  problème  d'inversion  des  priorités.  Avec  ce
       mécanisme,  quand  une  tâche  à  haute  priorité est bloquée par un verrou possédé par une tâche à basse
       priorité, la priorité de la seconde est temporairement amenée au même niveau que celle à haute  priorité,
       de  sorte  qu'elle  ne  soit  pas  doublée  par une tâche de niveau intermédiaire et qu'elle puisse ainsi
       avancer pour relâcher le verrou. Pour fonctionner, l'héritage de priorité doit  être  transitif,  ce  qui
       signifie  que si une tâche à haute priorité bloque sur le verrou d'une tâche à priorité intermédiaire (et
       ainsi de suite sur des chaînes de la taille de votre choix), les deux tâches (ou plus généralement toutes
       les tâches de la chaîne de verrous) voient leur niveau de priorité amené à celui  de  la  tâche  à  haute
       priorité.

       Du  point  de  vue de l'espace utilisateur, le futex a conscience d'un PI en acceptant une réglementation
       (décrite ci-dessous) entre l'espace utilisateur et le  noyau  sur  la  valeur  du  mot  futex,  couplé  à
       l'utilisation  d'opérations  futex  PI  décrites  ci-dessous  (contrairement  aux autres opérations futex
       décrites  ci-dessus,  celles  PI-futex  sont  conçues  pour  l'implémentation  de  mécanismes  IPC   très
       spécifiques).

       Les  opérations  PI-futex  décrites  ci-dessous  diffèrent  des  autres  opérations dans le sens où elles
       imposent des règles dans l'utilisation de la valeur du mot futex :

       -  Si le verrou n'est pas acquis, la valeur du mot futex doit être 0.

       -  Si le verrou est acquis, la valeur du mot futex doit être l'ID du thread (TID  ;  voir  gettid(2))  du
          thread propriétaire.

       -  Si  le  verrou  a  un  propriétaire  et  s'il  y  a  des threads en concurrence pour le verrou, le bit
          FUTEX_WAITERS doit être positionné dans la valeur du mot futex ; autrement dit, cette valeur est :

              FUTEX_WAITERS | TID

          (Remarquez que cela n'est pas possible pour un mot futex PI d'être sans propriétaire ni  FUTEX_WAITERS
          défini).

       Avec  cette  règle,  une  application  de  l'espace  utilisateur peut acquérir un verrou non acquis ou en
       relâcher un en utilisant des instructions  atomiques  dans  l'espace  utilisateur  (comme  une  opération
       compare-and-swap telle que cmpxchg sur l'architecture x86). L'acquisition d'un verrou consiste simplement
       dans l'utilisation de compare-and-swap pour positionner la valeur du mot futex de manière atomique sur le
       TID  de  l'appelant si sa valeur précédente était 0. Relâcher un verrou exige d'utiliser compare-and-swap
       pour positionner la valeur du mot futex sur 0 si la valeur précédente était le TID prévu.

       Si un futex est déjà acquis (c'est-à-dire qu'il a une valeur positive), les éléments en  attente  doivent
       utiliser  l'opération  FUTEX_LOCK_PI pour acquérir le verrou. Si d'autres threads attendent le verrou, le
       bit FUTEX_WAITERS est défini dans la valeur du futex ; dans ce cas le détenteur du verrou  doit  utiliser
       l'opération FUTEX_UNLOCK_PI pour relâcher le verrou.

       Dans  le  cas où les appelants sont bloqués dans le noyau (c'est-à-dire qu'ils doivent effectuer un appel
       futex()), ils traitent directement avec ce qu'on appelle un RT-mutex, un  mécanisme  de  verrouillage  du
       noyau qui implémente la sémantique de l'héritage de priorité requis. Après que le RT-mutex est acquis, la
       valeur  futex  est  mise  à  jour  en  fonction,  avant  que  le thread appelant ne renvoie vers l'espace
       utilisateur.

       Il est important de remarquer que le noyau mettra à jour la valeur du mot futex avant  de  renvoyer  vers
       l'espace  utilisateur  (cela  enlève  la possibilité pour la valeur d'un mot futex de se terminer dans un
       état non valable, par exemple en ayant un propriétaire mais en  ayant  la  valeur  0,  ou  en  ayant  des
       éléments en attente mais aucun bit FUTEX_WAITERS positionné).

       Si  un futex a un RT-mutex associé dans le noyau (c'est-à-dire qu'il y a des éléments en attente bloqués)
       et si le propriétaire du futex/RT-mutex meurt de manière inattendue, le  noyau  nettoie  le  RT-mutex  et
       passe  la  main  au  prochain  élément  en attente. Cela implique, en retour, que la valeur dans l'espace
       utilisateur soit mise à jour en fonction. Pour dire que c'est nécessaire,  le  noyau  positionne  le  bit
       FUTEX_OWNER_DIED  dans  le  mot  futex  ainsi  que  dans l'ID du thread du nouveau propriétaire. L'espace
       utilisateur peut détecter cette situation par la  présence  du  bit  FUTEX_OWNER_DIED  et  il  est  alors
       responsable pour nettoyer l'espace laissé par le propriétaire mort.

       Les  PI  futex sont utilisés en indiquant une des valeurs listées ci-dessous dans futex_op. Remarquez que
       les opérations de PI  futex  doivent  être  utilisées  par  paires  et  sont  soumises  à  des  exigences
       supplémentaires :

       -  FUTEX_LOCK_PI,  FUTEX_LOCK_PI2  et FUTEX_TRYLOCK_PI vont de pair avec FUTEX_UNLOCK_PI. FUTEX_UNLOCK_PI
          ne doit être appelé que sur un futex appartenant au thread appelant, tel que défini par les règles  de
          la valeur, sans quoi on obtient l'erreur EPERM.

       -  FUTEX_WAIT_REQUEUE_PI  va de pair avec FUTEX_CMP_REQUEUE_PI. Elles doivent s'effectuer depuis un futex
          non-PI vers un PI futex distinct (sans quoi on obtient l'erreur  EINVAL).  De  plus,  val  (le  nombre
          d'éléments en attente à réveiller) doit être de 1 (sans quoi on obtient l'erreur EINVAL).

       Les opérations PI futex sont comme suit :

       FUTEX_LOCK_PI (depuis Linux 2.6.18)
              Cette  opération est utilisée après avoir essayé sans succès d'acquérir un verrou en utilisant une
              instruction atomique en mode utilisateur, car le mot futex a une valeur positive – en  particulier
              parce qu'il contenait le TID (spécifique à l’espace de noms PID) du verrou propriétaire.

              L'opération  vérifie  la  valeur du mot futex sur l'adresse uaddr. Si la valeur est de 0, le noyau
              essaie de positionner de manière atomique la valeur du futex sur  le  TID  de  l'appelant.  Si  la
              valeur  du  mot  futex est positive, le noyau positionne de manière atomique le bit FUTEX_WAITERS,
              qui signale au propriétaire du futex qu'il ne  peut  pas  déverrouiller  le  futex  dans  l'espace
              utilisateur de manière atomique, en positionnant la valeur du futex à 0. Après cela, le noyau :

              (1)  Essaie de trouver le thread associé au TID du propriétaire.

              (2)  Crée ou réutilise l'état du noyau sur la base du propriétaire (s'il s'agit du premier élément
                   en  attente,  il n'existe pas d'état du noyau pour ce futex, donc il est créé en verrouillant
                   le RT-mutex et le propriétaire du futex devient propriétaire du RT-mutex). Si des éléments en
                   attente existent, l'état existant est réutilisé.

              (3)  Rattache l'élément en attente au futex (c'est-à-dire que  l'élément  est  mis  dans  la  file
                   d'attente du RT-futex).

              S'il  existe  plus d'un élément en attente, la mise dans la file d'un élément se fait par ordre de
              priorité descendant (pour des  informations  sur  l'ordre  des  priorités,  voir  les  points  sur
              l'ordonnancement  SCHED_DEADLINE,  SCHED_FIFO  et  SCHED_RR dans sched(7)). Le propriétaire hérite
              soit de la bande passante de processeur de l'élément en attente (si l'élément est  programmé  sous
              la  règle  SCHED_DEADLINE  ou  SCHED_FIFO),  soit de la priorité de l'élément en attente (s'il est
              programmé sous la règle SCHED_RR ou SCHED_FIFO). Cet héritage suit la chaîne de verrous  dans  les
              cas de verrous imbriqués et il effectue la détection des verrous morts (deadlocks).

              Le  paramètre  timeout  fournit  un délai de tentative de verrouillage. Si timeout est positif, la
              structure vers laquelle il pointe  indique  un  délai  absolu  mesuré  en  fonction  de  l'horloge
              CLOCK_REALTIME. Si timeout est NULL, l'opération se bloquera indéfiniment.

              Les paramètres uaddr2, val et val3 sont ignorés.

       FUTEX_LOCK_PI2 (depuis Linux 5.14)
              Cette  opération  est la même que FUTEX_LOCK_PI, sauf que l'horloge par rapport à laquelle timeout
              est mesuré peut être sélectionnée. Par défaut, le délai (absolu) indiqué dans timeout  est  mesuré
              par  rapport  à l'horloge CLOCK_MONOTONIC mais si l'attribut FUTEX_CLOCK_REALTIME est indiqué dans
              futex_op, le délai est mesuré par rapport à l'horloge CLOCK_REALTIME.

       FUTEX_TRYLOCK_PI (depuis Linux 2.6.18)
              L'opération essaie d'acquérir le verrou sur uaddr. Elle est appelée quand  l'acquisition  atomique
              dans l'espace utilisateur n'a pas réussi parce que le mot futex ne valait pas 0.

              Du  fait  que le noyau accède à plus d'informations d'état que l'espace utilisateur, l'acquisition
              du verrou pourrait réussir si elle est effectuée par le  noyau  dans  les  cas  où  le  mot  futex
              (c'est-à-dire  les  informations  d'état  accessibles  dans l'espace utilisateur) contient un état
              stable (FUTEX_WAITERS et/ou FUTEX_OWNER_DIED). Cela peut arriver quand le  propriétaire  du  futex
              est  mort.  L'espace utilisateur ne peut pas gérer cette condition de manière "race-free", mais le
              noyau peut corriger cela et acquérir le futex.

              Les paramètres uaddr2, val, timeout et val3 sont ignorés.

       FUTEX_UNLOCK_PI (depuis Linux 2.6.18)
              Cette opération réveille l'élément ayant la plus haute priorité et attendant un  FUTEX_LOCK_PI  ou
              un FUTEX_LOCK_PI2 à l'adresse indiquée par le paramètre uaddr.

              Cela est appelé quand la valeur dans l'espace utilisateur sur uaddr ne peut pas être passée à 0 de
              manière atomique depuis un TID (du propriétaire).

              Les paramètres uaddr2, val, timeout et val3 sont ignorés.

       FUTEX_CMP_REQUEUE_PI (depuis Linux 2.6.31)
              Cette opération est une variante PI-aware de FUTEX_CMP_REQUEUE. Elle remet en attente des éléments
              bloqués  avec  FUTEX_WAIT_REQUEUE_PI  sur  uaddr à partir d'un futex source non-PI (uaddr) vers un
              futex cible PI (uaddr2).

              Comme avec FUTEX_CMP_REQUEUE, cette opération réveille un maximum de val éléments qui attendent le
              futex sur uaddr. Toutefois,  pour  FUTEX_CMP_REQUEUE_PI,  val  doit  valoir  1  (puisque  son  but
              principal  est  d'éviter l’effet de troupeau (thundering herd). Les autres éléments sont supprimés
              de la file d'attente du futex source sur uaddr et ajoutés sur celle du futex cible sur uaddr2.

              Les paramètres val2 et val3 ont le même objectif qu'avec FUTEX_CMP_REQUEUE.

       FUTEX_WAIT_REQUEUE_PI (depuis Linux 2.6.31)
              Attendre un futex non-PI sur uaddr et se mettre potentiellement en  attente  (avec  une  opération
              FUTEX_CMP_REQUEUE_PI  dans  une  autre tâche), d'un futex PI sur uaddr2. L'opération d'attente sur
              uaddr est la même que pour FUTEX_WAIT.

              L'élément peut être retiré de la file d'attente sur uaddr sans être transféré sur uaddr2 à  l’aide
              d’une  opération  FUTEX_WAKE  dans une autre tâche. Dans ce cas, l'opération FUTEX_WAIT_REQUEUE_PI
              échoue avec l'erreur EAGAIN.

              Si timeout n'est pas NULL, la structure vers  laquelle  il  pointe  indique  un  délai  absolu  de
              l'opération d'attente. Si timeout est NULL, l'opération peut se bloquer indéfiniment.

              L'argument val3 est ignoré.

              FUTEX_WAIT_REQUEUE_PI et FUTEX_CMP_REQUEUE_PI ont été ajoutés pour gérer un cas d'utilisation bien
              particulier : la prise en charge des variables conditionnelles de threads POSIX ayant connaissance
              de l'héritage de priorité. L'idée est que ces opérations devraient toujours aller par paires, afin
              de  garantir  que  l'espace  utilisateur  et  le  noyau restent toujours synchronisés. Ainsi, dans
              l'opération FUTEX_WAIT_REQUEUE_PI, l'application dans l'espace utilisateur pré-indique la cible de
              la remise en attente qui va se faire dans l'opération FUTEX_CMP_REQUEUE_PI.

VALEUR RENVOYÉE

       En cas d'erreur (en supposant que futex() a été appelé à l’aide de  syscall(2)),  toutes  les  opérations
       renvoient -1 et positionnent errno pour indiquer l'erreur.

       En cas de succès, le code de retour dépend de l'opération, comme décrit dans la liste suivante :

       FUTEX_WAIT
              Renvoie  0  si  l'appelant  a  été  réveillé.  Remarquez  qu'un  réveil peut également résulter de
              l'utilisation de motifs d'utilisation classiques de futex dans du code non lié qui a  pu  utiliser
              l'emplacement mémoire du mot futex (par exemple des implémentations classiques basées sur futex de
              mutex  Pthreads  peuvent  provoquer cela dans certaines conditions). Donc, les appelants devraient
              toujours, à titre conservatoire, supposer qu'un code de retour 0 peut signifier un faux réveil, et
              donc utiliser la valeur  du  mot  futex  (à  savoir  le  schéma  de  synchronisation  de  l'espace
              utilisateur) pour décider de rester bloqués ou pas.

       FUTEX_WAKE
              Renvoie le nombre de processus en attente qui ont été réveillés.

       FUTEX_FD
              Renvoie le nouveau descripteur de fichier associé au futex.

       FUTEX_REQUEUE
              Renvoie le nombre de processus en attente qui ont été réveillés.

       FUTEX_CMP_REQUEUE
              Renvoie le nombre total d'éléments en attente réveillés ou remis dans la file du futex pour le mot
              futex sur uaddr2. Si cette valeur est supérieure à val, la différence devient le nombre d'éléments
              en attente remis dans la file du futex pour le mot futex sur uaddr2.

       FUTEX_WAKE_OP
              Renvoie  le  nombre  total  d'éléments  en  attente  réveillés. Il s'agit de la somme des éléments
              réveillés sur les deux futex pour les mots futex sur uaddr et uaddr2.

       FUTEX_WAIT_BITSET
              Renvoie 0 si l'appelant a  été  réveillé.  Voir  FUTEX_WAIT  sur  la  manière  d'interpréter  cela
              correctement en pratique.

       FUTEX_WAKE_BITSET
              Renvoie le nombre de processus en attente qui ont été réveillés.

       FUTEX_LOCK_PI
              Renvoie 0 si le futex a appliqué le verrou avec succès.

       FUTEX_LOCK_PI2
              Renvoie 0 si le futex a appliqué le verrou avec succès.

       FUTEX_TRYLOCK_PI
              Renvoie 0 si le futex a appliqué le verrou avec succès.

       FUTEX_UNLOCK_PI
              Renvoie 0 si le futex a correctement enlevé le verrou.

       FUTEX_CMP_REQUEUE_PI
              Renvoie le nombre total d'éléments en attente réveillés ou remis dans la file du futex pour le mot
              futex sur uaddr2. Si cette valeur est supérieure à val, la différence devient le nombre d'éléments
              en attente remis dans la file du futex pour le mot futex sur uaddr2.

       FUTEX_WAIT_REQUEUE_PI
              Renvoie  0  si  l'appelant a été mis dans la file d'attente avec succès au futex pour le mot futex
              sur uaddr2.

ERREURS

       EACCES Pas d'accès en lecture à la mémoire d'un mot futex.

       EAGAIN (FUTEX_WAIT, FUTEX_WAIT_BITSET, FUTEX_WAIT_REQUEUE_PI) La  valeur  vers  laquelle  pointait  uaddr
              n'était pas égale à la valeur val attendue au moment de l'appel.

              Remarque  :  sur  Linux,  les  noms  symboliques EAGAIN et EWOULDBLOCK (les deux apparaissent dans
              différents endroits du code futex du noyau) ont la même valeur.

       EAGAIN (FUTEX_CMP_REQUEUE, FUTEX_CMP_REQUEUE_PI) La valeur vers laquelle pointait uaddr n'était pas égale
              à la valeur val3 attendue.

       EAGAIN (FUTEX_LOCK_PI,   FUTEX_LOCK_PI2,   FUTEX_TRYLOCK_PI,   FUTEX_CMP_REQUEUE_PI)   L'ID   du   thread
              propriétaire  du  futex  sur  uaddr  (pour  FUTEX_CMP_REQUEUE_PI  : uaddr2) est sur le point de se
              terminer, mais il n'a pas encore géré le nettoyage de l'état interne. Réessayez.

       EDEADLK
              (FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_CMP_REQUEUE_PI) Le mot futex sur uaddr est
              déjà verrouillé par l'appelant.

       EDEADLK
              (FUTEX_CMP_REQUEUE_PI) Pendant qu'il remettait en attente un élément du PI futex pour le mot futex
              sur uaddr2, le noyau a détecté un verrou mort (deadlock).

       EFAULT Le paramètre d'un pointeur nécessaire (c'est-à-dire uaddr, uaddr2 ou timeout) ne pointait pas vers
              une adresse valable de l'espace utilisateur.

       EINTR  Une opération FUTEX_WAIT ou FUTEX_WAIT_BITSET a été interrompue par un  signal  (voir  signal(7)).
              Dans  Linux  2.6.22,  cette  erreur  pouvait  aussi  être  renvoyée  pour  un faux réveil ; depuis
              Linux 2.6.22, cela n'arrive plus.

       EINVAL L'opération dans futex_op fait partie de celles qui utilisent un délai, mais le paramètre  timeout
              fourni  n'était  pas  valable  (tv_sec  valait  moins  de  0  ou  tv_nsec  ne  valait pas moins de
              1 000 000 000).

       EINVAL L'opération indiquée dans futex_op utilise uaddr et/ou uaddr2 mais l'un d'eux ne pointe  pas  vers
              un objet valable — c'est-à-dire, l'adresse n'est pas alignée sur quatre octets.

       EINVAL (FUTEX_WAIT_BITSET, FUTEX_WAKE_BITSET) Le masque de bit fourni dans val3 vaut zéro.

       EINVAL (FUTEX_CMP_REQUEUE_PI)   uaddr  est  égal  à  uaddr2  (c'est-à-dire qu'une remise en attente a été
              tentée sur le même futex).

       EINVAL (FUTEX_FD) Le numéro du signal fourni dans val n'est pas valable.

       EINVAL (FUTEX_WAKE,  FUTEX_WAKE_OP,  FUTEX_WAKE_BITSET,  FUTEX_REQUEUE,  FUTEX_CMP_REQUEUE)  Le  noyau  a
              détecté  une  incohérence  entre  l'état  de  l'espace  utilisateur  sur  uaddr et l'état du noyau
              — c'est-à-dire qu'il a détecté un élément qui attend  dans  FUTEX_LOCK_PI  ou  FUTEX_LOCK_PI2  sur
              uaddr.

       EINVAL (FUTEX_LOCK_PI,   FUTEX_LOCK_PI2,  FUTEX_TRYLOCK_PI,  FUTEX_UNLOCK_PI)  Le  noyau  a  détecté  une
              incohérence entre l'état de l'espace utilisateur sur uaddr et l'état du noyau. Cela  indique  soit
              une corruption d'état, soit que le noyau a trouvé un élément en attente sur uaddr qui attend aussi
              à l'aide de FUTEX_WAIT ou de FUTEX_WAIT_BITSET.

       EINVAL (FUTEX_CMP_REQUEUE_PI) Le noyau a détecté une incohérence entre l'état de l'espace utilisateur sur
              uaddr  et  l'état  du noyau ; c'est-à-dire qu'il a détecté un élément qui attend via FUTEX_WAIT ou
              FUTEX_WAIT_BITSET sur uaddr2.

       EINVAL (FUTEX_CMP_REQUEUE_PI) Le noyau a détecté une incohérence entre l'état de l'espace utilisateur sur
              uaddr et l'état du noyau ; c'est-à-dire qu'il  a  détecté  un  élément  qui  attend  à  l'aide  de
              FUTEX_WAIT ou de FUTEX_WAIT_BITESET sur uaddr.

       EINVAL (FUTEX_CMP_REQUEUE_PI) Le noyau a détecté une incohérence entre l'état de l'espace utilisateur sur
              uaddr  et  l'état  du  noyau  ;  c'est-à-dire  qu'il  a  détecté un élément qui attend à l'aide de
              FUTEX_LOCK_PI ou de FUTEX_LOCK_PI2 (au lieu de FUTEX_WAIT_REQUEUE_PI).

       EINVAL (FUTEX_CMP_REQUEUE_PI) Tentative de remise dans la file d'un élément  en  attente  vers  un  futex
              différent de celui indiqué avec l'appel FUTEX_WAIT_REQUEUE_PI correspondant pour cet élément.

       EINVAL (FUTEX_CMP_REQUEUE_PI) Le paramètre val ne vaut pas 1.

       EINVAL Argument incorrect.

       ENFILE (FUTEX_FD) La limite du nombre total de fichiers ouverts sur le système a été atteinte.

       ENOMEM (FUTEX_LOCK_PI,  FUTEX_LOCK_PI2,  FUTEX_TRYLOCK_PI,  FUTEX_CMP_REQUEUE_PI)  Le  noyau  n'a  pas pu
              allouer de la mémoire pour conserver les informations d'état.

       ENOSYS Opération non valable indiquée dans futex_op.

       ENOSYS L'option FUTEX_CLOCK_REALTIME était indiquée dans  futex_op,  mais  l'opération  qui  l'accompagne
              n'est ni FUTEX_WAIT, ni FUTEX_WAIT_BITSET, ni FUTEX_WAIT_REQUEUE_PI, ni FUTEX_LOCK_PI2.

       ENOSYS (FUTEX_LOCK_PI,    FUTEX_LOCK_PI2,    FUTEX_TRYLOCK_PI,   FUTEX_UNLOCK_PI,   FUTEX_CMP_REQUEUE_PI,
              FUTEX_WAIT_REQUEUE_PI) Une vérification pendant l'exécution a déterminé que l'opération n'est  pas
              disponible.  Les  opérations  PI-futex ne sont pas implémentées sur toutes les architectures et ne
              sont pas prises en charge sur certaines variantes de processeur.

       EPERM  (FUTEX_LOCK_PI,  FUTEX_LOCK_PI2,  FUTEX_TRYLOCK_PI,  FUTEX_CMP_REQUEUE_PI)  L'appelant  n'est  pas
              autorisé à se rattacher au futex sur uaddr (pour FUTEX_CMP_REQUEUE_PI : le futex sur uaddr2) (cela
              peut venir d'une corruption de l'état dans l'espace utilisateur).

       EPERM  (FUTEX_UNLOCK_PI) Le verrou représenté par le mot futex n'appartient pas à l'appelant.

       ESRCH  (FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_TRYLOCK_PI, FUTEX_CMP_REQUEUE_PI) L'ID du thread dans le mot
              futex sur uaddr n'existe pas.

       ESRCH  (FUTEX_CMP_REQUEUE_PI) L'ID du thread dans le mot futex sur uaddr2 n'existe pas.

       ETIMEDOUT
              L'opération  de futex_op a utilisé un délai indiqué dans timeout et le délai a expiré avant la fin
              de l'opération.

STANDARDS

       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 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;        /* 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/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/

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.

Pages du manuel de Linux 6.9.1                    15 juin 2024                                          futex(2)