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 2d : URING_CMD (passthrough)

Spécification technique — Version 1.0 (cible kernel : Linux 6.12 LTS)

Position. Le Temps 2d spécifie l’opération IORING_OP_URING_CMD (46), un sous-protocole de passthrough par lequel io_uring transmet une commande opaque au gestionnaire uring_cmd du sous-système propriétaire du fichier visé (socket, NVMe, ublk…). Sous-module dédié air-sys-syscall::io_uring::cmd. C’est l’opération la plus « brute » des Temps 2x : sa sémantique dépend du sous-système cible, pas d’io_uring. Réutilise le cœur du Temps 1.


1. Nature et contraintes

1.1 Le mécanisme

URING_CMD porte deux choses dans le SQE :

  • cmd_op (__u32) : l’opération propre au sous-système.
  • une zone de commande cmd[] : 16 octets dans un SQE standard (la zone addr3/padding), ou 80 octets si le ring est créé avec IORING_SETUP_SQE128.

Le contenu de cmd[] est opaque pour io_uring : c’est le gestionnaire f_op->uring_cmd du fichier qui l’interprète. Conséquence : la façade ne peut garantir la sûreté que pour les commandes qu’elle connaît typées (socket) ; le reste est un mécanisme générique encadré.

1.2 Conventions du Temps 2d

  • Détection SQE128 : si une commande nécessite plus de 16 octets et que le ring n’a pas SetupFlags::SQE128, la soumission échoue en amont (Err(EINVAL), Principe 4) — pas de troncature silencieuse.
  • Flag FIXED : IORING_URING_CMD_FIXED (bit 0) permet d’utiliser un buffer enregistré (index dans buf_index) comme zone de données associée à la commande. Seul flag disponible en 6.12.
  • Pas de multishot : IORING_URING_CMD_MULTISHOT est postérieur à 6.12 → hors périmètre (doc maître §1.2).

2. Commandes socket (couche typée sûre)

io_uring 6.12 expose, via URING_CMD sur un FD de socket, quatre commandes (enum io_uring_socket_op). La façade les enveloppe typées et sûres :

#![allow(unused)]
fn main() {
impl IoUring {
    /// SIOCINQ : octets disponibles en lecture. cmd_op = SOCKET_URING_OP_SIOCINQ (0).
    pub fn submit_socket_inq(&mut self, sock: BorrowedFd<'_>)
        -> Result<SubmissionToken, Errno>;
    /// SIOCOUTQ : octets en attente d'émission. cmd_op = SOCKET_URING_OP_SIOCOUTQ (1).
    pub fn submit_socket_outq(&mut self, sock: BorrowedFd<'_>)
        -> Result<SubmissionToken, Errno>;
    /// getsockopt. cmd_op = SOCKET_URING_OP_GETSOCKOPT (2).
    pub fn submit_getsockopt(&mut self, sock: BorrowedFd<'_>, level: SocketOptionLevel,
                             optname: i32, value: Vec<u8>)
        -> Result<SubmissionToken, Errno>;
    /// setsockopt. cmd_op = SOCKET_URING_OP_SETSOCKOPT (3).
    pub fn submit_setsockopt(&mut self, sock: BorrowedFd<'_>, level: SocketOptionLevel,
                             optname: i32, value: Vec<u8>)
        -> Result<SubmissionToken, Errno>;
}
}
  • inq/outq : complétion into_result() -> i32 (nombre d’octets). Équivalents : ioctl(SIOCINQ/SIOCOUTQ), mais dans le ring, sans ioctl synchrone — utile pour piloter le flux d’un socket dans un reactor.
  • getsockopt : value est le buffer de sortie (ownership transféré, S1) ; le SQE porte level/optname/optlen/optval. Complétion into_buffer_result() -> (Vec<u8>, usize) (valeur + longueur effective).
  • setsockopt : value (octets, Principe 3) déplacé dans le slot. Complétion completed().
  • SocketOptionLevel : type partagé avec family-net (SOL_SOCKET, IPPROTO_TCP…).
  • Erreurs : ENOPROTOOPT, EINVAL, ENOTSOCK, EFAULT.

Ces quatre commandes remplacent avantageusement les ioctl/getsockopt synchrones dans un chemin asynchrone, sans quitter le reactor.


3. Mécanisme générique (sous-systèmes arbitraires)

Pour les sous-systèmes que la façade ne connaît pas typés (NVMe passthrough, ublk, futurs gestionnaires), deux niveaux.

3.1 Niveau typé via trait UringCommand

#![allow(unused)]
fn main() {
/// Une commande passthrough d'un sous-système, sérialisable dans la zone cmd[].
///
/// # Safety
/// L'implémenteur garantit que `encode` produit une commande valide pour le
/// gestionnaire `uring_cmd` du `fd` ciblé, et que `cmd_op`/les buffers associés
/// respectent le contrat du sous-système.
pub unsafe trait UringCommand {
    /// Opération propre au sous-système (cmd_op).
    fn cmd_op(&self) -> u32;
    /// Taille requise de la zone de commande (≤ 16, ou ≤ 80 si SQE128).
    fn cmd_len(&self) -> usize;
    /// Sérialise la commande dans `out` (long. `cmd_len`).
    fn encode(&self, out: &mut [u8]);
    /// Interprétation typée de la complétion.
    type Output;
    fn interpret(&self, completion: &Completion) -> Result<Self::Output, Errno>;
}

impl IoUring {
    pub fn submit_uring_cmd<C: UringCommand>(&mut self, fd: BorrowedFd<'_>, cmd: C)
        -> Result<SubmissionToken, Errno>;
}
}
  • La façade vérifie cmd.cmd_len() ≤ 16 (ou ≤ 80 si SQE128), sinon Err(EINVAL). Le trait est unsafe car la validité de la commande dépend du sous-système, hors du contrôle d’io_uring.
  • Exemple de consommateur : un crate de stockage (couche supérieure) définit un NvmePassthroughCmd: UringCommand (struct ~72 octets ⇒ exige SQE128). Air couche 0 fournit le mécanisme, pas les structures NVMe spécifiques (elles vivent là où elles sont utilisées).

3.2 Niveau brut unsafe (échappatoire)

#![allow(unused)]
fn main() {
impl IoUring {
    /// # Safety
    /// `cmd_data` doit être une commande valide pour le gestionnaire uring_cmd
    /// du `fd`. Tout buffer référencé par la commande doit rester valide
    /// jusqu'à la complétion. `cmd_data.len()` ≤ 16 (ou ≤ 80 si SQE128).
    pub unsafe fn submit_uring_cmd_raw(&mut self, fd: BorrowedFd<'_>,
        cmd_op: u32, cmd_data: &[u8], flags: UringCmdFlags)
        -> Result<SubmissionToken, Errno>;
}
}
  • Dernier recours pour les sous-systèmes non encore typés. Documentation # Safety exhaustive (conv. couche 0). UringCmdFlags : FIXED.

4. Récapitulatif

# opcodeOpcodeFaçade (sûre)Façade (générique)
46URING_CMDsubmit_socket_inq/outq, submit_getsockopt/setsockoptsubmit_uring_cmd<C> / submit_uring_cmd_raw (unsafe)

cmd_op socket couverts (6.12) : SIOCINQ (0), SIOCOUTQ (1), GETSOCKOPT (2), SETSOCKOPT (3). TX_TIMESTAMP/GETSOCKNAME = post-6.12, hors périmètre.


5. Types ajoutés / partagés

Nouveaux : UringCmdFlags (bitflags, FIXED), trait UringCommand. Partagés : SocketOptionLevel (family-net). Méthode Completion réutilisée : into_result/into_buffer_result/completed.


6. Stratégie de tests

  • Intégration : inq/outq sur un socket loopback avec données connues en file ; getsockopt(SO_RCVBUF) / setsockopt(SO_REUSEADDR) round-trip ; comparaison avec les getsockopt/ioctl synchrones (family-net).
  • SQE128 : commande > 16 octets refusée sans SQE128 (EINVAL) ; acceptée avec.
  • Sûreté : Miri sur le chemin submit_uring_cmd<C> (sérialisation dans cmd[] bornée) ; test que cmd_len > capacité est refusé.
  • Fuzzing : décodage des complétions getsockopt (données kernel externes).
  • Couverture 100 % lignes + branches ; le submit_uring_cmd_raw unsafe testé via un faux gestionnaire en harnais, ou consigné en COVERAGE-EXCEPTIONS.md si non provoquable.

7. Décisions de fond émergées au Temps 2d

  1. Deux niveaux d’exposition : commandes socket typées sûres d’un côté ; trait UringCommand (unsafe trait) + forme brute unsafe pour les sous-systèmes arbitraires de l’autre. On ne prétend pas typer ce que le sous-système seul connaît.
  2. Détection SQE128 en amont — refus net plutôt que troncature silencieuse.
  3. Structures NVMe hors couche 0 — Air fournit le mécanisme passthrough ; les commandes spécifiques (NVMe, ublk) sont définies par leurs consommateurs.
  4. Pas de multishot uring_cmd — postérieur à 6.12.

8. Travail à reprendre

Spec suivante : io-uring-3a-registration.md (ressources fixes modernes : FixedFdTable, RegisteredBuffers, ring fd enregistré, eventfd, personality, affinités io-wq, napi, clock, clone_buffers, alloc_range). Traduction anglaise globale après validation des documents français.


Licence du document : MPL 2.0 Statut : Spécification technique du Temps 2d (URING_CMD) du module air-sys-syscall::io_uring, cible kernel 6.12 LTS.