Spec couche 0 — Module io_uring, Temps 4 : accès brut (niveau 1)
Spécification technique — Version 1.0 (cible kernel : Linux 6.12 LTS)
Position. Le Temps 4 spécifie l’accès niveau 1 au protocole d’anneau : manipulation directe des SQE/CQE bruts, pour les cas que l’API typée (niveaux Temps 1–3f) ne couvre pas. Sous-module
air-sys-syscall::io_uring::raw. C’est la soupape de sécurité d’ADR-022 (Décision 1) : presque tout passe par le niveau 2 ; le niveau 1 reste disponible, encadré par un contrat# Safetyexhaustif.
1. Quand (ne pas) utiliser le niveau brut
Cas d’usage légitimes :
- Opérations non encore wrappées : un opcode kernel apparu après la façade (rappel : périmètre figé à 6.12 ; un opcode 6.13+ détecté par probe peut être piloté en brut en attendant son wrapper niveau 2).
- Optimisations de pointe : batching agressif, agencement de SQE que l’API typée n’exprime pas.
- Outils : debug, monitoring, inspection de l’état des anneaux.
À éviter sinon. Le niveau brut contourne le slab S1 (donc l’ownership sûr des buffers) et expose des champs où une erreur corrompt le ring. La règle Air : rester au niveau 2 sauf nécessité mesurée (Principe 5).
2. Types bruts
2.1 RawSubmissionQueueEntry / RawCompletionQueueEntry
#![allow(unused)]
fn main() {
/// Miroir exact de `struct io_uring_sqe` (64 octets ; 128 si SETUP_SQE128).
#[repr(C)]
pub struct RawSubmissionQueueEntry { /* opcode, flags, ioprio, fd, off/addr2, addr, len,
op_flags, user_data, buf_index, personality,
splice_fd_in/file_index, addr3/cmd… */ }
/// Miroir exact de `struct io_uring_cqe` (16 octets ; 32 si SETUP_CQE32).
#[repr(C)]
pub struct RawCompletionQueueEntry {
pub user_data: u64,
pub res: i32,
pub flags: u32,
// big_cqe[] (16 octets) si CQE32
}
/// Opcode brut (couvre aussi les opcodes hors `IoUringOpcode`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RawOpcode(pub u8);
}
#[repr(C)]et layout bit-pour-bit identique au header uapi v6.12 ; testé par assertions de taille/offset (statique) — toute dérive casse la compilation.- Les variantes
SQE128/CQE32sont prises en compte : les accesseurs respectent la taille configurée du ring (unRawSubmissionQueueEntryde 64 o sur un ring nonSQE128; la zonecmd[]de 80 o n’existe que sousSQE128). - Constructeurs/accesseurs typés pour remplir un SQE sans calcul d’offset
manuel :
RawSubmissionQueueEntry::nop(),.set_fd(),.set_addr(),.set_len(),.set_user_data(),.set_flags(), etc.
3. Soumission brute
#![allow(unused)]
fn main() {
impl IoUring {
/// Réserve un emplacement SQE libre et le rend pour remplissage manuel.
/// `None` si la SQ est pleine.
///
/// # Safety
/// L'appelant doit écrire un SQE **valide** : opcode supporté, `fd`/`addr`/
/// `len` cohérents, et **tout buffer référencé par `addr` doit rester
/// valide jusqu'à la complétion** (le slab S1 ne gère PAS ce buffer). Le
/// `user_data` doit respecter la règle de coexistence (§5).
pub unsafe fn raw_get_submission_queue_entry(&mut self) -> Option<&mut RawSubmissionQueueEntry>;
/// Capacité totale de la SQ (entries effectives).
pub fn submission_queue_capacity(&self) -> u32; // sûr
/// Emplacements SQ disponibles.
pub fn submission_queue_available(&self) -> u32; // sûr
/// Capacité totale de la CQ.
pub fn completion_queue_capacity(&self) -> u32; // sûr
}
}
-
- Publication =
submit()(Temps 1), qui reste sûr. L’appelant remplit un ou plusieursRawSubmissionQueueEntryviaraw_get_submission_queue_entry, puis appellesubmit()/submit_and_wait() - c’est la façade qui exécute le store release sur la queue (Temps 1 §3.2).
Le protocole d’ordering reste donc géré par la façade ; l’
unsafene porte que sur le contenu du SQE et la validité des buffers, pas sur le protocole d’anneau.
- Publication =
4. Complétion brute
#![allow(unused)]
fn main() {
impl IoUring {
/// Inspecte la prochaine complétion brute sans la consommer. `None` si la CQ
/// est vide. (Sûr : lecture seule via load acquire interne.)
pub fn raw_peek_completion_queue_entry(&self) -> Option<&RawCompletionQueueEntry>;
/// Consomme `n` complétions de la CQ (avance la tête, store release).
pub fn raw_advance_completion_queue(&mut self, n: u32);
}
}
- Lecture/avance bruts pour les outils de monitoring et les opérations soumises
en brut.
raw_peek_completion_queue_entryest sûr (lecture seule) ;raw_advance_completion_queueest sûr (la façade fait l’ordering), mais l’interprétation deres/flagsest à la charge de l’appelant (selon l’opcode qu’il a soumis).
5. Coexistence brut / niveau 2 : règle du user_data (essentiel)
Le slab S1 (Temps 1 §4.2) encode le user_data des opérations de niveau 2 sous
la forme (génération << 32) | slot, avec slot < capacité. Une opération brute
fixe librement son user_data — risque de collision avec l’encodage du
slab.
Règle gelée : une opération brute doit positionner le bit de poids fort
(RAW_USER_DATA_TAG = 1 << 63) de son user_data. La boucle de complétion de la
façade route alors :
user_data & RAW_USER_DATA_TAG == 0⇒ complétion gérée : décodée via le slab, livrée commeCompletion(Temps 1) ;user_data & RAW_USER_DATA_TAG != 0⇒ complétion brute : livrée telle quelle (RawCompletionQueueEntry) à l’appelant, sans toucher au slab.
#![allow(unused)]
fn main() {
pub const RAW_USER_DATA_TAG: u64 = 1 << 63;
}
Conséquence : le slab n’utilise jamais le bit 63 (slot+génération tiennent sur
63 bits — largement suffisant). Brut et niveau 2 coexistent sur le même ring
sans collision. La façade rejette (debug_assert + erreur) un raw_get_submission_queue_entry
dont le user_data n’a pas le tag, en build de test.
6. Contrat # Safety (récapitulatif)
L’appelant du niveau brut garantit :
- Opcode valide et supporté par le kernel courant (vérifier via
supports_op/ probe). - Cohérence des champs du SQE (
fd,addr,len, flags) pour l’opcode. - Validité mémoire : tout buffer pointé par
addr/addr2/addr3reste valide et non déplacé jusqu’à la complétion (le slab S1 ne le protège pas). - Tag
user_data(§5) positionné sur toute op brute. - Pas de double consommation d’une complétion (
raw_advance_completion_queuecohérent avec lesraw_peek_completion_queue_entry).
La façade garantit en retour : l’ordering d’anneau (publication/consommation release/acquire) et la non-corruption des structures internes (le brut ne donne pas accès aux pointeurs mmap nus, seulement aux SQE/CQE via emplacements bornés).
7. Types ajoutés / partagés
Nouveaux : RawSubmissionQueueEntry, RawCompletionQueueEntry, RawOpcode, constante RAW_USER_DATA_TAG. Réutilise
le ring et son ordering (Temps 1). Les structures d’argument register brutes
(io_uring_rsrc_register, io_uring_buf_reg, etc.) sont exposées en
#[repr(C)] ici pour les outils, mais leur usage normal passe par les types
typés des Temps 3a/3b.
8. Stratégie de tests
- Layout : assertions statiques de taille/offset de
RawSubmissionQueueEntry/RawCompletionQueueEntrycontre le header v6.12 (64/128 et 16/32 octets) ; échec de compilation si dérive. - Intégration : soumettre un
NOPen brut (tag user_data), le voir revenir viaraw_peek_completion_queue_entry/raw_advance_completion_queue; implémenter en brut une op simple et comparer au wrapper niveau 2 équivalent. - Coexistence : mélanger ops niveau 2 (slab) et ops brutes (taguées) sur le même ring ; vérifier le routage correct des complétions, aucune collision.
- Sûreté :
debug_assertdu tag manquant ; Miri surraw_get_submission_queue_entry/avance (bornes des emplacements) ; fuzzing du décodageRawCompletionQueueEntry(données kernel externes, Principe 3). - Couverture : le niveau brut
unsafeest testé via NOP et opcodes simples ; les branches non provoquables sont consignées dansCOVERAGE-EXCEPTIONS.mdavec justification.
9. Décisions de fond émergées au Temps 4
- Ordering toujours géré par la façade — même en brut,
submit()/raw_advance_completion_queuefont les barrières release/acquire ; l’unsafene porte que sur le contenu du SQE et la validité des buffers, pas sur le protocole d’anneau. On ne donne jamais accès aux têtes/queues nues. - Tag
user_data(bit 63) — coexistence brut/niveau 2 sans collision sur le même ring ; le slab n’utilise jamais ce bit. - Pas de pointeurs mmap nus exposés — le brut passe par des emplacements SQE/CQE bornés, pas par les anneaux nus ; réduit la surface d’erreur.
# Safetyexhaustif et localisé — conforme aux conventions couche 0 ; chaque fonctionunsafedocumente ses préconditions.- Soupape, pas voie royale — le niveau brut existe pour les 5 % de cas que le niveau 2 ne couvre pas ; la documentation décourage son usage par défaut.
10. Fin du module io_uring
Avec ce Temps 4, l’inventaire du document maître est entièrement couvert :
- Temps 1 (cœur) ; 2a (fs) ; 2b (réseau) ; 2c (async) ; 2d (uring_cmd) ; 3a (registration) ; 3b (buffers fournis) ; 3c (linked) ; 3d (multishot) ; 3e (multi-thread) ; 3f (confinement) ; 4 (raw).
Toutes les fonctionnalités io_uring de la cible 6.12 sont spécifiées en façade
Rust, hors interfaces obsolètes (évacuées vers UNSUPPORTED.md).
Travail à reprendre : traduction anglaise globale des documents du module
(après validation des versions françaises), puis confier l’implémentation des
corps (todo!()) à partir des squelettes rustdoc validés.
Licence du document : MPL 2.0
Statut : Spécification technique du Temps 4 (accès brut) du module air-sys-syscall::io_uring, cible kernel 6.12 LTS. Clôt la spécification du module.