Spec couche 0 — Module io_uring, Temps 3b : buffers fournis (ring-mapped)
Spécification technique — Version 1.0 (cible kernel : Linux 6.12 LTS)
Position. Le Temps 3b spécifie les buffers fournis ring-mapped (
IORING_REGISTER_PBUF_RING), qui remplacent l’ancien mécanisme op-basedPROVIDE_BUFFERS/REMOVE_BUFFERS(évacué, doc maître §4). Sous-moduleair-sys-syscall::io_uring::provided. Réutilise le cœur du Temps 1 et la registration du Temps 3a.Périmètre : register opcodes
PBUF_RING(22),UNREGISTER_PBUF_RING(23),PBUF_STATUS(26) ; drapeau de soumissionIOSQE_BUFFER_SELECT; flags de complétionCQE_F_BUFFERetCQE_F_BUF_MORE.
1. Le problème résolu
Pour un recv classique, il faut pré-engager un buffer par opération — donc
par connexion en attente. Un serveur à 10 000 connexions oisives gaspille 10 000
buffers immobilisés. Les buffers fournis inversent le modèle :
L’application enregistre un groupe de buffers ; elle soumet un
recv/readavecIOSQE_BUFFER_SELECTsans buffer attaché ; le kernel choisit un buffer du groupe au moment où des données arrivent, et rend son identifiant dans le CQE (CQE_F_BUFFER, id dans les 16 bits hauts decqe->flags).
Les buffers ne sont consommés que par les connexions effectivement actives. Combiné au recv multishot (Temps 3d), un seul SQE sert un flux entier, chaque datagramme atterrissant dans un buffer choisi par le kernel.
2. Le groupe de buffers — ProvidedBufferRing
#![allow(unused)]
fn main() {
pub struct ProvidedBufferRing { /* bgid, mémoire de l'anneau, buffers backing */ }
pub struct ProvidedBufferRingOptions {
/// Mémoire de l'anneau allouée par le kernel (IOU_PBUF_RING_MMAP) puis
/// mmappée par la façade, au lieu d'être fournie par l'application.
pub kernel_mmap: bool,
/// Consommation incrémentale (IOU_PBUF_RING_INC) : un buffer peut servir
/// plusieurs complétions, consommé au fur et à mesure (cf. §5).
pub incremental: bool,
}
impl ProvidedBufferRing {
/// PBUF_RING (22). Enregistre un groupe `group_id` de `count` buffers de
/// `buf_size` octets (count = puissance de 2). `count` borne aussi
/// l'anneau de buffers.
pub fn register(ring: &mut IoUring, group_id: u16, count: NonZeroU16,
buf_size: NonZeroU32, opts: ProvidedBufferRingOptions)
-> Result<Self, Errno>;
/// UNREGISTER_PBUF_RING (23). Rend la mémoire des buffers.
pub fn unregister(self, ring: &mut IoUring) -> Result<(), Errno>;
/// PBUF_STATUS (26). Tête courante du groupe (diagnostic / régulation).
pub fn status(&self, ring: &IoUring) -> Result<ProvidedBufferRingStatus, Errno>;
pub fn group_id(&self) -> u16;
/// Nombre de buffers actuellement disponibles (non checkout).
pub fn available(&self) -> u16;
}
pub struct ProvidedBufferRingStatus { pub head: u32 }
}
- Deux modes mémoire : buffers fournis par l’application (par défaut, sur une
MmapRegionpossédée — data plane) ; ou anneau alloué par le kernel (kernel_mmap) puis mmappé àIORING_OFF_PBUF_RING | (group_id << IORING_OFF_PBUF_SHIFT). - Ownership :
ProvidedBufferRingpossède la mémoire des buffers tant que le groupe est enregistré ;unregisterla restitue. Lien téardown S2 : désenregistrement avant destruction du ring. - Réapprovisionnement : l’application rend les buffers consommés au groupe (avance la queue de l’anneau de buffers) — cf. §4.
3. Soumission avec sélection automatique
#![allow(unused)]
fn main() {
impl IoUring {
/// recv sur sélection auto de buffer dans `group` (IOSQE_BUFFER_SELECT).
/// `bundle` ⇒ IORING_RECVSEND_BUNDLE (plusieurs buffers contigus en un recv).
pub fn submit_receive_provided(&mut self, sock: BorrowedFd<'_>, group: &ProvidedBufferRing, flags: MessageFlags, bundle: bool)
-> Result<SubmissionToken, Errno>;
/// read sur sélection auto de buffer (`bundle` ⇒ IORING_RECVSEND_BUNDLE).
pub fn submit_read_provided(&mut self, fd: BorrowedFd<'_>, group: &ProvidedBufferRing, length: u32, offset: Option<u64>, bundle: bool)
-> Result<SubmissionToken, Errno>;
}
}
- Aucun buffer n’est attaché à la soumission : seul le
group_idest transmis, avecIOSQE_BUFFER_SELECT. Le slot S1 ne détient donc pas de buffer (le buffer appartient au groupe). - Variantes multishot (
recv_multishot/read_multishot) : Temps 3d — elles s’appuient sur ce même groupe. - Bundle (
IORING_RECVSEND_BUNDLE,FEAT_RECVSEND_BUNDLE) : un seul recv/send peut consommer plusieurs buffers contigus du groupe d’un coup (référencé en Temps 2b) — exposé via un paramètrebundle: boolici. - Pénurie : si le groupe est vide quand des données arrivent, la complétion
porte
-ENOBUFS; l’application doit réapprovisionner et resoumettre.
4. Consommation : le guard ProvidedBuffer
#![allow(unused)]
fn main() {
impl Completion {
/// Récupère le buffer choisi par le kernel pour cette complétion.
/// `None` si la complétion n'a pas utilisé de buffer fourni.
pub fn into_provided_buffer<'r>(self, group: &'r mut ProvidedBufferRing)
-> Option<ProvidedBuffer<'r>>;
}
/// Accès RAII aux données reçues dans un buffer fourni. Rend le buffer au
/// groupe (réapprovisionnement) à la libération du guard.
pub struct ProvidedBuffer<'r> { /* id, longueur utile, emprunt du groupe */ }
impl<'r> ProvidedBuffer<'r> {
pub fn id(&self) -> u16;
pub fn data(&self) -> &[u8]; // longueur = octets reçus (cqe->res)
pub fn data_mut(&mut self) -> &mut [u8];
}
impl Drop for ProvidedBuffer<'_> { /* réapprovisionne le groupe */ }
}
- Cycle checkout → traitement → return, rendu sûr par RAII : le kernel a
« sorti » un buffer (id dans le CQE) ;
into_provided_buffermatérialise un accès borné aux octets reçus ; la libération du guard rend le buffer au groupe (avance la queue de l’anneau). Oublier de rendre = buffer perdu pour le groupe → le guard le rend automatiquement. - L’emprunt
&'r mut ProvidedBufferRinggarantit qu’on ne désenregistre pas le groupe tant qu’un buffer est en cours de traitement.
5. Consommation incrémentale (incremental)
Avec ProvidedBufferRingOptions::incremental (IOU_PBUF_RING_INC) :
- Un gros buffer peut être consommé partiellement sur plusieurs
complétions. Le CQE porte alors
CQE_F_BUF_MORE(Completion::flags()), signalant que le même buffer recevra d’autres complétions et n’est donc pas rendu automatiquement. - Le guard
ProvidedBufferreflète cela : siCQE_F_BUF_MOREest positionné, sa libération ne réapprovisionne pas (le kernel continue d’utiliser le buffer) ; le réapprovisionnement n’a lieu qu’à la complétion finale (sansBUF_MORE). L’application et la façade suivent ensemble l’index de consommation (contrat documenté). - Intérêt : enregistrer de grandes plages (un seul gros
memfd) et ne consommer que ce qui est nécessaire par réception — économe en mémoire pour le data plane AirCom.
6. Récapitulatif
| # | Élément | Façade |
|---|---|---|
| reg 22 | PBUF_RING | ProvidedBufferRing::register |
| reg 23 | UNREGISTER_PBUF_RING | ProvidedBufferRing::unregister |
| reg 26 | PBUF_STATUS | ProvidedBufferRing::status |
| flag SQE | IOSQE_BUFFER_SELECT | submit_receive_provided / submit_read_provided |
| flag CQE | CQE_F_BUFFER | ProvidedBuffer::id (via into_provided_buffer) |
| flag CQE | CQE_F_BUF_MORE | consommation incrémentale (§5) |
Remplace IORING_OP_PROVIDE_BUFFERS (31) et REMOVE_BUFFERS (32),
évacués vers UNSUPPORTED.md.
7. Types ajoutés / partagés
Nouveaux : ProvidedBufferRing, ProvidedBufferRingOptions, ProvidedBufferRingStatus,
ProvidedBuffer<'r>. Nouvelle méthode de Completion : into_provided_buffer.
Partagés : MmapRegion (family-mem), MessageFlags (Temps 2b).
8. Stratégie de tests
- Intégration : enregistrer un groupe,
recv_providedsur un socket loopback, vérifier l’id de buffer rendu et les octets reçus, libérer le guard, vérifier le réapprovisionnement (available()remonte) ; pénurie de buffers (-ENOBUFS) puis réapprovisionnement ; modekernel_mmap; bundle multi-buffers ;status(). - Incrémental : gros buffer consommé sur plusieurs recv, séquence
CQE_F_BUF_MOREpuis complétion finale, réapprovisionnement uniquement à la fin. - Sûreté (compile-fail + Miri) : impossible de désenregistrer le groupe tant
qu’un
ProvidedBufferest vivant (emprunt mut) ; pas de double rendu d’un buffer ; pas de lecture hors desresoctets reçus. - Property-based : pour toute séquence checkout/return,
available ≤ count, aucun id rendu deux fois, aucun buffer perdu. - Couverture 100 % lignes + branches.
9. Décisions de fond émergées au Temps 3b
- Seul le mécanisme ring-mapped — l’op-based classique est abandonné ; l’API est plus simple et sans syscall par lot de buffers.
- Guard RAII
ProvidedBuffer— le réapprovisionnement est automatique à la libération ; on ne peut pas « oublier » de rendre un buffer. - Emprunt
&mutdu groupe pendant le traitement — interdit le désenregistrement sous les pieds d’un buffer en cours de lecture. - Incrémental traité explicitement —
CQE_F_BUF_MOREchange la sémantique de rendu du guard ; documenté et testé, pas laissé implicite. - Adossement
MmapRegion— les buffers fournis du data plane vivent sur un memfd partagé possédé (réutilise le handle de vivacité defamily-mem).
9 bis. Note d’implémentation — écarts kernel 6.12/6.17 (PR #42)
Implémentation : sous-module air-sys-syscall::io_uring::provided (PR #42,
mergée). La surface validée n’a pas été modifiée (le bloc §3 a été aligné sur
la prose : le paramètre bundle: bool, déjà décrit, est désormais visible dans
les signatures). Écarts kernel observés (jamais corrigés en silence — ADR-032) :
- Constantes 6.11/6.12 absentes des headers exécuteurs.
IOU_PBUF_RING_INC(2),IORING_RECVSEND_BUNDLE(1<<4deioprio) etIORING_CQE_F_BUF_MORE(1<<4; déjà porté parCompletionFlags) sont absents de<linux/io_uring.h>installé → valeurs reprises de l’uapi amont, validées au runtime (kernel 6.17). Consigné dansraw.rs(« Écart headers »). - Modèle
&mut groupdu guard.into_provided_buffer(self, &'r mut group)sérialise le traitement (unProvidedBuffervivant à la fois) ⇒available()n’est pas observable pendant un checkout. La décrémentation est vérifiée via le cheminmem::forget. Conforme à la surface, pas une divergence. - Accounting incrémental idempotent. En mode
IOU_PBUF_RING_INC, un même buffer produit plusieurs complétions (BUF_MORE) ;availableest décrémenté une seule fois au premier checkout et ré-incrémenté au rendu final (suivi par buffer,out_flags).
10. Travail à reprendre
Spec suivante : io-uring-3c-linked.md (chaînes d’opérations liées :
soft/hard link, LinkedChainBuilder, intégration du link_timeout du
Temps 2c). Traduction anglaise globale après validation des documents français.
Licence du document : MPL 2.0
Statut : Spécification technique du Temps 3b (buffers fournis ring-mapped) du module air-sys-syscall::io_uring, cible kernel 6.12 LTS.