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 suru32) ; le champ legacypoll_events(u16) est évacué (doc maître §4).- Temporisations d’attente vs opérations
TIMEOUT: l’attente bornée d’une complétion passe parwait_completion_timeout(EXT_ARG/ABS_TIMER, décision Temps 1, §14.5). L’opTIMEOUTde 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 pourmadvise(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_resultinjecte unresviaIORING_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_SUCCESSne change pas leresdu 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 porteIORING_TIMEOUT_UPDATE. Annule ou re-arme un timeout en vol. - Erreurs :
ENOENT(cible inconnue/déjà déclenchée),EBUSY.
3.3 submit_link_timeout
#![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 leLinkedChainBuilderdu 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 dusync_canceldu Temps 1 (qui, lui, passe parregisteret est bloquant). CancelTarget/CancelFlags: parToken(user_data), parFd,FdFixed, parOp, ouAny(IORING_ASYNC_CANCEL_*).ALL= annuler toutes les correspondances.- Complétion :
into_result();-ENOENTsi rien ne correspond,-EALREADYsi déjà en cours d’annulation. Précision kernel : le nombre d’ops annulées n’est rendu (res = N) qu’avecCancelFlags::ALL; sansALL, une annulation réussie rendres = 0. (Pour compter, utiliserALL.) - 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 (sansF_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), à lapoll. - 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_WAITn’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
epollen couche 0. Les typesEpollOp/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 familleepollcouche 0 est à produire si d’autres consommateurs apparaissent (3ᵉ manque couche 0 révélé par les couches sup., avec inotify etMmapRegion).
6. Synchronisation
6.1 submit_futex_wait / submit_futex_wake / submit_futex_waitv — implé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 uneMmapRegion+ offset (cf.family-mem-mmap-region.md §3) : le mot futex vit dans une région partageable que le slot S1 garde vivante (uneMmapRegionLiveness), interdisantmunmaptant 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_waitvattend sur plusieurs futex à la fois. offsetlocalise le mot futex (u32) dans la région : validé en amont viaMmapRegion::futex_word— région inscriptible (WRITE, car la réf rendue est mutable), bornes + alignement sur 4 —EINVALsinon, avant soumission (Principe 4). La tailleFUTEX2_SIZE_U32est imposée (le mot d’uneMmapRegionest unAtomicU32) ; seulPRIVATEest exposé parFutexFlags.mask(pourwait/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_waitvdu kernel ne porte pas de masque par-attendu ({ val, uaddr, flags, __reserved }) ;FutexWaitern’expose donc pas de masque (un champ silencieusement ignoré serait un footgun, ADR-032). Pour une attente masquée, utilisersubmit_futex_wait(mono-attente). - Complétion :
wait/waitvcomplètent quand réveillés (ou-EAGAINsi la valeur ≠expectedà l’armement) viacompleted();wakerend le nombre de réveillés viainto_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 :waitidpré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_dataarbitraires). 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 parsendmsg/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 deREGISTER_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/openat2direct (Temps 3a).
8. Récapitulatif des 15 opérations
| # opcode | Opcode | Façade | Complétion |
|---|---|---|---|
| 0 | NOP | submit_nop / _with_result | completed / into_result |
| 6 | POLL_ADD | submit_poll_add | into_poll_result |
| 7 | POLL_REMOVE | submit_poll_remove / submit_poll_update | completed |
| 11 | TIMEOUT | submit_timeout | completed (-ETIME) |
| 12 | TIMEOUT_REMOVE | submit_timeout_remove / _update | completed |
| 14 | ASYNC_CANCEL | submit_cancel | into_result |
| 15 | LINK_TIMEOUT | (via LinkedChainBuilder, Temps 3c) | — |
| 20 | FILES_UPDATE | submit_files_update | into_result |
| 29 | EPOLL_CTL | submit_epoll_ctl | completed |
| 40 | MSG_RING | submit_message_ring_data / _fd | completed |
| 50 | WAITID | submit_waitid | into_waitid_result |
| 51 | FUTEX_WAIT | submit_futex_wait | completed |
| 52 | FUTEX_WAKE | submit_futex_wake | into_result |
| 53 | FUTEX_WAITV | submit_futex_waitv | completed |
| 54 | FIXED_FD_INSTALL | submit_fixed_fd_install | installed_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 (-ECANCELEDreç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
link_timeoutjamais en op isolée — exposé uniquement via leLinkedChainBuilder(Temps 3c) ; émission isolée refusée en amont.cancelasync vssync_cancel— deux voies assumées : asynchrone dans le flux (ici) et synchrone bloquante (Temps 1, base du téardown S2).msg_ringreconnu 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.- Futex async adossé au handle de vivacité
MmapRegion— même mécanisme de sûreté mémoire quemadvise(Temps 2a), pas d’unsafeexposée. 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.