Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec couche 0 — Module io_uring, Temps 2c : opérations async-spécifiques

Spécification technique — Version 1.0 (cible kernel : Linux 6.12 LTS)

Position. Le Temps 2c spécifie les 15 opérations d’io_uring qui n’ont pas d’équivalent filesystem/réseau direct : contrôle du ring, temporisation, annulation, surveillance d’événements, synchronisation et communication inter-ring (doc maître io-uring-0-inventaire.md, axe B). Elles réutilisent le cœur du Temps 1. Plusieurs sont la base d’API affinées aux Temps 3c (linked), 3d (multishot) ou 3a (ressources fixes) — les renvois sont indiqués.


1. Conventions transverses du Temps 2c

  • poll32_events : on n’expose que la variante 32 bits (PollEvents = bitflags sur u32) ; le champ legacy poll_events (u16) est évacué (doc maître §4).
  • Temporisations d’attente vs opérations TIMEOUT : l’attente bornée d’une complétion passe par wait_completion_timeout (EXT_ARG/ABS_TIMER, décision Temps 1, §14.5). L’op TIMEOUT de ce Temps est pour les temporisations dans le flux (échéances autonomes, comptage de complétions, link-timeout) — usages explicites distincts.
  • Mémoire partagée (futex) : un mot futex vit dans de la mémoire qui doit rester valide jusqu’à la complétion ; on réutilise le handle de vivacité adossé à MmapRegion (family-mem) introduit pour madvise (Temps 2a §6.2).
  • Types partagés : WaitTarget/WaitOptions/SignalInfo (family-process), EpollOp/EpollEvent (family-*), PollEvents. Renvoi aux familles pour les numéros de syscall équivalents.

2. Contrôle : nop

#![allow(unused)]
fn main() {
pub fn submit_nop(&mut self) -> Result<SubmissionToken, Errno>;                 // OP 0
pub fn submit_nop_with_result(&mut self, injected: i32)
    -> Result<SubmissionToken, Errno>;                                          // NOP_INJECT_RESULT
}
  • Opcode : IORING_OP_NOP (0). Aucune action ; complète immédiatement.
  • Usage : amorçage/mesure du ring, jalon dans une chaîne (Temps 3c), tests (la variante _with_result injecte un res via IORING_NOP_INJECT_RESULT — précieux pour tester les chemins d’erreur sans provoquer une vraie erreur kernel).
  • Complétion : completed() / into_result().

3. Temporisation

3.1 submit_timeout

#![allow(unused)]
fn main() {
pub fn submit_timeout(&mut self, spec: TimeoutSpec, flags: TimeoutFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 11
}
  • Opcode : IORING_OP_TIMEOUT (11).
  • TimeoutSpec : durée (__kernel_timespec) et/ou seuil de complétions (off = nombre de CQE après lequel le timeout se déclenche). Un timeout peut donc être « après 10 ms » et/ou « après N complétions ».
  • TimeoutFlags : ABS (échéance absolue), BOOTTIME/REALTIME (choix d’horloge), ETIME_SUCCESS, MULTISHOT (déclenchements répétés → variante multishot, Temps 3d).
  • Complétion : completed() ; res == -ETIME à l’expiration. Précision (comportement réel kernel 6.12) : ETIME_SUCCESS ne change pas le res du CQE (toujours -ETIME à l’expiration) ; il n’altère que le marquage « échec » utilisé par les chaînes liées (Temps 3c) — l’expiration n’y casse pas la chaîne. (La rédaction initiale « l’expiration est un succès » était trompeuse hors contexte de chaîne.)

3.2 submit_timeout_remove / submit_timeout_update

#![allow(unused)]
fn main() {
pub fn submit_timeout_remove(&mut self, target: SubmissionToken)
    -> Result<SubmissionToken, Errno>;                                          // OP 12
pub fn submit_timeout_update(&mut self, target: SubmissionToken, spec: TimeoutSpec, flags: TimeoutFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 12 + TIMEOUT_UPDATE
}
  • Opcode : IORING_OP_TIMEOUT_REMOVE (12) ; l’update porte IORING_TIMEOUT_UPDATE. Annule ou re-arme un timeout en vol.
  • Erreurs : ENOENT (cible inconnue/déjà déclenchée), EBUSY.
#![allow(unused)]
fn main() {
pub fn submit_link_timeout(&mut self, spec: TimeoutSpec, flags: TimeoutFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 15
}
  • Opcode : IORING_OP_LINK_TIMEOUT (15). Sémantique : borne dans le temps l’opération précédente d’une chaîne liée ; si le timeout expire en premier, l’op liée est annulée (ECANCELED), et inversement.
  • Contrainte : n’a de sens qu’attaché à une op via IOSQE_IO_LINK. La façade sûre l’expose via le LinkedChainBuilder du Temps 3c (.with_link_timeout(spec)), pas en op isolée — l’émettre seule retourne une erreur de validation amont (Principe 4).

4. Annulation : cancel (asynchrone)

#![allow(unused)]
fn main() {
pub fn submit_cancel(&mut self, target: CancelTarget, flags: CancelFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 14
}
  • Opcode : IORING_OP_ASYNC_CANCEL (14). Variante asynchrone du sync_cancel du Temps 1 (qui, lui, passe par register et est bloquant).
  • CancelTarget/CancelFlags : par Token (user_data), par Fd, FdFixed, par Op, ou Any (IORING_ASYNC_CANCEL_*). ALL = annuler toutes les correspondances.
  • Complétion : into_result() ; -ENOENT si rien ne correspond, -EALREADY si déjà en cours d’annulation. Précision kernel : le nombre d’ops annulées n’est rendu (res = N) qu’avec CancelFlags::ALL ; sans ALL, une annulation réussie rend res = 0. (Pour compter, utiliser ALL.)
  • Interaction slab : l’annulation d’une op fait arriver sa complétion avec -ECANCELED ; le slot S1 (et son buffer) est restitué normalement. Pour le multishot, la complétion finale (sans F_MORE) libère le slot.

5. Surveillance d’événements

5.1 submit_poll_add / submit_poll_remove / submit_poll_update

#![allow(unused)]
fn main() {
pub fn submit_poll_add(&mut self, fd: BorrowedFd<'_>, events: PollEvents)
    -> Result<SubmissionToken, Errno>;                                          // OP 6
pub fn submit_poll_remove(&mut self, target: SubmissionToken)
    -> Result<SubmissionToken, Errno>;                                          // OP 7
pub fn submit_poll_update(&mut self, target: SubmissionToken, events: PollEvents)
    -> Result<SubmissionToken, Errno>;                                          // OP 7 + POLL_UPDATE
}
  • Opcodes : POLL_ADD (6), POLL_REMOVE (7). Surveille un FD pour des événements (lecture/écriture/erreur), à la poll.
  • Complétion (poll_add) : into_poll_result() -> PollEvents (événements prêts). Mono-coup ici ; multishot (IORING_POLL_ADD_MULTI, plusieurs notifications) et level-triggered (POLL_ADD_LEVEL) au Temps 3d.
  • poll_update : modifie les événements surveillés ou le user_data d’un poll en vol (POLL_UPDATE_EVENTS/POLL_UPDATE_USER_DATA).

5.2 submit_epoll_ctl

#![allow(unused)]
fn main() {
pub fn submit_epoll_ctl(&mut self, epfd: BorrowedFd<'_>, op: EpollOp, fd: BorrowedFd<'_>, event: EpollEvent)
    -> Result<SubmissionToken, Errno>;                                          // OP 29
}
  • Opcode : IORING_OP_EPOLL_CTL (29). Équivalent : epoll_ctl (add/mod/del sur un set epoll), exécuté en asynchrone.
  • Complétion : completed(). Note : en 6.12, c’est la seule intégration epoll d’io_uring (EPOLL_WAIT n’arrive qu’après 6.12, hors périmètre). Utile pour piloter un set epoll existant sans syscall synchrone.
  • Note d’implémentation (manque couche 0) : il n’existe pas de famille epoll en couche 0. Les types EpollOp/EpollEvent (miroir #[repr(C, packed)]) ont été ajoutés à air-sys-types::io_uring (consommés par cette façade) ; la création d’un epfd (epoll_create1) reste test-only (pas de wrapper public). Une vraie petite famille epoll couche 0 est à produire si d’autres consommateurs apparaissent (3ᵉ manque couche 0 révélé par les couches sup., avec inotify et MmapRegion).

6. Synchronisation

6.1 submit_futex_wait / submit_futex_wake / submit_futex_waitvimplémenté

Implémenté (PR coordonnée family-mem, 2026-06-12). La signature historique &AtomicU32 (insoundable en async : le borrow ne survit pas au retour de la façade) a été remplacée par une référence dans une MmapRegion + offset (cf. family-mem-mmap-region.md §3) : le mot futex vit dans une région partageable que le slot S1 garde vivante (une MmapRegionLiveness), interdisant munmap tant que l’op est en vol — ni UAF, ni fuite (prouvé Miri + loom).

#![allow(unused)]
fn main() {
pub fn submit_futex_wait(&mut self, region: &MmapRegion, offset: usize,
                         expected: u64, mask: u64, flags: FutexFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 51
pub fn submit_futex_wake(&mut self, region: &MmapRegion, offset: usize,
                         nr: u64, mask: u64, flags: FutexFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 52
pub fn submit_futex_waitv(&mut self, waiters: Vec<FutexWaiter>, flags: FutexFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 53

pub struct FutexWaiter {
    pub region: MmapRegion,   // possédée (clone) ⇒ garde de vivacité naturelle
    pub offset: usize,        // borné, aligné sur 4 (sinon EINVAL)
    pub expected: u64,        // pas de masque : futex_waitv kernel n'en porte pas
}
}
  • Opcodes : FUTEX_WAIT (51), FUTEX_WAKE (52), FUTEX_WAITV (53).
  • Intérêt : un reactor io_uring peut attendre un futex sans bloquer son thread — il intègre la synchronisation userspace dans le même mécanisme que les I/O. futex_waitv attend sur plusieurs futex à la fois.
  • offset localise le mot futex (u32) dans la région : validé en amont via MmapRegion::futex_word — région inscriptible (WRITE, car la réf rendue est mutable), bornes + alignement sur 4 — EINVAL sinon, avant soumission (Principe 4). La taille FUTEX2_SIZE_U32 est imposée (le mot d’une MmapRegion est un AtomicU32) ; seul PRIVATE est exposé par FutexFlags. mask (pour wait/wake) = bitset futex (addr3).
  • Sûreté : le slot S1 retient la (les) MmapRegionLiveness (cf. §1) ⇒ la région reste mappée jusqu’à la complétion.
  • Note ABI : struct futex_waitv du kernel ne porte pas de masque par-attendu ({ val, uaddr, flags, __reserved }) ; FutexWaiter n’expose donc pas de masque (un champ silencieusement ignoré serait un footgun, ADR-032). Pour une attente masquée, utiliser submit_futex_wait (mono-attente).
  • Complétion : wait/waitv complètent quand réveillés (ou -EAGAIN si la valeur ≠ expected à l’armement) via completed() ; wake rend le nombre de réveillés via into_result().

6.2 submit_waitid

#![allow(unused)]
fn main() {
pub fn submit_waitid(&mut self, target: WaitTarget, options: WaitOptions, info: Box<SignalInfo>)
    -> Result<SubmissionToken, Errno>;                                          // OP 50
}
  • Opcode : IORING_OP_WAITID (50). Équivalent : waitid (cf. family-process.md ; conv. couche 0 : waitid préféré à wait/waitpid).
  • WaitTarget : pid / pgid / pidfd / tous — typé (pas d’entier brut).
  • Sortie : le kernel remplit info (SignalInfo), buffer possédé déplacé dans le slot. Complétion : into_waitid_result() -> Result<Box<SignalInfo>, Errno>.
  • Intérêt : récolter la fin d’un processus enfant sans bloquer le reactor — brique d’un superviseur (pertinent pour air-launchd, couche 5).

7. Inter-ring et ressources

7.1 submit_msg_ring

#![allow(unused)]
fn main() {
pub fn submit_message_ring_data(&mut self, target: &IoUring, res: i32, user_data: u64, flags: MessageRingFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 40 (MSG_DATA)
pub fn submit_message_ring_fd(&mut self, target: &IoUring, src_slot: u32, dst_slot: FixedSlotTarget, flags: MessageRingFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 40 (MSG_SEND_FD)
}
  • Opcode : IORING_OP_MSG_RING (40). Envoie un message d’un ring vers un autre :
    • MSG_DATA : poste un CQE dans le ring cible (res + user_data arbitraires). Mécanisme de notification inter-thread/inter-service très léger — pertinent pour réveiller un reactor pair (lien AirCom).
    • MSG_SEND_FD : transfère un descripteur enregistré vers la table de FD d’un autre ring, sans passer par sendmsg/socket.
  • MessageRingFlags : CQE_SKIP (pas de CQE côté cible), FLAGS_PASS (propager des flags vers le CQE cible).
  • Complétion (côté émetteur) : completed(). Côté cible : un CQE apparaît dans sa CQ.
  • Sûreté : le ring cible est emprunté (&IoUring) ; en pratique on transmet souvent un ring fd enregistré (Temps 3a) pour découpler les durées de vie — détaillé au Temps 3a/3e.

7.2 submit_files_update

#![allow(unused)]
fn main() {
pub fn submit_files_update(&mut self, offset: u32, fds: Vec<RawFd>)
    -> Result<SubmissionToken, Errno>;                                          // OP 20
}
  • Opcode : IORING_OP_FILES_UPDATE (20). Met à jour des slots de la table de FD enregistrée de façon asynchrone (équivalent op de REGISTER_FILES_UPDATE2, mais dans le flux de soumission).
  • Renvoi : la table de FD fixes (FixedFdTable) est définie au Temps 3a.
  • Complétion : into_result() = nombre de slots mis à jour.

7.3 submit_fixed_fd_install

#![allow(unused)]
fn main() {
pub fn submit_fixed_fd_install(&mut self, fixed_slot: u32, flags: InstallFdFlags)
    -> Result<SubmissionToken, Errno>;                                          // OP 54
}
  • Opcode : IORING_OP_FIXED_FD_INSTALL (54). Convertit un descripteur direct/enregistré (qui n’a pas de FD ordinaire) en un FD ordinaire, rendu dans la complétion.
  • InstallFdFlags : NO_CLOEXEC (par défaut le FD installé est CLOEXEC).
  • Complétion : completion.installed_fd() -> Result<OwnedFd, Errno>.
  • Usage : passer à du code tiers un FD réel à partir d’un descripteur direct obtenu via accept/socket/openat2 direct (Temps 3a).

8. Récapitulatif des 15 opérations

# opcodeOpcodeFaçadeComplétion
0NOPsubmit_nop / _with_resultcompleted / into_result
6POLL_ADDsubmit_poll_addinto_poll_result
7POLL_REMOVEsubmit_poll_remove / submit_poll_updatecompleted
11TIMEOUTsubmit_timeoutcompleted (-ETIME)
12TIMEOUT_REMOVEsubmit_timeout_remove / _updatecompleted
14ASYNC_CANCELsubmit_cancelinto_result
15LINK_TIMEOUT(via LinkedChainBuilder, Temps 3c)
20FILES_UPDATEsubmit_files_updateinto_result
29EPOLL_CTLsubmit_epoll_ctlcompleted
40MSG_RINGsubmit_message_ring_data / _fdcompleted
50WAITIDsubmit_waitidinto_waitid_result
51FUTEX_WAITsubmit_futex_waitcompleted
52FUTEX_WAKEsubmit_futex_wakeinto_result
53FUTEX_WAITVsubmit_futex_waitvcompleted
54FIXED_FD_INSTALLsubmit_fixed_fd_installinstalled_fd

9. Types ajoutés / partagés

Nouveaux au Temps 2c : TimeoutSpec, TimeoutFlags, PollEvents, CancelFlags, FutexFlags, FutexWaiter, MessageRingFlags, InstallFdFlags, FixedSlotTarget. Partagés : WaitTarget, WaitOptions, SignalInfo (family-process), EpollOp, EpollEvent. Nouvelles méthodes de Completion : into_poll_result, into_waitid_result, installed_fd. FixedFdTable et le ring fd enregistré : Temps 3a.


10. Stratégie de tests

  • Intégration : nop (+ injection de résultat), timeout (expiration + ETIME_SUCCESS + comptage de complétions), cancel d’une op en vol (-ECANCELED reçu, slot restitué), poll_add sur un pipe puis écriture, epoll_ctl add/mod/del, futex_wait réveillé par futex_wake, futex_waitv multi-attente, waitid sur un enfant qui se termine, msg_ring data entre deux rings (CQE apparaît côté cible), fixed_fd_install d’un fd direct.
  • Property-based : cancel idempotent (ENOENT/EALREADY), poll events cohérents.
  • Sûreté : Miri/handle de vivacité pour les futex (la mémoire du mot futex survit à l’op) ; pas de fuite de FD sur msg_ring_fd / fixed_fd_install.
  • Erreurs via simulateur : ETIME, ECANCELED, ENOENT, EAGAIN (futex).
  • Couverture 100 % lignes + branches.

11. Décisions de fond émergées au Temps 2c

  1. link_timeout jamais en op isolée — exposé uniquement via le LinkedChainBuilder (Temps 3c) ; émission isolée refusée en amont.
  2. cancel async vs sync_cancel — deux voies assumées : asynchrone dans le flux (ici) et synchrone bloquante (Temps 1, base du téardown S2).
  3. msg_ring reconnu comme primitive inter-reactor — notification et transfert de FD entre rings ; brique potentielle du réveil de pairs AirCom. La gestion fine des durées de vie inter-ring est détaillée au Temps 3a/3e.
  4. Futex async adossé au handle de vivacité MmapRegion — même mécanisme de sûreté mémoire que madvise (Temps 2a), pas d’unsafe exposée.
  5. nop_with_result — exposé surtout pour l’outillage de test (couverture des branches d’erreur sans vraie erreur kernel).

12. Travail à reprendre

Spec suivante : io-uring-2d-cmd.md (URING_CMD : passthrough générique + commandes socket SIOCINQ/SIOCOUTQ/get/setsockopt). Traduction anglaise globale après validation des documents français.


Licence du document : MPL 2.0 Statut : Spécification technique du Temps 2c (async-spécifiques) du module air-sys-syscall::io_uring, cible kernel 6.12 LTS.