====== std.io_uring — Asynchrones I/O (io_uring) ======
''import std.io_uring;''
io_uring ist der moderne Linux-Mechanismus für asynchrones I/O (Kernel 5.1+). SQEs (Submission Queue Entries) werden in einen Ring-Puffer geschrieben, den Kernel dann unabhängig abarbeitet. Abschlüsse erscheinen als CQEs (Completion Queue Entries) im CQ-Ring. Syscall-Overhead wird durch den gemeinsamen mmap-Speicher minimiert.
→ [[lyx_-_programmiersprache:units:net:epoll|std.net.epoll]] · [[lyx_-_programmiersprache:units:fs|std.fs]] · [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher]]
----
===== Konstanten =====
==== Opcode-Konstanten (SQE-Typen) ====
^ Konstante ^ Wert ^ Beschreibung ^
| ''IORING_OP_NOP'' | 0 | No-Op (Test) |
| ''IORING_OP_READV'' | 1 | Scatter-Read (iovec) |
| ''IORING_OP_WRITEV'' | 2 | Gather-Write (iovec) |
| ''IORING_OP_TIMEOUT'' | 11 | Timeout |
| ''IORING_OP_ACCEPT'' | 13 | accept4 |
| ''IORING_OP_CONNECT'' | 16 | connect |
| ''IORING_OP_READ'' | 22 | Einfacher Read |
| ''IORING_OP_WRITE'' | 23 | Einfacher Write |
| ''IORING_OP_SEND'' | 26 | send |
| ''IORING_OP_RECV'' | 27 | recv |
==== SQE-Feld-Offsets ====
^ Konstante ^ Offset ^ Typ ^ Bedeutung ^
| ''SQE_OPCODE'' | 0 | uint8 | Opcode (IORING_OP_*) |
| ''SQE_FLAGS'' | 1 | uint8 | SQE-Flags |
| ''SQE_FD'' | 4 | int32 | Dateideskriptor |
| ''SQE_OFF'' | 8 | uint64 | Datei-Offset |
| ''SQE_ADDR'' | 16 | uint64 | Puffer-Zeiger |
| ''SQE_LEN'' | 24 | uint32 | Pufferlänge |
| ''SQE_USER_DATA'' | 32 | uint64 | Frei wählbare Kennung |
==== CQE-Feld-Offsets ====
^ Konstante ^ Offset ^ Typ ^ Bedeutung ^
| ''CQE_USER_DATA'' | 0 | uint64 | Wie in der SQE gesetzt |
| ''CQE_RES'' | 8 | int32 | Ergebnis (negativ = Fehler-errno) |
----
===== Funktionen =====
==== Ring-Verwaltung ====
^ Funktion ^ Signatur ^ Beschreibung ^
| ''IoUringCreate'' | ''(entries: int64): int64'' | Initialisiert Ring mit mind. ''entries'' Slots; gibt Ring-Handle oder 0 zurück |
| ''IoUringFree'' | ''(ring: int64): int64'' | Gibt alle Ring-Ressourcen frei (munmap + close) |
| ''IoUringFd'' | ''(ring: int64): int64'' | Liefert den io_uring-Dateideskriptor |
| ''IoUringEnter'' | ''(fd: int64, toSubmit: int64, minComplete: int64, flags: int64): int64'' | Direkter ''sys_io_uring_enter''-Aufruf |
==== SQE-Verwaltung ====
^ Funktion ^ Signatur ^ Beschreibung ^
| ''IoUringGetSQE'' | ''(ring: int64): int64'' | Nächste freie SQE (auf Null gesetzt); 0 wenn Ring voll |
| ''IoUringSubmit'' | ''(ring: int64): int64'' | Veröffentlicht alle vorbereiteten SQEs |
| ''IoUringSubmitAndWait'' | ''(ring: int64, minComplete: int64): int64'' | Submit + blockiert bis ''minComplete'' CQEs fertig |
==== CQE-Verwaltung ====
^ Funktion ^ Signatur ^ Beschreibung ^
| ''IoUringPeekCQE'' | ''(ring: int64): int64'' | Nächste fertige CQE oder 0 wenn keine vorhanden |
| ''IoUringWaitCQE'' | ''(ring: int64): int64'' | Blockiert bis eine CQE verfügbar ist |
| ''IoUringCQESeen'' | ''(ring: int64, cqe: int64): int64'' | Markiert CQE als verarbeitet (Head +1) |
| ''IoUringCQEResult'' | ''(cqe: int64): int64'' | Liest Ergebnis aus CQE (negativ = Fehler-errno) |
| ''IoUringCQEUserData'' | ''(cqe: int64): int64'' | Liest user_data-Feld der CQE |
==== SQE-Setup-Helfer ====
^ Funktion ^ Beschreibung ^
| ''IoUringSQESetNop(sqe, userData)'' | No-Op konfigurieren (für Tests) |
| ''IoUringSQESetRead(sqe, fd, buf, len, offset, userData)'' | Asynchronen Read konfigurieren |
| ''IoUringSQESetWrite(sqe, fd, buf, len, offset, userData)'' | Asynchronen Write konfigurieren |
----
===== Verwendung =====
==== Grundprinzip: NOP-Test ====
import std.io_uring;
fn main(): int64 {
var ring: int64 := IoUringCreate(8);
if (ring = 0) { return -1; }
var sqe: int64 := IoUringGetSQE(ring);
if (sqe = 0) { IoUringFree(ring); return -1; }
IoUringSQESetNop(sqe, 42);
IoUringSubmit(ring);
var cqe: int64 := IoUringWaitCQE(ring);
var res: int64 := IoUringCQEResult(cqe);
var ud: int64 := IoUringCQEUserData(cqe);
IoUringCQESeen(ring, cqe);
IoUringFree(ring);
return 0;
}
==== Asynchroner File-Read ====
import std.io_uring;
import std.alloc;
import std.fs;
fn AsyncRead(path: pchar, outBuf: int64, bufLen: int64): int64 {
var fd: int64 := OpenFile(path, 0);
if (fd < 0) { return fd; }
var ring: int64 := IoUringCreate(4);
if (ring = 0) { CloseFile(fd); return -12; }
var sqe: int64 := IoUringGetSQE(ring);
IoUringSQESetRead(sqe, fd, outBuf, bufLen, 0, 1);
IoUringSubmit(ring);
var cqe: int64 := IoUringWaitCQE(ring);
var res: int64 := IoUringCQEResult(cqe);
IoUringCQESeen(ring, cqe);
IoUringFree(ring);
CloseFile(fd);
return res; // Anzahl gelesener Bytes oder negatives errno
}
==== Mehrere Operationen parallel ====
import std.io_uring;
import std.alloc;
fn MultiWrite(fd: int64, buf1: int64, len1: int64,
buf2: int64, len2: int64): int64 {
var ring: int64 := IoUringCreate(8);
if (ring = 0) { return -12; }
// Zwei Writes in den Ring stellen
var sqe1: int64 := IoUringGetSQE(ring);
IoUringSQESetWrite(sqe1, fd, buf1, len1, 0, 100);
var sqe2: int64 := IoUringGetSQE(ring);
IoUringSQESetWrite(sqe2, fd, buf2, len2, len1, 200);
// Beide abschicken, auf beide warten
IoUringSubmitAndWait(ring, 2);
var result: int64 := 0;
var i: int64 := 0;
while (i < 2) {
var cqe: int64 := IoUringPeekCQE(ring);
if (cqe != 0) {
var res: int64 := IoUringCQEResult(cqe);
if (res < 0) { result := res; }
IoUringCQESeen(ring, cqe);
}
i := i + 1;
}
IoUringFree(ring);
return result;
}
----
===== Hinweise =====
* **Kernel 5.1+**: io_uring ist auf älteren Kerneln nicht verfügbar. ''IoUringCreate'' gibt 0 zurück wenn ''sys_io_uring_setup'' fehlschlägt.
* **Ring-Größe**: Der Kernel rundet ''entries'' auf die nächste Zweierpotenz. Für einzelne Operationen reicht ''IoUringCreate(4)''.
* **CQE-Seen**: Nach dem Verarbeiten einer CQE **muss** ''IoUringCQESeen'' aufgerufen werden, sonst bleibt der Ring blockiert.
* **SQE-Reihenfolge**: Operationen werden asynchron ausgeführt; Reihenfolge ist nicht garantiert. Für sequentielle Abhängigkeiten: ''IOSQE_IO_LINK''-Flag setzen (nicht in diesem Wrapper implementiert).
* **SINGLE_MMAP**: Ab Kernel 5.4 teilen SQ und CQ eine gemeinsame mmap-Region (''IORING_FEAT_SINGLE_MMAP''). Der Wrapper erkennt und handhabt das automatisch.
* **offset -1 bei Sockets**: Für Sockets und Pipes muss ''offset'' auf ''-1'' gesetzt werden (aktuelle Position, kein seekbares Medium).
----
Letzte Aktualisierung: 2026-06-05