#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_registered_ring_fd"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — registered ring fd (`IORING_REGISTER_RING_FDS`, Linux 5.18).
*
* Every `io_uring_enter(2)` normally takes the ring's file descriptor as its
* first argument, so the kernel must do an `fdget()` to translate that integer
* into the backing `struct file` and a matching `fdput()` afterwards — once per
* syscall. For submit-heavy, syscall-per-batch workloads that fdget/fdput pair
* is pure overhead. `IORING_REGISTER_RING_FDS` lets userspace register the ring
* fd once and thereafter pass a small *registered index* plus the
* `IORING_ENTER_REGISTERED_RING` flag, so the kernel skips the fd lookup
* entirely on the hot path.
*
* `during` wires this up transparently: after `registerRingFd()` succeeds it
* passes the registered index + `ENTER_REGISTERED_RING` on every subsequent
* `submit`/`wait`. We prove the registered ring is functional by running a full
* NOP submit/complete cycle through it, then revert with `unregisterRingFd()`.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md
* § "5.18 — Ring-fd registration, msg-ring, linked-file".
*
* Run with: `dub run --single registered-ring-fd.d`
*
* Portability: if io_uring is unavailable, or the kernel predates 5.18 (the
* register call returns `-EINVAL`/`-EOPNOTSUPP`/`-ENOSYS`), the program prints a
* `SKIP:` line and exits 0 so it stays green regardless of the host kernel.
*/
module (module) io_uring_registered_ring_fdio_uring — registered ring fd (IORING_REGISTER_RING_FDS, Linux 5.18).
Every io_uring_enter(2) normally takes the ring's file descriptor as its
first argument, so the kernel must do an fdget() to translate that integer
into the backing struct file and a matching fdput() afterwards — once per
syscall. For submit-heavy, syscall-per-batch workloads that fdget/fdput pair
is pure overhead. IORING_REGISTER_RING_FDS lets userspace register the ring
fd once and thereafter pass a small registered index plus the
IORING_ENTER_REGISTERED_RING flag, so the kernel skips the fd lookup
entirely on the hot path.
during wires this up transparently: after registerRingFd() succeeds it
passes the registered index + ENTER_REGISTERED_RING on every subsequent
submit/wait. We prove the registered ring is functional by running a full
NOP submit/complete cycle through it, then revert with unregisterRingFd().
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md
§ "5.18 — Ring-fd registration, msg-ring, linked-file".
Run with: dub run --single registered-ring-fd.d
Portability
if io_uring is unavailable, or the kernel predates 5.18 (the
register call returns -EINVAL/-EOPNOTSUPP/-ENOSYS), the program prints a
SKIP: line and exits 0 so it stays green regardless of the host kernel.
io_uring_registered_ring_fd;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.errnoD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/errno.h.html, errno.h
Source
core/stdc/errno.d
errno : (alias constant) io_uring_registered_ring_fd.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL, (alias constant) io_uring_registered_ring_fd.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP, (alias constant) io_uring_registered_ring_fd.ENOSYS = int core.stdc.errno.ENOSYS = 38ENOSYS;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) io_uring_registered_ring_fd.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
int int D main()main()
{
enum ulong (constant) ulong io_uring_registered_ring_fd.main.cookie = 1367404557LUcookie = 0x5180_F00D; // 5.18 marker, just a recognizable user_data
(struct) during.UringMain entry point to work with io_uring.
It hides SubmissionQueue and CompletionQueue behind standard range interface.
We put in SubmissionEntry entries and take out CompletionEntry entries.
Use predefined prepXX methods to fill required fields of SubmissionEntry before put or during putWith.
Note
prepXX functions doesn't touch previous entry state, just fills in operation properties. This is because for
less error prone interface it is cleared automatically when prepared using putWith. So when using on own SubmissionEntry
(outside submission queue), that would be added to the submission queue using put, be sure its cleared if it's
reused for multiple operations.
Uring (local variable) during.Uring ioio;
const (local variable) const(int) setupRetsetupRet = (local variable) during.Uring ioio.int during.setup(ref during.Uring uring, uint entries = 128u, during.io_uring.SetupFlags flags = SetupFlags.NONE) nothrow @nogc @safeSetup new instance of io_uring into provided Uring structure.
setup(8);
if ((local variable) const(int) setupRetsetupRet < 0)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: io_uring_setup failed (errno %d) — io_uring unavailable on this host", -(local variable) const(int) setupRetsetupRet);
return 0;
}
// Register the ring's own fd. On success `during` swaps in the registered
// index + ENTER_REGISTERED_RING for all later io_uring_enter(2) calls.
const (local variable) const(int) regRetregRet = (local variable) during.Uring ioio.int during.Uring.registerRingFd() nothrow @nogc @trustedRegisters the ring's own file descriptor with the kernel (IORING_REGISTER_RING_FDS).
Once registered, during transparently passes the registered index plus
EnterFlags.ENTER_REGISTERED_RING to every io_uring_enter(2), avoiding the fdget/fdput
cost on each call. Mirrors liburing's io_uring_register_ring_fd.
registerRingFd();
if ((local variable) const(int) regRetregRet == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (local variable) const(int) regRetregRet == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP || (local variable) const(int) regRetregRet == -(constant) int core.stdc.errno.ENOSYS = 38ENOSYS)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: IORING_REGISTER_RING_FDS unsupported (errno %d) — needs Linux 5.18+", -(local variable) const(int) regRetregRet);
return 0;
}
if ((local variable) const(int) regRetregRet < 0)
{
// Any other negative result is a genuine, unexpected failure.
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("registerRingFd failed: errno %d", -(local variable) const(int) regRetregRet);
return 1;
}
// From here on, every submit/wait reaches the kernel via the *registered*
// ring index — no per-syscall fdget/fdput. Drive a NOP through it to prove
// the registered ring still submits and completes correctly.
(local variable) during.Uring ioio.putWith!((ref SubmissionEntry e) {
e.prepNop();
e.user_data = cookie;
})();
const (local variable) const(int) submittedsubmitted = (local variable) during.Uring ioio.int during.Uring.submit(uint want) nothrow @nogc @safeSubmits qued SubmissionEntry to be processed by kernel.
submit(1); // uses the registered ring fd under the hood
if ((local variable) const(int) submittedsubmitted < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit (via registered ring) failed: errno %d", -(local variable) const(int) submittedsubmitted);
return 1;
}
(local variable) during.Uring ioio.int during.Uring.wait(uint want = 1u) nothrow @nogcSimmilar to submit but with this method we just wait for required number
of CompletionEntries.
wait(1);
const (local variable) const(int) resres = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) int during.io_uring.CompletionEntry.resresult code for this event
res;
const (local variable) const(ulong) echoedechoed = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) ulong during.io_uring.CompletionEntry.user_datasqe->data submission passed back
user_data;
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("NOP via registered ring completed with error: errno %d", -(local variable) const(int) resres);
return 1;
}
if ((local variable) const(ulong) echoedechoed != (constant) ulong io_uring_registered_ring_fd.main.cookie = 1367404557LUcookie)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("user_data mismatch: expected 0x%X, got 0x%X", (constant) ulong io_uring_registered_ring_fd.main.cookie = 1367404557LUcookie, (local variable) const(ulong) echoedechoed);
return 1;
}
// Revert to using the real ring fd again. Mirrors liburing's
// io_uring_unregister_ring_fd; -EINVAL here would mean "wasn't registered".
const (local variable) const(int) unregRetunregRet = (local variable) during.Uring ioio.int during.Uring.unregisterRingFd() nothrow @nogc @trustedReverts registerRingFd — subsequent io_uring_enter(2) calls use the real ring fd again.
Mirrors liburing's io_uring_unregister_ring_fd.
unregisterRingFd();
if ((local variable) const(int) unregRetunregRet < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("unregisterRingFd failed: errno %d", -(local variable) const(int) unregRetunregRet);
return 1;
}
void std.stdio.writefln!(char, const(int), const(ulong))(in char[] fmt, const(int) __param_1, const(ulong) __param_2) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: registered ring fd, ran a NOP through it (res=%d, user_data 0x%X), then unregistered — io_uring_enter skips per-call fdget/fdput",
(local variable) const(int) resres, (local variable) const(ulong) echoedechoed);
return 0;
}