====== 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