Spec couche 0 — Module io_uring : document maître d’inventaire
Spécification technique — Version 1.0 (cible kernel figée : Linux 6.12 LTS)
Rôle de ce document. C’est la carte de référence du module
air-sys-syscall::io_uring. Il énumère exhaustivement la surface ABI d’io_uring telle qu’elle existe dans le kernel 6.12 (plancher figé), la classe (retenue / obsolète-évacuée), la rattache à un Temps de spécification et à un symbole de façade Rust, puis se termine par le squelette rustdoc du cœur de l’API.Il rend l’objectif « exposer toutes les fonctionnalités d’io_uring » vérifiable : toute entrée du header uapi
io_uring.hde la v6.12 doit apparaître ici avec une décision. C’est l’entrée à valider avant la production des specs détaillées par Temps et avant que l’implémentation (corps des fonctions) soit confiée.Relation avec l’existant. Ce document remplace le rôle d’inventaire de
io-uring-overview.md(calibré sur un kernel ~5.15, devenu incomplet). Les décisions structurantes restent celles de l’ADR-022 ; les deux décisions de soundness de la section 3 étendent ADR-022 et feront l’objet d’un amendement RFC (cf. §3.4).
1. Cible kernel figée et méthodologie
1.1 Décision de périmètre
- Plancher = Linux 6.12 LTS. Tout ce qui est antérieur à 6.12 est évacué au démarrage. On n’implémentera des chemins pour kernels plus anciens que s’il y a une demande explicite (et alors par feature-gate + fallback documenté).
- Source de vérité = le header
include/uapi/linux/io_uring.hdu tagv6.12. Tout symbole présent dans ce header est dans le périmètre ; tout symbole présent uniquement dansmaster(postérieur) est hors périmètre de démarrage et n’entrera que plus tard, par détection runtime, sans amendement (ADR-022, statut futur). - Une seule façade : la moderne. Quand le kernel 6.12 offre déjà une
variante moderne d’une fonctionnalité, Air n’expose que la moderne.
L’ancienne est consignée dans
docs/UNSUPPORTED.mdavec justification (cohérent avec « variantes modernes préférées » du CLAUDE.md). Cf. §4.
1.2 Hors périmètre de démarrage (présent dans master, absent de 6.12)
Recensé ici pour mémoire, pour ne pas le réintroduire par erreur :
- Opcodes :
RECV_ZC,EPOLL_WAIT,READV_FIXED,WRITEV_FIXED,PIPE,NOP128,URING_CMD128. - Register opcodes :
SEND_MSG_RING,ZCRX_IFQ,ZCRX_CTRL,RESIZE_RINGS,MEM_REGION,QUERY,BPF_FILTER. - Setup flags :
HYBRID_IOPOLL,CQE_MIXED,SQE_MIXED,SQ_REWIND. - CQE flags :
IORING_CQE_F_SKIP,IORING_CQE_F_32. - Enter flags :
EXT_ARG_REG,NO_IOWAIT. - Features :
RW_ATTR,NO_IOWAIT. - uring_cmd socket op :
SOCKET_URING_OP_TX_TIMESTAMP,SOCKET_URING_OP_GETSOCKNAME(6.12 n’a que SIOCINQ/SIOCOUTQ/GET/SETSOCKOPT).
2. Le modèle io_uring en bref (rappel d’ancrage)
io_uring repose sur trois syscalls et un protocole d’anneaux mmappés :
| Syscall | Rôle |
|---|---|
io_uring_setup(entries, params) | Crée le ring, retourne un FD, remplit les offsets de mmap. |
io_uring_enter(fd, to_submit, min_complete, flags, arg, argsz) | Soumet des SQE et/ou attend des CQE. |
io_uring_register(fd, opcode, arg, nr_args) | Enregistre/configure des ressources (buffers, files, restrictions…). |
Trois zones mmappées : l’anneau de soumission (SQ), l’anneau de complétion
(CQ), et le tableau de SQE (64 octets, ou 128 avec SETUP_SQE128). Le
CQE fait 16 octets (ou 32 avec SETUP_CQE32). Le couplage user_data
(posé dans le SQE, rendu dans le CQE) est le fil qui relie une soumission à sa
complétion — c’est le pivot de la façade Air (cf. §3.1).
La surface fonctionnelle se décompose en 8 axes énumérables ; les sections 5.1 à 5.8 les épuisent un par un.
2.1 Terminologie et sigles (référence)
io_uring emploie des sigles standardisés (kernel, man pages, liburing). Ce tableau les définit une fois pour toutes ; la documentation Air privilégie la forme verbeuse et n’utilise les sigles que là où ils correspondent à un nom ABI kernel littéral (qu’on ne renomme jamais, par exactitude).
| Sigle | Forme complète (EN) | Forme complète (FR) | Statut dans la façade Air |
|---|---|---|---|
| SQ | submission queue | file de soumission | sigle = anneau kernel ; façade verbeuse (submission_queue_*) |
| CQ | completion queue | file de complétion | sigle = anneau kernel ; façade verbeuse (completion_queue_*) |
| SQE | submission queue entry | entrée de soumission | type façade RawSubmissionQueueEntry ; struct kernel io_uring_sqe (gardé) |
| CQE | completion queue entry | entrée de complétion | type façade RawCompletionQueueEntry ; struct kernel io_uring_cqe (gardé) |
| FD | file descriptor | descripteur de fichier | types std OwnedFd/BorrowedFd/RawFd (gardés) |
| ZC | zero-copy | copie zéro | façade verbeuse : submit_send_zero_copy, ZeroCopyFlags, into_zero_copy_buffer |
| op / opcode | operation code | code d’opération | façade verbeuse : IoUringOpcode, RawOpcode |
| SQPOLL | submission-queue polling | scrutation de la file de soumission | nom de flag kernel IORING_SETUP_SQPOLL (gardé) ; type SqpollIoUring |
| NAPI | New API (busy-poll réseau) | — | nom de mécanisme kernel (gardé) |
sq_entries / cq_entries | submission/completion queue depth | profondeur des files | champs ABI de io_uring_params (gardés littéralement) |
Règle appliquée. Les identifiants de la façade Air sont écrits en toutes
lettres (submission_queue_*, completion_queue_*, IoUringOpcode,
ZeroCopyFlags, in_flight, etc.) — verbeux et sans ambiguïté pour le
développeur (Principe 7). Les noms ABI du kernel (io_uring_sqe,
io_uring_cqe, IORING_*, sqe->res, cq_entries…) sont conservés
verbatim : les renommer introduirait une divergence avec le kernel et les man
pages, donc moins de précision, pas plus.
⚠️ Précision factuelle. Le « S » de SQ/SQE est Submission (soumission), pas « Send ». SQ = submission queue, SQE = submission queue entry.
3. Décisions de soundness gelées (étendent ADR-022)
Ces décisions déterminent toutes les signatures de la façade. Elles sont gelées avant l’écriture du moindre corps de fonction.
3.1 Décision S1 — État des opérations en vol : slab pré-alloué
Problème. Le modèle « transfert d’ownership » (ADR-022, Décision 3) prend
le buffer en argument et le rend à la complétion. Entre soumission et
complétion, il faut garer quelque part {buffer, métadonnées, type d'op},
retrouvable par user_data. Une HashMap<u64, _> allouerait par opération —
ce qui viole la règle CLAUDE.md « pas d’allocation heap dans le happy
path ».
Décision. L’IoUring possède un slab pré-alloué dimensionné à la
profondeur de la SQ (sq_entries). Chaque opération en vol occupe un slot.
Le SubmissionToken encapsule un index de slot + génération (compteur de génération anti-réutilisation),
pas un user_data brut. user_data côté kernel = l’index de slot encodé.
Conséquences :
- Zéro allocation par opération dans le happy path : le buffer transféré est déplacé (move) dans le slot, pas copié ni réalloué.
- Back-pressure naturelle : slab plein ⇒
submit_*retourneErr(Errno::EBUSY)(ou un type d’erreur dédié) avant d’atteindre le kernel. - Le slot est libéré quand la complétion correspondante est consommée et que l’ownership du buffer est rendu à l’appelant.
3.2 Décision S2 — Téardown sûr : Drop quiesce, shutdown() explicite
Problème. Si l’IoUring (ou un buffer en vol) est libéré alors que le
kernel a encore des opérations en cours, le kernel peut écrire dans de la
mémoire libérée. C’est le hasard de soundness io_uring + Rust. ADR-022 ne
le traitait pas.
Décision (fidèle à « sur-sécuriser puis dégraisser après mesure », Principe 5).
shutdown(self) -> Result<(), Errno>: voie propre et explicite. Émet une annulation globale (IORING_REGISTER_SYNC_CANCELouASYNC_CANCEL_ANY), draine les complétions restantes, puis ferme le FD. Ne bloque pas indéfiniment (timeout).Drop: filet de sécurité. Si des opérations sont encore en vol,Dropquiesce (annule + draine de façon bloquante) avant de rendre la mémoire. Le coût (blocage potentiel enDrop) est assumé et documenté ; l’utilisateur soucieux de performance appelleshutdown()explicitement.- Les slots de la Décision S1 retiennent l’ownership des buffers tant que l’op n’est pas complétée ⇒ le buffer ne peut pas être libéré avant le kernel.
3.3 Décision S3 — Restrictions & sandbox comme primitive capability
IORING_REGISTER_RESTRICTIONS + SETUP_R_DISABLED + REGISTER_ENABLE_RINGS
forment une primitive de confinement : créer un ring désactivé, restreindre
les opcodes/flags/register-ops autorisés, puis l’activer. Cela mappe
directement sur le modèle d’entitlements signés d’ADR-010 et les capabilities
AirCom (ADR-001).
Décision. Cette primitive est traitée en première classe (Temps 3f,
cf. §6), pas noyée dans la registration générique. La façade expose un
constructeur IoUringBuilder capable d’appliquer un jeu de restrictions
avant l’activation, de sorte qu’un service Air confiné reçoive un ring dont
le kernel garantit qu’il ne peut émettre que les opérations autorisées par son
manifeste.
3.4 Statut RFC
S1, S2, S3 étendent ADR-022 sans contredire ses 10 décisions. Elles sont
consignées dans l’ADR-028 (« Soundness et téardown du module io_uring »),
complément d’ADR-022 — cf. ../../adrs/ADR-028-soundness-io-uring-fr.md.
4. Règle « pas d’obsolète » : décisions concrètes
Variantes legacy évacuées vers UNSUPPORTED.md (le kernel 6.12 offre déjà
la moderne) :
| Legacy (évacué) | Remplaçant moderne (exposé) | Raison |
|---|---|---|
IORING_REGISTER_BUFFERS (reg 0) | IORING_REGISTER_BUFFERS2 (reg 15) + BUFFERS_UPDATE (16) | Tagging des ressources (FEAT_RSRC_TAGS), sparse, update partiel. |
IORING_REGISTER_FILES (reg 2) | IORING_REGISTER_FILES2 (reg 13) + FILES_UPDATE2 (14) | Idem ; struct io_uring_files_update est marqué deprecated dans le header. |
IORING_REGISTER_FILES_UPDATE (reg 6) | IORING_REGISTER_FILES_UPDATE2 (reg 14) | Idem. |
IORING_OP_PROVIDE_BUFFERS (op 31) | IORING_REGISTER_PBUF_RING (reg 22) | Provided buffers ring-mapped, sans syscall par lot, consommation incrémentale (IOU_PBUF_RING_INC). |
IORING_OP_REMOVE_BUFFERS (op 32) | IORING_UNREGISTER_PBUF_RING (reg 23) | Idem. |
Champ SQE poll_events (__u16) | poll32_events (__u32) | Le header note « compatibility » ; on n’expose que la version 32 bits. |
Note : UNREGISTER_BUFFERS (1) et UNREGISTER_FILES (3) restent exposés —
ce sont les désenregistrements des ressources enregistrées via les variantes
*2. EVENTFD_ASYNC (7) et EPOLL_CTL (op 29) restent (non obsolètes :
respectivement variante utile et seule intégration epoll en 6.12).
5. Inventaire exhaustif des 8 axes
Légende colonne « Décision » : R = retenu/exposé · O = obsolète, évacué
vers UNSUPPORTED.md · G = gated runtime (probe/feature, exposé mais
faillible).
5.1 Axe A — Setup flags IORING_SETUP_* (17 en 6.12)
Exposés comme bitflags SetupFlags, appliqués via IoUringBuilder.
| Flag | Bit | Décision | Symbole façade / note |
|---|---|---|---|
IOPOLL | 0 | R | SetupFlags::IOPOLL — polling I/O (stockage O_DIRECT). |
SQPOLL | 1 | R | SetupFlags::SQPOLL — thread kernel de poll de la SQ (Temps 3e). |
SQ_AFF | 2 | R | SetupFlags::SQ_AFF — affinité CPU du thread SQPOLL. |
CQSIZE | 3 | R | IoUringBuilder::cq_entries(). |
CLAMP | 4 | R | SetupFlags::CLAMP. |
ATTACH_WQ | 5 | R | IoUringBuilder::attach_work_queue(&IoUring) — partage du pool io-wq. |
R_DISABLED | 6 | R | primitive sandbox (Temps 3f, §3.3). |
SUBMIT_ALL | 7 | R | SetupFlags::SUBMIT_ALL. |
COOP_TASKRUN | 8 | R | SetupFlags::COOP_TASKRUN. |
TASKRUN_FLAG | 9 | R | SetupFlags::TASKRUN_FLAG. |
SQE128 | 10 | R | requis pour URING_CMD à payload large (Temps 2d, Temps 4). |
CQE32 | 11 | R | requis pour certaines complétions étendues. |
SINGLE_ISSUER | 12 | R | recommandé Air : reactor thread-per-core. |
DEFER_TASKRUN | 13 | R | recommandé Air (combiné SINGLE_ISSUER) : latence basse. |
NO_MMAP | 14 | R | mémoire des anneaux fournie par l’appelant. |
REGISTERED_FD_ONLY | 15 | R | ring fd uniquement enregistré (combine REG_REG_RING). |
NO_SQARRAY | 16 | R | supprime l’indirection du tableau d’index SQ. |
5.2 Axe B — Opérations IORING_OP_* (58 en 6.12, indices 0–57)
Colonne « Temps » = spec détaillée de destination (cf. §6).
| Opcode | # | Décision | Temps | Symbole façade |
|---|---|---|---|---|
NOP | 0 | R | 2c | submit_nop |
READV | 1 | R | 2a | submit_readv |
WRITEV | 2 | R | 2a | submit_writev |
FSYNC | 3 | R | 2a | submit_fsync |
READ_FIXED | 4 | R | 2a/3a | submit_read_fixed (buffer enregistré) |
WRITE_FIXED | 5 | R | 2a/3a | submit_write_fixed |
POLL_ADD | 6 | R | 2c | submit_poll_add (+ flags multi/level) |
POLL_REMOVE | 7 | R | 2c | submit_poll_remove |
SYNC_FILE_RANGE | 8 | R | 2a | submit_sync_file_range |
SENDMSG | 9 | R | 2b | submit_send_message |
RECVMSG | 10 | R | 2b | submit_receive_message |
TIMEOUT | 11 | R | 2c | submit_timeout |
TIMEOUT_REMOVE | 12 | R | 2c | submit_timeout_remove / update |
ACCEPT | 13 | R | 2b | submit_accept (+ multishot Temps 3d) |
ASYNC_CANCEL | 14 | R | 2c | submit_cancel |
LINK_TIMEOUT | 15 | R | 2c/3c | submit_link_timeout |
CONNECT | 16 | R | 2b | submit_connect |
FALLOCATE | 17 | R | 2a | submit_fallocate |
OPENAT | 18 | O | — | évacué → OPENAT2 (superset, OpenHow). |
CLOSE | 19 | R | 2a | submit_close |
FILES_UPDATE | 20 | R | 2c/3a | submit_files_update (op, distinct du register) |
STATX | 21 | R | 2a | submit_statx |
READ | 22 | R | 2a | submit_read |
WRITE | 23 | R | 2a | submit_write |
FADVISE | 24 | R | 2a | submit_fadvise |
MADVISE | 25 | R | 2a | submit_madvise |
SEND | 26 | R | 2b | submit_send |
RECV | 27 | R | 2b | submit_receive (+ multishot Temps 3d) |
OPENAT2 | 28 | R | 2a | submit_openat2 |
EPOLL_CTL | 29 | R | 2c | submit_epoll_ctl |
SPLICE | 30 | R | 2a | submit_splice |
PROVIDE_BUFFERS | 31 | O | — | évacué → PBUF_RING (reg 22). |
REMOVE_BUFFERS | 32 | O | — | évacué → UNREGISTER_PBUF_RING. |
TEE | 33 | R | 2a | submit_tee |
SHUTDOWN | 34 | R | 2b | submit_shutdown |
RENAMEAT | 35 | R | 2a | submit_renameat (renameat2 sémantique) |
UNLINKAT | 36 | R | 2a | submit_unlinkat |
MKDIRAT | 37 | R | 2a | submit_mkdirat |
SYMLINKAT | 38 | R | 2a | submit_symlinkat |
LINKAT | 39 | R | 2a | submit_linkat |
MSG_RING | 40 | R | 2c | submit_msg_ring (data / send_fd) |
FSETXATTR | 41 | R | 2a | submit_fsetxattr |
SETXATTR | 42 | R | 2a | submit_setxattr |
FGETXATTR | 43 | R | 2a | submit_fgetxattr |
GETXATTR | 44 | R | 2a | submit_getxattr |
SOCKET | 45 | R | 2b | submit_socket |
URING_CMD | 46 | R | 2d | submit_uring_cmd (+ socket cmds) |
SEND_ZC | 47 | R | 2b | submit_send_zero_copy (zero-copy, CQE NOTIF) |
SENDMSG_ZC | 48 | R | 2b | submit_send_message_zero_copy |
READ_MULTISHOT | 49 | R | 3d | submit_read_multishot |
WAITID | 50 | R | 2c | submit_waitid |
FUTEX_WAIT | 51 | R | 2c | submit_futex_wait |
FUTEX_WAKE | 52 | R | 2c | submit_futex_wake |
FUTEX_WAITV | 53 | R | 2c | submit_futex_waitv |
FIXED_FD_INSTALL | 54 | R | 2c/3a | submit_fixed_fd_install |
FTRUNCATE | 55 | R | 2a | submit_ftruncate |
BIND | 56 | R | 2b | submit_bind |
LISTEN | 57 | R | 2b | submit_listen |
Bilan opcodes : 55 retenus, 3 évacués (OPENAT, PROVIDE_BUFFERS,
REMOVE_BUFFERS).
5.3 Axe C — Register opcodes IORING_REGISTER_* (31 en 6.12, 0–30)
| Register op | # | Décision | Temps | Symbole façade |
|---|---|---|---|---|
REGISTER_BUFFERS | 0 | O | — | → BUFFERS2. |
UNREGISTER_BUFFERS | 1 | R | 3a | RegisteredBuffers::unregister |
REGISTER_FILES | 2 | O | — | → FILES2. |
UNREGISTER_FILES | 3 | R | 3a | FixedFdTable::unregister |
REGISTER_EVENTFD | 4 | R | 3a | IoUring::register_eventfd |
UNREGISTER_EVENTFD | 5 | R | 3a | IoUring::unregister_eventfd |
REGISTER_FILES_UPDATE | 6 | O | — | → FILES_UPDATE2. |
REGISTER_EVENTFD_ASYNC | 7 | R | 3a | register_eventfd_async |
REGISTER_PROBE | 8 | R | 1 | IoUring::probe / supports_op |
REGISTER_PERSONALITY | 9 | R | 3a | IoUring::register_personality |
UNREGISTER_PERSONALITY | 10 | R | 3a | unregister_personality |
REGISTER_RESTRICTIONS | 11 | R | 3f | IoUringBuilder::restrict (§3.3) |
REGISTER_ENABLE_RINGS | 12 | R | 3f | IoUringBuilder::enable |
REGISTER_FILES2 | 13 | R | 3a | FixedFdTable::register |
REGISTER_FILES_UPDATE2 | 14 | R | 3a | FixedFdTable::update |
REGISTER_BUFFERS2 | 15 | R | 3a | RegisteredBuffers::register |
REGISTER_BUFFERS_UPDATE | 16 | R | 3a | RegisteredBuffers::update |
REGISTER_IOWQ_AFF | 17 | R | 3a | IoUring::set_work_queue_affinity |
UNREGISTER_IOWQ_AFF | 18 | R | 3a | clear_work_queue_affinity |
REGISTER_IOWQ_MAX_WORKERS | 19 | R | 3a | set_work_queue_max_workers |
REGISTER_RING_FDS | 20 | R | 3a | IoUring::register_ring_fd |
UNREGISTER_RING_FDS | 21 | R | 3a | unregister_ring_fd |
REGISTER_PBUF_RING | 22 | R | 3b | ProvidedBufferRing::register |
UNREGISTER_PBUF_RING | 23 | R | 3b | ProvidedBufferRing::unregister |
REGISTER_SYNC_CANCEL | 24 | R | 1/2c | IoUring::sync_cancel (utilisé par S2) |
REGISTER_FILE_ALLOC_RANGE | 25 | R | 3a | FixedFdTable::set_alloc_range |
REGISTER_PBUF_STATUS | 26 | R | 3b | ProvidedBufferRing::status |
REGISTER_NAPI | 27 | R | 3a | IoUring::register_napi |
UNREGISTER_NAPI | 28 | R | 3a | unregister_napi |
REGISTER_CLOCK | 29 | R | 3a | IoUring::register_clock |
REGISTER_CLONE_BUFFERS | 30 | R | 3a | RegisteredBuffers::clone_from |
USE_REGISTERED_RING (flag 1<<31) | — | R | 1 | appliqué de façon transparente quand le ring fd est enregistré. |
Bilan register : 28 retenus, 3 évacués (REGISTER_BUFFERS,
REGISTER_FILES, REGISTER_FILES_UPDATE).
5.4 Axe D — Flags par SQE IOSQE_* (7)
| Flag | Décision | Symbole façade |
|---|---|---|
FIXED_FILE | R | implicite via les variantes « fixed » (FD enregistré). |
IO_DRAIN | R | SubmitOptions::drain(). |
IO_LINK | R | LinkedChainBuilder (Temps 3c). |
IO_HARDLINK | R | LinkedChainBuilder::add_hard_link. |
ASYNC | R | SubmitOptions::force_async(). |
BUFFER_SELECT | R | implicite via ProvidedBufferRing (Temps 3b). |
CQE_SKIP_SUCCESS | R | SubmitOptions::skip_cqe_on_success() (FEAT_CQE_SKIP). |
5.5 Axe E — Flags de CQE IORING_CQE_F_* (5 en 6.12)
| Flag | Décision | Exposition |
|---|---|---|
BUFFER | R | Completion::buffer_id() -> Option<u16>. |
MORE | R | Completion::has_more() (multishot, Temps 3d). |
SOCK_NONEMPTY | R | Completion::socket_has_pending_data(). |
NOTIF | R | Completion::is_notif() (zero-copy send, Temps 2b). |
BUF_MORE | R | consommation incrémentale (IOU_PBUF_RING_INC, Temps 3b). |
5.6 Axe F — Flags d’io_uring_enter (6 en 6.12)
Gérés en interne par la façade (pas exposés crûment) :
| Flag | Décision | Usage interne |
|---|---|---|
GETEVENTS | R | submit_and_wait, wait_completion. |
SQ_WAKEUP | R | réveil du thread SQPOLL (Temps 3e). |
SQ_WAIT | R | attente que la SQ se vide (SQPOLL). |
EXT_ARG | R | timeout d’attente (getevents_arg), wait_completion_timeout. |
REGISTERED_RING | R | appliqué si ring fd enregistré (transparent). |
ABS_TIMER | R | timeout absolu (FEAT_MIN_TIMEOUT). |
5.7 Axe G — Features IORING_FEAT_* (16 en 6.12)
Lues dans io_uring_params.features après setup, exposées via
IoUringCapabilities :
SINGLE_MMAP, NODROP, SUBMIT_STABLE, RW_CUR_POS, CUR_PERSONALITY,
FAST_POLL, POLL_32BITS, SQPOLL_NONFIXED, EXT_ARG, NATIVE_WORKERS,
RSRC_TAGS, CQE_SKIP, LINKED_FILE, REG_REG_RING, RECVSEND_BUNDLE,
MIN_TIMEOUT. → tous R, mappés sur des prédicats IoUringCapabilities::*.
5.8 Axe H — Protocole d’anneau brut (Temps 4)
Structures kernel exposées en #[repr(C)] dans ::raw, + flags d’anneau :
io_uring_sqe(64 o / 128 o),io_uring_cqe(16 o / 32 o).io_sqring_offsets,io_cqring_offsets,io_uring_params.- Flags d’anneau SQ :
SQ_NEED_WAKEUP,SQ_CQ_OVERFLOW,SQ_TASKRUN. - Flag d’anneau CQ :
CQ_EVENTFD_DISABLED. - Offsets mmap :
OFF_SQ_RING,OFF_CQ_RING,OFF_SQES,OFF_PBUF_RING. - Structures d’argument register :
io_uring_rsrc_register,io_uring_rsrc_update(2),io_uring_buf(_ring|_reg|_status),io_uring_napi,io_uring_clock_register,io_uring_clone_buffers,io_uring_sync_cancel_reg,io_uring_file_index_range,io_uring_probe(_op),io_uring_restriction,io_uring_getevents_arg,io_uring_recvmsg_out. - Enums :
io_uring_op,io_uring_register_op,io_uring_register_restriction_op,io_uring_msg_ring_flags,io_uring_socket_op,io_uring_register_pbuf_ring_flags,io_wq_type.
6. Découpage en Temps révisé
Le découpage d’ADR-022 est conservé et étendu (ajouts : 2d uring_cmd,
séparation 3b provided-buffers-ring, 3f restrictions/sandbox) :
| Temps | Sous-module | Périmètre | Symboles |
|---|---|---|---|
| 1 | (racine) | Cycle de vie du ring, setup flags, slab (S1), Drop/shutdown (S2), soumission/complétion, Completion, probe/capabilities. | ~20 |
| 2a | (racine) | Filesystem (read/write/sync/open/stat/dir/perm/xattr/splice/ftruncate…). | 26 |
| 2b | (racine) | Réseau (accept/connect/send/recv/msg/shutdown/socket/bind/listen + zero-copy send/sendmsg). | 12 |
| 2c | (racine) | Async-spécifiques (nop/timeout/cancel/poll/futex/waitid/msg_ring/epoll_ctl/files_update/fixed_fd_install). | 15 |
| 2d | ::cmd | URING_CMD : passthrough + commandes socket (SIOCINQ/OUTQ, get/setsockopt). | 3-5 |
| 3a | ::registration | Ressources fixes modernes : files2/buffers2/update, ring_fds, eventfd, personality, iowq aff/workers, napi, clock, clone_buffers, alloc_range. | ~25 |
| 3b | ::provided | Provided buffers ring-mapped (PBUF_RING/STATUS, incrémental). | 6-8 |
| 3c | ::linked | Chaînes liées (soft/hard link, link_timeout). | 5-7 |
| 3d | ::multishot | Multishot (accept/recv/poll/read_multishot). | 5-7 |
| 3e | ::shared | Multi-thread : LockedIoUring, RingPool, SqpollIoUring. | 10-12 |
| 3f | ::sandbox | Restrictions + R_DISABLED + ENABLE_RINGS (capability, §3.3). | 6-8 |
| 4 | ::raw | Protocole brut (SQE/CQE, anneaux, structs register). | 8-12 |
7. Squelette rustdoc du cœur (Temps 1) — à valider avant implémentation
Ce squelette est le contrat que Claude Code devra implémenter (corps =
todo!()). Lessubmit_*exhaustifs par opcode (Temps 2a–2d) et les sous-modules (3a–3f, 4) sont produits dans leurs specs dédiées sur le même modèle. Conventions couche 0 (ADR-021) appliquées :Result<_, Errno>, EINTR remonté,Option<T>au lieu de sentinelles, FD enOwnedFd/BorrowedFd, pas d’alloc heap dans le happy path (S1).
#![allow(unused)]
fn main() {
//! Module `air-sys-syscall::io_uring` — façade typée d'io_uring (cible 6.12).
//!
//! Niveau d'abstraction 2 (ADR-022, Décision 1) : soumission/complétion typée.
//! Le niveau 1 (anneaux bruts) vit dans [`raw`]. Les buffers suivent le modèle
//! de transfert d'ownership (ADR-022, Décision 3), garés dans un slab
//! pré-alloué (S1) ; le téardown est sûr par `Drop` quiescent + [`IoUring::shutdown`]
//! (S2).
use crate::Errno;
use core::num::NonZeroU32;
use std::os::fd::{BorrowedFd, OwnedFd};
// ---------------------------------------------------------------------------
// Construction & cycle de vie
// ---------------------------------------------------------------------------
/// Anneau io_uring. `Send` mais pas `Sync` (ADR-022, Décision 6) : un reactor
/// par thread. Pour le multi-thread, voir [`shared`].
pub struct IoUring {
/* fd: OwnedFd, mmaps SQ/CQ/SQE, slab d'ops en vol (S1), capabilities */
}
/// Construit un [`IoUring`], applique setup flags et restrictions (S3) avant
/// activation.
pub struct IoUringBuilder { /* ... */ }
impl IoUringBuilder {
/// Démarre un builder pour `entries` SQE (arrondi à la puissance de 2 sup.).
pub fn new(entries: NonZeroU32) -> Self { todo!() }
/// Taille explicite de la CQ (`SETUP_CQSIZE`).
pub fn with_completion_queue_entries(self, entries: NonZeroU32) -> Self { todo!() }
/// Active des flags de setup (cf. [`SetupFlags`]).
pub fn with_flags(self, flags: SetupFlags) -> Self { todo!() }
/// Partage le pool io-wq d'un ring existant (`SETUP_ATTACH_WQ`).
pub fn attach_work_queue(self, other: &IoUring) -> Self { todo!() }
/// Crée le ring désactivé (`R_DISABLED`) et applique des restrictions
/// (S3). Doit être suivi de [`IoUringBuilder::enable`].
pub fn restrict(self, restrictions: &[Restriction]) -> Self { todo!() }
/// Finalise : `io_uring_setup(2)`. Si `restrict` a été utilisé, le ring
/// est créé désactivé et doit être activé via [`IoUring::enable`].
pub fn build(self) -> Result<IoUring, Errno> { todo!() }
}
impl IoUring {
/// Raccourci : `IoUringBuilder::new(entries).build()`.
pub fn new(entries: NonZeroU32) -> Result<Self, Errno> { todo!() }
/// Active un ring créé avec `R_DISABLED` (`REGISTER_ENABLE_RINGS`).
pub fn enable(&mut self) -> Result<(), Errno> { todo!() }
/// Téardown propre (S2) : annule les ops en vol, draine, ferme le FD.
/// Préférer à un `drop` implicite sur chemin chaud.
pub fn shutdown(self) -> Result<(), Errno> { todo!() }
}
impl Drop for IoUring {
/// Filet de sécurité (S2) : si des ops sont en vol, quiesce (annule +
/// draine, **bloquant**) avant de libérer la mémoire. Coût assumé.
fn drop(&mut self) { todo!() }
}
// ---------------------------------------------------------------------------
// Soumission & complétion
// ---------------------------------------------------------------------------
impl IoUring {
/// Soumet les SQE en attente (`io_uring_enter`, sans attente). Retourne le
/// nombre soumis. EINTR remonté (ADR-021, conv. 2).
pub fn submit(&mut self) -> Result<u32, Errno> { todo!() }
/// Soumet puis attend au moins `want` complétions.
pub fn submit_and_wait(&mut self, want: u32) -> Result<u32, Errno> { todo!() }
/// Attend (bloquant) la prochaine complétion et la consomme.
pub fn wait_completion(&mut self) -> Result<Completion, Errno> { todo!() }
/// Attend une complétion avec délai maximal (`EXT_ARG`/`ABS_TIMER`).
pub fn wait_completion_timeout(
&mut self,
timeout: core::time::Duration,
) -> Result<Option<Completion>, Errno> { todo!() }
/// Récupère une complétion si disponible, sans bloquer.
pub fn try_completion(&mut self) -> Option<Completion> { todo!() }
/// Itère les complétions disponibles dans la CQ.
pub fn completions(&mut self) -> CompletionIter<'_> { todo!() }
/// Options par-opération (link, drain, async, skip-cqe) appliquées à la
/// prochaine soumission.
pub fn with(&mut self, opts: SubmitOptions) -> &mut Self { todo!() }
}
/// Jeton opaque reliant une soumission à sa complétion. Encapsule un index de
/// slot du slab + génération (compteur de génération anti-réutilisation) — *pas* un `user_data` brut (S1).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubmissionToken { /* slot: u32, gen: u32 */ }
/// Jeton d'une opération multishot (plusieurs complétions). Voir [`multishot`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MultishotToken { /* ... */ }
/// Options de soumission (flags `IOSQE_*` exposés sûrement).
#[derive(Debug, Clone, Copy, Default)]
pub struct SubmitOptions { /* ... */ }
impl SubmitOptions {
pub fn drain(self) -> Self { todo!() }
pub fn force_async(self) -> Self { todo!() }
pub fn skip_cqe_on_success(self) -> Self { todo!() }
}
// ---------------------------------------------------------------------------
// Complétion
// ---------------------------------------------------------------------------
/// Une complétion (CQE) typée. Méthodes d'interprétation selon l'op soumise
/// (ADR-022, Décision 9) ; en cas d'échec, retournent `Err(Errno)`.
pub struct Completion { /* user_data décodé, res, flags */ }
impl Completion {
/// Jeton de l'opération qui a produit cette complétion.
pub fn token(&self) -> SubmissionToken { todo!() }
/// Résultat brut du kernel (sémantique dépendante de l'op).
pub fn raw_result(&self) -> i32 { todo!() }
/// Flags de complétion (cf. [`CompletionFlags`]).
pub fn flags(&self) -> CompletionFlags { todo!() }
/// `IORING_CQE_F_MORE` : d'autres complétions suivront (multishot).
pub fn has_more(&self) -> bool { todo!() }
/// `IORING_CQE_F_NOTIF` : complétion de notification zero-copy.
pub fn is_notif(&self) -> bool { todo!() }
/// ID du buffer fourni consommé (`IORING_CQE_F_BUFFER`).
pub fn buffer_id(&self) -> Option<u16> { todo!() }
/// `IORING_CQE_F_SOCK_NONEMPTY` : données restantes après un recv.
pub fn socket_has_pending_data(&self) -> bool { todo!() }
// Interprétations typées (échantillon ; complétées par Temps 2a–2d) :
/// Octets lus + restitution du buffer transféré (S1).
pub fn into_read_result(self) -> Result<(Vec<u8>, usize), Errno> { todo!() }
/// Octets écrits + restitution du buffer.
pub fn into_write_result(self) -> Result<(Vec<u8>, usize), Errno> { todo!() }
/// FD accepté (`submit_accept`), CLOEXEC par défaut.
pub fn accepted_fd(self) -> Result<OwnedFd, Errno> { todo!() }
/// FD ouvert (`submit_openat2`).
pub fn opened_fd(self) -> Result<OwnedFd, Errno> { todo!() }
/// Succès sans valeur de retour (close, fsync…).
pub fn completed(&self) -> Result<(), Errno> { todo!() }
}
/// Itérateur sur les complétions disponibles.
pub struct CompletionIter<'ring> { /* ... */ }
// ---------------------------------------------------------------------------
// Capacités & introspection
// ---------------------------------------------------------------------------
impl IoUring {
/// Vrai si le kernel courant supporte `op` (`REGISTER_PROBE`, Décision 8).
pub fn supports_op(&self, op: IoUringOpcode) -> bool { todo!() }
/// Features négociées au setup (axe G).
pub fn capabilities(&self) -> IoUringCapabilities { todo!() }
/// Annulation synchrone globale ou ciblée (`REGISTER_SYNC_CANCEL`),
/// brique de [`IoUring::shutdown`] (S2).
pub fn sync_cancel(&mut self, target: CancelTarget) -> Result<u32, Errno> { todo!() }
}
/// Features io_uring du kernel courant (axe G). Prédicats stables.
#[derive(Debug, Clone, Copy)]
pub struct IoUringCapabilities { /* bits features */ }
impl IoUringCapabilities {
pub fn single_mmap(&self) -> bool { todo!() }
pub fn nodrop(&self) -> bool { todo!() }
pub fn cqe_skip(&self) -> bool { todo!() }
pub fn min_timeout(&self) -> bool { todo!() }
pub fn recvsend_bundle(&self) -> bool { todo!() }
pub fn reg_reg_ring(&self) -> bool { todo!() }
/* … un prédicat par feature de l'axe G … */
}
/// Énumération des opérations (axe B) pour le probe et les restrictions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum IoUringOpcode { Nop, Readv, Writev, /* … 55 variantes retenues … */ }
/// Cible d'annulation pour [`IoUring::sync_cancel`].
#[derive(Debug, Clone, Copy)]
pub enum CancelTarget {
Token(SubmissionToken),
Fd(BorrowedFd<'static>),
Op(IoUringOpcode),
Any,
}
// ---------------------------------------------------------------------------
// Bitflags & sandbox (Temps 3f, détaillé dans ::sandbox)
// ---------------------------------------------------------------------------
bitflags::bitflags! {
/// Flags de `io_uring_setup` (axe A, 17 flags en 6.12).
pub struct SetupFlags: u32 {
const IOPOLL = 1 << 0;
const SQPOLL = 1 << 1;
const SQ_AFF = 1 << 2;
const CLAMP = 1 << 4;
const R_DISABLED = 1 << 6;
const SUBMIT_ALL = 1 << 7;
const COOP_TASKRUN = 1 << 8;
const TASKRUN_FLAG = 1 << 9;
const SQE128 = 1 << 10;
const CQE32 = 1 << 11;
const SINGLE_ISSUER = 1 << 12;
const DEFER_TASKRUN = 1 << 13;
const NO_MMAP = 1 << 14;
const REGISTERED_FD_ONLY = 1 << 15;
const NO_SQARRAY = 1 << 16;
/* CQSIZE / ATTACH_WQ exposés via méthodes du builder */
}
/// Flags de complétion (axe E, 5 flags en 6.12).
pub struct CompletionFlags: u32 {
const BUFFER = 1 << 0;
const MORE = 1 << 1;
const SOCK_NONEMPTY = 1 << 2;
const NOTIF = 1 << 3;
const BUF_MORE = 1 << 4;
}
}
/// Une restriction appliquée avant activation (S3 ; `io_uring_restriction`).
#[derive(Debug, Clone, Copy)]
pub enum Restriction {
/// N'autoriser que cet opcode de soumission.
AllowOp(IoUringOpcode),
/// N'autoriser que ce register op.
AllowRegister(u8),
/// Flags SQE autorisés.
SqeFlagsAllowed(SubmitOptions),
/// Flags SQE requis sur chaque soumission.
SqeFlagsRequired(SubmitOptions),
}
}
8. Traits transverses pressentis (à arbitrer en Temps 1)
À valider : faut-il un trait commun aux opérations soumissibles pour factoriser linked/multishot, ou des méthodes inhérentes suffisent-elles (verbosité au service de la clarté, Principe 7) ?
trait Submittable— une opération qui peut être soumise (utilisée parLinkedChainBuilder). Candidat ; risque d’abstraction prématurée.trait FixedResource— ressource enregistrable (FD / buffer). Candidat.
Recommandation : ne pas introduire ces traits au Temps 1. Les ajouter seulement si les Temps 3a/3c en révèlent le besoin par répétition mesurée.
9. Travail à reprendre (specs détaillées par Temps)
Produire, au niveau de détail des autres familles (signature complète, syscall
sous-jacent, préconditions/# Safety, comportement, erreurs, perf, tests,
exemples), les fichiers : io-uring-1-core.md, io-uring-2a-filesystem.md,
io-uring-2b-network.md, io-uring-2c-async.md, io-uring-2d-cmd.md,
io-uring-3a-registration.md, io-uring-3b-provided.md,
io-uring-3c-linked.md, io-uring-3d-multishot.md, io-uring-3e-shared.md,
io-uring-3f-sandbox.md, io-uring-4-raw.md.
Puis : version anglaise de ce document maître.
Licence du document : MPL 2.0
Statut : Document maître d’inventaire du module air-sys-syscall::io_uring, cible kernel 6.12 LTS. À valider avant production des specs détaillées par Temps.