#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_read_write_fixed"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — registered (fixed) buffers with `IORING_OP_WRITE_FIXED` /
* `IORING_OP_READ_FIXED` (Linux 5.1).
*
* Fixed buffers shipped in the original 5.1 introduction alongside `NOP` and the
* vectored read/write ops. The idea: register a fixed set of user buffers with the
* kernel **once** (`io_uring_register(IORING_REGISTER_BUFFERS)`), so the kernel can
* pin and map their pages up front. Subsequent `*_FIXED` ops then refer to a buffer
* by **index** instead of an address+length, letting the kernel skip the per-I/O
* `get_user_pages` / page-pinning dance — the headline zero-overhead-mapping win.
*
* This example:
* 1. opens a throwaway file under `/tmp` (`O_CREAT|O_RDWR|O_TRUNC`),
* 2. registers ONE buffer with `io.registerBuffers(buf[])`,
* 3. `WRITE_FIXED`s a known payload from buffer index 0 at offset 0,
* 4. clears the buffer region, then `READ_FIXED`s it back into index 0,
* 5. asserts the bytes round-trip.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md § "5.1 — The introduction".
*
* Run with: `dub run --single read-write-fixed.d`
*
* Portability: fixed buffers are part of the 5.1 baseline, so no feature probe is
* needed — but if the running kernel has no `io_uring` at all (too old, or blocked
* by a seccomp/container policy), `setup` fails and we print a `SKIP:` line and exit
* 0 so the example stays green in CI regardless of host kernel.
*/
module (module) io_uring_read_write_fixedio_uring — registered (fixed) buffers with IORING_OP_WRITE_FIXED /
IORING_OP_READ_FIXED (Linux 5.1).
Fixed buffers shipped in the original 5.1 introduction alongside NOP and the
vectored read/write ops. The idea: register a fixed set of user buffers with the
kernel once (io_uring_register(IORING_REGISTER_BUFFERS)), so the kernel can
pin and map their pages up front. Subsequent *_FIXED ops then refer to a buffer
by index instead of an address+length, letting the kernel skip the per-I/O
get_user_pages / page-pinning dance — the headline zero-overhead-mapping win.
This example:
opens a throwaway file under /tmp (O_CREAT|O_RDWR|O_TRUNC),
registers ONE buffer with io.registerBuffers(buf[]),
WRITE_FIXEDs a known payload from buffer index 0 at offset 0,
clears the buffer region, then READ_FIXEDs it back into index 0,
asserts the bytes round-trip.
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md § "5.1 — The introduction".
Run with: dub run --single read-write-fixed.d
Portability
fixed buffers are part of the 5.1 baseline, so no feature probe is
needed — but if the running kernel has no io_uring at all (too old, or blocked
by a seccomp/container policy), setup fails and we print a SKIP: line and exit
0 so the example stays green in CI regardless of host kernel.
io_uring_read_write_fixed;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
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_read_write_fixed.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdlibD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h
Source
core/stdc/stdlib.d
stdlib : (alias) io_uring_read_write_fixed.free = void core.stdc.stdlib.free(void* ptr) nothrow @nogcfree, (alias) io_uring_read_write_fixed.malloc = void* core.stdc.stdlib.malloc(ulong size) nothrow @nogcmalloc;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.errnoD header file for GNU/Linux
errno : (alias constant) io_uring_read_write_fixed.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL, (alias constant) io_uring_read_write_fixed.ENOSYS = int core.stdc.errno.ENOSYS = 38ENOSYS, (alias constant) io_uring_read_write_fixed.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.fcntlfcntl : (alias constant) io_uring_read_write_fixed.O_CREAT = int core.sys.posix.fcntl.O_CREAT = 64O_CREAT, (alias constant) io_uring_read_write_fixed.O_RDWR = int core.sys.posix.fcntl.O_RDWR = 2O_RDWR, (alias constant) io_uring_read_write_fixed.O_TRUNC = int core.sys.posix.fcntl.O_TRUNC = 512O_TRUNC, open;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.unistdD header file for POSIX.
unistd : (alias) io_uring_read_write_fixed.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose, (alias) io_uring_read_write_fixed.unlink = int core.sys.posix.unistd.unlink(scope const(char*)) nothrow @nogcunlink;
int int D main()main()
{
enum (alias) object.string = stringstring (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload = "io_uring fixed buffers, since Linux 5.1";
(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;
}
// A throwaway file we own; unlinked on exit. The trailing NUL keeps it a valid
// C string for the libc open()/unlink() calls.
static immutable char[] (immutable global) immutable(string) io_uring_read_write_fixed.main.pathpath = "/tmp/io_uring_rw_fixed_example.tmp\0";
const (local variable) const(int) fdfd = open(&(immutable global) immutable(string) io_uring_read_write_fixed.main.pathpath[0], (constant) int core.sys.posix.fcntl.O_CREAT = 64O_CREAT | (constant) int core.sys.posix.fcntl.O_RDWR = 2O_RDWR | (constant) int core.sys.posix.fcntl.O_TRUNC = 512O_TRUNC, (template instance) io_uring_read_write_fixed.octal!"600"octal!"600");
if ((local variable) const(int) fdfd < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("open(%s) failed", (immutable global) immutable(string) io_uring_read_write_fixed.main.pathpath[0 .. $ - 1]);
return 1;
}
scope (exit)
{
int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) const(int) fdfd);
int core.sys.posix.unistd.unlink(scope const(char*)) nothrow @nogcunlink(&(immutable global) immutable(string) io_uring_read_write_fixed.main.pathpath[0]);
}
// Register a single fixed buffer. `registerBuffers` pins the pages and maps them
// into the kernel once; *_FIXED ops below reference this region by index 0.
enum (alias) object.size_t = ulongsize_t (constant) ulong io_uring_read_write_fixed.main.bufLen = 4096LUbufLen = 4096;
auto (local variable) ubyte* bpbp = cast(ubyte*) void* core.stdc.stdlib.malloc(ulong size) nothrow @nogcmalloc((constant) ulong io_uring_read_write_fixed.main.bufLen = 4096LUbufLen);
if ((local variable) ubyte* bpbp is null)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("malloc(%d) failed", (constant) ulong io_uring_read_write_fixed.main.bufLen = 4096LUbufLen);
return 1;
}
scope (exit) void core.stdc.stdlib.free(void* ptr) nothrow @nogcfree((local variable) ubyte* bpbp);
ubyte[] (local variable) ubyte[] bufferbuffer = (local variable) ubyte* bpbp[0 .. (constant) ulong io_uring_read_write_fixed.main.bufLen = 4096LUbufLen];
const (local variable) const(int) regRetregRet = (local variable) during.Uring ioio.int during.Uring.registerBuffers!(ubyte[])(ubyte[] buffers) nothrow @nogc @systemRegister single buffer to be mapped into the kernel for faster buffered operations.
To use the buffers, the application must specify the fixed variants for of operations,
READ_FIXED or WRITE_FIXED in the SubmissionEntry also with used buf_index set
in entry extra data.
An application can increase or decrease the size or number of registered buffers by first
unregistering the existing buffers, and then issuing a new call to io_uring_register() with
the new buffers.
registerBuffers((local variable) ubyte[] bufferbuffer);
if ((local variable) const(int) regRetregRet < 0)
{
// Registration itself is a 5.1 baseline feature; an error here is unexpected.
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("registerBuffers failed: errno %d", -(local variable) const(int) regRetregRet);
return 1;
}
scope (exit) (local variable) during.Uring ioio.int during.Uring.unregisterBuffers() nothrow @nogc @trustedReleases all previously registered buffers associated with the io_uring instance.
An application need not unregister buffers explicitly before shutting down the io_uring instance.
unregisterBuffers();
// Stage the payload into the registered region and WRITE_FIXED it to the file.
// prepWriteFixed(e, fd, offset, slice-of-the-registered-buffer, bufferIndex).
(local variable) ubyte[] bufferbuffer[0 .. (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength] = cast(const(ubyte)[]) (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload;
(local variable) during.Uring ioio.putWith!(
(ref SubmissionEntry e, int f, ubyte[] b)
{
e.prepWriteFixed(f, 0, b, 0); // bufferIndex 0, file offset 0
e.user_data = 1;
})(during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, int f, ubyte[] b) nothrow @nogc @safe
{
prepWriteFixed(e, f, 0L, b, cast(ushort)0u);
e.user_data = 1LU;
}
, const(int), ubyte[])(ref const(int) __param_0, ubyte[] __param_1) nothrow @nogc return ref @safeAdds new entry to the SubmissionQueue.
Note that this just adds entry to the queue and doesn't advance the tail
marker kernel sees. For that finishSq() is needed to be called next.
Also note that to actually enter new entries to kernel,
it's needed to call submit().
fd, during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, int f, ubyte[] b) nothrow @nogc @safe
{
prepWriteFixed(e, f, 0L, b, cast(ushort)0u);
e.user_data = 1LU;
}
, const(int), ubyte[])(ref const(int) __param_0, ubyte[] __param_1) nothrow @nogc return ref @safeAdds new entry to the SubmissionQueue.
Note that this just adds entry to the queue and doesn't advance the tail
marker kernel sees. For that finishSq() is needed to be called next.
Also note that to actually enter new entries to kernel,
it's needed to call submit().
buffer[0 .. (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength]);
auto (local variable) 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);
if ((local variable) int submittedsubmitted < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit (write) failed: errno %d", -(local variable) 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);
auto (local variable) int wreswres = (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;
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
// *_FIXED can report -EINVAL/-EOPNOTSUPP if fixed buffers are unavailable in this
// environment (e.g. a restrictive sandbox) — treat that as an honest SKIP.
if ((local variable) int wreswres == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (local variable) int wreswres == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP || (local variable) int wreswres == -(constant) int core.stdc.errno.ENOSYS = 38ENOSYS)
{
void std.stdio.writefln!(char, int)(in char[] fmt, int __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: WRITE_FIXED unsupported here (errno %d)", -(local variable) int wreswres);
return 0;
}
if ((local variable) int wreswres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("WRITE_FIXED failed: errno %d", -(local variable) int wreswres);
return 1;
}
if ((local variable) int wreswres != cast(int) (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("WRITE_FIXED short write: wrote %d of %d bytes", (local variable) int wreswres, (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength);
return 1;
}
// Clear the registered region so the read genuinely round-trips through the file,
// not through stale buffer contents.
(local variable) ubyte[] bufferbuffer[0 .. (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength] = 0;
(local variable) during.Uring ioio.putWith!(
(ref SubmissionEntry e, int f, ubyte[] b)
{
e.prepReadFixed(f, 0, b, 0); // bufferIndex 0, file offset 0
e.user_data = 2;
})(during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, int f, ubyte[] b) nothrow @nogc @safe
{
prepReadFixed(e, f, 0L, b, cast(ushort)0u);
e.user_data = 2LU;
}
, const(int), ubyte[])(ref const(int) __param_0, ubyte[] __param_1) nothrow @nogc return ref @safeAdds new entry to the SubmissionQueue.
Note that this just adds entry to the queue and doesn't advance the tail
marker kernel sees. For that finishSq() is needed to be called next.
Also note that to actually enter new entries to kernel,
it's needed to call submit().
fd, during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, int f, ubyte[] b) nothrow @nogc @safe
{
prepReadFixed(e, f, 0L, b, cast(ushort)0u);
e.user_data = 2LU;
}
, const(int), ubyte[])(ref const(int) __param_0, ubyte[] __param_1) nothrow @nogc return ref @safeAdds new entry to the SubmissionQueue.
Note that this just adds entry to the queue and doesn't advance the tail
marker kernel sees. For that finishSq() is needed to be called next.
Also note that to actually enter new entries to kernel,
it's needed to call submit().
buffer[0 .. (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength]);
(local variable) 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);
if ((local variable) int submittedsubmitted < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit (read) failed: errno %d", -(local variable) 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);
auto (local variable) int rresrres = (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;
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
if ((local variable) int rresrres == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (local variable) int rresrres == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP || (local variable) int rresrres == -(constant) int core.stdc.errno.ENOSYS = 38ENOSYS)
{
void std.stdio.writefln!(char, int)(in char[] fmt, int __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: READ_FIXED unsupported here (errno %d)", -(local variable) int rresrres);
return 0;
}
if ((local variable) int rresrres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("READ_FIXED failed: errno %d", -(local variable) int rresrres);
return 1;
}
if ((local variable) int rresrres != cast(int) (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("READ_FIXED short read: read %d of %d bytes", (local variable) int rresrres, (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength);
return 1;
}
if (cast(const(char)[]) (local variable) ubyte[] bufferbuffer[0 .. (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength] != (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("round-trip mismatch: got %s", cast(const(char)[]) (local variable) ubyte[] bufferbuffer[0 .. (constant) string io_uring_read_write_fixed.main.payload = "io_uring fixed buffers, since Linux 5.1"payload.(constant) ulong "io_uring fixed buffers, since Linux 5.1".length = 39LUlength]);
return 1;
}
void std.stdio.writefln!(char, int)(in char[] fmt, int __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln(
"ok: WRITE_FIXED then READ_FIXED round-tripped %d bytes through a registered buffer (index 0)",
(local variable) int rresrres);
return 0;
}
// `octal!"600"` — compile-time octal literal for the file mode, avoiding a leading-0
// literal (deprecated in D) while keeping the intent obvious.
private template (template) io_uring_read_write_fixed.octal(string s)octal(string s)
{
enum uint (constant) uint io_uring_read_write_fixed.octal!"600" = 384uoctal = uint io_uring_read_write_fixed.parseOctal(string s) pure nothrow @nogc @safeparseOctal((constant) string io_uring_read_write_fixed.s = "600"s);
}
private uint uint io_uring_read_write_fixed.parseOctal(string s) pure nothrow @nogc @safeparseOctal((alias) object.string = stringstring (parameter) string ss) pure nothrow @safe @nogc
{
uint (local variable) uint vv;
foreach ((parameter) immutable(char) cc; (parameter) string ss)
(local variable) uint vv = (local variable) uint vv * 8 + ((local variable) immutable(char) cc - '0');
return (local variable) uint vv;
}