IPC · Section 2
futex(2)
Mutex utilisateur rapide — la primitive noyau derrière chaque mutex pthread, variable de condition et verrou moderne sous Linux.
Signature
#include <linux/futex.h>
#include <sys/syscall.h>
long futex(uint32_t * uaddr, int futex_op, uint32_t val, const struct timespec * timeout, uint32_t * uaddr2, uint32_t val3);- uaddr
- Pointeur sur le mot futex (uint32_t) dans la mémoire utilisateur du processus.
- futex_op
- Code d'opération (voir le tableau des drapeaux). Les 7 bits bas choisissent l'opération ; les bits hauts portent FUTEX_PRIVATE_FLAG / FUTEX_CLOCK_REALTIME.
- val
- Valeur propre à l'opération. Pour FUTEX_WAIT, c'est la valeur que uaddr doit avoir pour que l'attente prenne effet ; pour FUTEX_WAKE, le nombre maximum de threads à réveiller.
- timeout
- Pour les opérations WAIT, timeout relatif ou absolu. NULL signifie attendre indéfiniment.
- uaddr2
- Pour les opérations de requeue (FUTEX_REQUEUE, FUTEX_CMP_REQUEUE), adresse du futex secondaire. Sinon inutilisé.
- val3
- Propre à l'opération. Pour FUTEX_WAIT_BITSET / FUTEX_WAKE_BITSET porte le masque de bits pour réveil sélectif.
Description
futex() est la brique de toute primitive de synchronisation utilisateur sous Linux. L'idée : l'état du verrou (un uint32_t à uaddr) est lu et modifié par l'application en espace utilisateur via des instructions atomiques ; le noyau n'est invoqué que pour le chemin lent — quand un thread doit dormir en attente, ou en réveiller un autre. FUTEX_WAIT vérifie atomiquement que *uaddr vaut toujours val et, si oui, bloque le thread appelant ; FUTEX_WAKE réveille jusqu'à val threads en attente sur uaddr. FUTEX_PRIVATE_FLAG marque un futex comme privé au processus (le noyau saute la traduction d'adresse inter-processus), utilisé par défaut par toutes les implémentations pthread modernes. De nombreuses opérations existent (REQUEUE pour le broadcast de condvar, LOCK_PI / UNLOCK_PI pour les mutex à héritage de priorité, WAIT_BITSET pour les réveils sélectifs, FUTEX_WAKE_OP pour les opérations composées atomiques) ; voir futex(2) pour l'ensemble complet.
Numéros par architecture
| Architecture | Numéro | ABI | Point d'entrée |
|---|---|---|---|
| x86 (i386) | 240 | i386 | sys_futex_time32 |
| x64 (x86_64) | 202 | common | sys_futex |
| ARM64 (aarch64) | 98 | — | — |
Historique noyau
Introduit dans Linux 2.5.7.
2.5.7
futex() a été ajouté en 2.5.7 (sorti en 2.6) pour remplacer les anciens appels sémaphores lourds. C'est la primitive de verrouillage la plus marquante du Linux moderne, rendant les mutex utilisateur essentiellement gratuits dans le cas non contenté.
2.6.7
Le support des futex robustes a été ajouté : set_robust_list / get_robust_list laissent un thread enregistrer une liste par thread des mutex qu'il détient. Si le thread meurt (ou son processus est tué) en pleine section, le noyau parcourt la liste et marque chacun owner-died, laissant les autres détecter et récupérer.
2.6.18
Les opérations PI (FUTEX_LOCK_PI / FUTEX_UNLOCK_PI / FUTEX_TRYLOCK_PI) ont été ajoutées pour les threads temps réel — le noyau booste la priorité d'ordonnancement du détenteur quand un attendant de plus haute priorité bloque, évitant les inversions classiques.
5.16
futex_waitv() est arrivé : attendre sur plusieurs futex simultanément et se réveiller sur le premier prêt — utilisé par les émulateurs et moteurs de jeux pour des attentes multi-événements efficaces sans les acrobaties FUTEX_REQUEUE.
seccomp & conteneurs
Docker default profile
Autorisé
Podman default profile
Autorisé
futex() est dans tous les profils par défaut et ne peut être bloqué : chaque appel pthread l'utilise. Le plus récent futex_waitv() (Linux 5.16) est aussi fondamental et doit être autorisé. Pas de filtrage d'arguments utile — les codes d'op et adresses sont applicatifs.
libseccomp
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex_waitv), 0);Exemple strace
$ strace -e futex -p $(pidof firefox) 2>&1 | head -5
futex(0x7f3a14002910, FUTEX_WAIT_PRIVATE, 2, NULL) = 0
futex(0x7f3a14002914, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x55d4e8c1d0a0, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 0, {tv_sec=…}, FUTEX_BITSET_MATCH_ANY) = -1 ETIMEDOUTLes lignes futex() dominent toute strace d'un programme multithreadé — utiliser le mode résumé -c pour voir si la contention domine le temps. Un programme « bloqué » montre souvent un thread en FUTEX_WAIT sans FUTEX_WAKE correspondant ; comparer l'uaddr à /proc/<pid>/maps et la pile du thread détenteur (gdb) identifie l'interblocage. Filtrer avec -e trace=futex pour ne voir que le motif de contention.
Sécurité & observabilité
futex() n'est généralement pas une primitive de sécurité — son universalité fait que son absence est plus intéressante que sa présence. Cela dit, les CVE noyau dans l'implémentation futex ont eu un impact historique (le célèbre pivot Dirty COW utilisait la sémantique futex, et la course PI CVE-2014-3153 a donné root sur Android pendant des années), donc la posture de version noyau compte. Le tracepoint eBPF sys_enter_futex est trop bruyant pour la supervision générale — la plupart des charges en émettent des milliers par seconde. Pour le débogage pthread, l'approche utilisateur (mode debug des mutex glibc, ou rr) est plus productive que la trace syscall.
Erreurs
- EACCES
- —
- EAGAIN
- FUTEX_WAIT : *uaddr n'était pas égal à val au moment du contrôle (un wake est survenu juste avant ; le chemin utilisateur doit réessayer).
- EFAULT
- uaddr hors de l'espace d'adressage du processus.
- EINTR
- Attente interrompue par un signal. Réessayer côté utilisateur.
- EINVAL
- Mauvaise opération, uaddr mal aligné (doit être aligné 4 octets), ou combinaison de drapeaux invalide.
- ENOSYS
- —
- ETIMEDOUT
- FUTEX_WAIT avec timeout écoulé avant réveil.
Drapeaux
- FUTEX_WAIT
- 0
- Vérifie atomiquement *uaddr == val et, si oui, dort jusqu'à réveil (ou timeout). Le chemin de contention de pthread_mutex_lock().
- FUTEX_WAKE
- 1
- Réveille jusqu'à val threads en attente sur uaddr. Le chemin de réveil de pthread_mutex_unlock().
- FUTEX_REQUEUE
- 3
- Réveille jusqu'à val attendants, puis déplace jusqu'à val2 (slot timeout) autres attendants de uaddr vers uaddr2. Le chemin broadcast de pthread_cond_broadcast().
- FUTEX_CMP_REQUEUE
- 4
- —
- FUTEX_WAKE_OP
- 5
- —
- FUTEX_LOCK_PI
- 6
- Variante héritage de priorité — quand un thread haute priorité se bloque sur un futex tenu par un thread basse priorité, le noyau booste temporairement la priorité du détenteur pour éviter l'inversion.
- FUTEX_UNLOCK_PI
- 7
- —
- FUTEX_TRYLOCK_PI
- 8
- —
- FUTEX_WAIT_BITSET
- 9
- —
- FUTEX_WAKE_BITSET
- 10
- —
- FUTEX_PRIVATE_FLAG
- 128
- Optimisation processus-privé — évite la résolution d'adresse dans le hash global futex du noyau, utilisé par toute glibc moderne.
- FUTEX_CLOCK_REALTIME
- 256
- —