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 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 # Safety exhaustif.


1. Quand (ne pas) utiliser le niveau brut

Cas d’usage légitimes :

  1. 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).
  2. Optimisations de pointe : batching agressif, agencement de SQE que l’API typée n’exprime pas.
  3. 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/CQE32 sont prises en compte : les accesseurs respectent la taille configurée du ring (un RawSubmissionQueueEntry de 64 o sur un ring non SQE128 ; la zone cmd[] de 80 o n’existe que sous SQE128).
  • 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
}
}

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_entry est sûr (lecture seule) ; raw_advance_completion_queue est sûr (la façade fait l’ordering), mais l’interprétation de res/flags est à 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 comme Completion (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 :

  1. Opcode valide et supporté par le kernel courant (vérifier via supports_op / probe).
  2. Cohérence des champs du SQE (fd, addr, len, flags) pour l’opcode.
  3. Validité mémoire : tout buffer pointé par addr/addr2/addr3 reste valide et non déplacé jusqu’à la complétion (le slab S1 ne le protège pas).
  4. Tag user_data (§5) positionné sur toute op brute.
  5. Pas de double consommation d’une complétion (raw_advance_completion_queue cohérent avec les raw_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/RawCompletionQueueEntry contre le header v6.12 (64/128 et 16/32 octets) ; échec de compilation si dérive.
  • Intégration : soumettre un NOP en brut (tag user_data), le voir revenir via raw_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_assert du tag manquant ; Miri sur raw_get_submission_queue_entry/avance (bornes des emplacements) ; fuzzing du décodage RawCompletionQueueEntry (données kernel externes, Principe 3).
  • Couverture : le niveau brut unsafe est testé via NOP et opcodes simples ; les branches non provoquables sont consignées dans COVERAGE-EXCEPTIONS.md avec justification.

9. Décisions de fond émergées au Temps 4

  1. Ordering toujours géré par la façade — même en brut, submit()/ raw_advance_completion_queue font les barrières release/acquire ; l’unsafe ne 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.
  2. Tag user_data (bit 63) — coexistence brut/niveau 2 sans collision sur le même ring ; le slab n’utilise jamais ce bit.
  3. 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.
  4. # Safety exhaustif et localisé — conforme aux conventions couche 0 ; chaque fonction unsafe documente ses préconditions.
  5. 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.