#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_registered_files"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — registered files + `IOSQE_FIXED_FILE` (Linux 5.1).
*
* One of the original 5.1 features: `IORING_REGISTER_FILES` lets a ring
* pre-register a table of file descriptors with the kernel. Submissions then
* address a file by its *table index* instead of a raw fd, with the
* `IOSQE_FIXED_FILE` flag set on the SQE.
*
* Why it matters: for every plain (non-fixed) op the kernel must look the fd up
* in the process fd table and bump/drop the file's reference count on each
* submit/complete. A registered file is grabbed once at registration time and
* held for the life of the table, so per-op submission skips that fget/fput
* dance entirely — a measurable win for high-IOPS workloads that hammer the
* same descriptors.
*
* This program:
* 1. creates a temp file (libc) and writes a known 256-byte pattern,
* 2. reopens it read-only and registers it as fixed-file table index 0,
* 3. submits a `prepRead` whose `fd` argument is the *index* 0, with
* `SubmissionEntryFlags.FIXED_FILE` set, then verifies the bytes read back
* match the pattern,
* 4. unregisters the table.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md § "5.1 — The introduction".
*
* Run with: `dub run --single registered-files.d`
*
* Portability: if the running kernel has no `io_uring` (too old, or blocked by a
* seccomp/container policy), the program prints a `SKIP:` line and exits 0. The
* fixed-file table itself dates from the 5.1 introduction, so any kernel new
* enough to run io_uring at all supports it.
*/
module (module) io_uring_registered_filesio_uring — registered files + IOSQE_FIXED_FILE (Linux 5.1).
One of the original 5.1 features: IORING_REGISTER_FILES lets a ring
pre-register a table of file descriptors with the kernel. Submissions then
address a file by its table index instead of a raw fd, with the
IOSQE_FIXED_FILE flag set on the SQE.
Why it matters: for every plain (non-fixed) op the kernel must look the fd up
in the process fd table and bump/drop the file's reference count on each
submit/complete. A registered file is grabbed once at registration time and
held for the life of the table, so per-op submission skips that fget/fput
dance entirely — a measurable win for high-IOPS workloads that hammer the
same descriptors.
This program:
creates a temp file (libc) and writes a known 256-byte pattern,
reopens it read-only and registers it as fixed-file table index 0,
submits a prepRead whose fd argument is the index 0, with
SubmissionEntryFlags.FIXED_FILE set, then verifies the bytes read back
match the pattern,
unregisters the table.
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md § "5.1 — The introduction".
Run with: dub run --single registered-files.d
Portability
if the running kernel has no io_uring (too old, or blocked by a
seccomp/container policy), the program prints a SKIP: line and exits 0. The
fixed-file table itself dates from the 5.1 introduction, so any kernel new
enough to run io_uring at all supports it.
io_uring_registered_files;
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.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_registered_files.malloc = void* core.stdc.stdlib.malloc(ulong size) nothrow @nogcmalloc, (alias) io_uring_registered_files.free = void core.stdc.stdlib.free(void* ptr) nothrow @nogcfree;
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_registered_files.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL, (alias constant) io_uring_registered_files.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.fcntlfcntl : open, (alias constant) io_uring_registered_files.O_CREAT = int core.sys.posix.fcntl.O_CREAT = 64O_CREAT, (alias constant) io_uring_registered_files.O_RDONLY = int core.sys.posix.fcntl.O_RDONLY = 0O_RDONLY, (alias constant) io_uring_registered_files.O_WRONLY = int core.sys.posix.fcntl.O_WRONLY = 1O_WRONLY, (alias constant) io_uring_registered_files.O_TRUNC = int core.sys.posix.fcntl.O_TRUNC = 512O_TRUNC;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.unistdD header file for POSIX.
unistd : (alias) io_uring_registered_files.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose, (alias) io_uring_registered_files.unlink = int core.sys.posix.unistd.unlink(scope const(char*)) nothrow @nogcunlink, (alias) io_uring_registered_files.write = long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogcwrite;
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_files.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
import (package) stdstd.(module) std.stringString handling functions.
Category Functions Searching column indexOf indexOfAny indexOfNeither lastIndexOf lastIndexOfAny lastIndexOfNeither Comparison isNumeric Mutation capitalize Pruning and Filling center chomp chompPrefix chop detabber detab entab entabber leftJustify outdent rightJustify strip stripLeft stripRight wrap Substitution abbrev soundex soundexer succ tr translate Miscellaneous assumeUTF fromStringz lineSplitter representation splitLines toStringz Objects of types string, wstring, and dstring are value types and cannot be mutated element-by-element. For using mutation during building strings, use char[], wchar[], or dchar[]. The xxxstring types are preferable because they don't exhibit undesired aliasing, thus making code more robust.
The following functions are publicly imported:
Module Functions Publicly imported functions std.algorithm cmp, std,algorithm,comparison count, std,algorithm,searching endsWith, std,algorithm,searching startsWith, std,algorithm,searching std.array join, std,array replace, std,array replaceInPlace, std,array split, std,array empty, std,array std.format format, std,format sformat, std,format std.uni icmp, std,uni toLower, std,uni toLowerInPlace, std,uni toUpper, std,uni toUpperInPlace, std,uni There is a rich set of functions for string handling defined in other modules. Functions related to Unicode and ASCII are found in std.uni and std.ascii, respectively. Other functions that have a wider generality than just strings can be found in std.algorithm and std.range.
Source
std/string.d
string : (alias) io_uring_registered_files.toStringz = immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedtoStringz;
int int D main()main()
{
enum ulong (constant) ulong io_uring_registered_files.main.cookie = 1364262913LUcookie = 0x5151_0001;
// A deterministic 256-byte pattern (0,1,2,...,255) we will write and then
// read back through the fixed-file table.
ubyte[256] (local variable) ubyte[256] patternpattern;
foreach ((parameter) ulong ii, ref (parameter) ubyte bb; (local variable) ubyte[256] patternpattern)
(local variable) ubyte bb = cast(ubyte) (local variable) ulong ii;
(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;
}
// --- 1. Create a temp file and write the known pattern via plain libc. ---
const char* (local variable) const(char*) pathpath = immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz("/tmp/io_uring_registered_files.tmp");
{
const (local variable) const(int) wfdwfd = open(int core.sys.posix.fcntl.open64(scope const(char*), int, ...) nothrow @nogcpath, (constant) int core.sys.posix.fcntl.O_CREAT = 64O_CREAT | (constant) int core.sys.posix.fcntl.O_WRONLY = 1O_WRONLY | (constant) int core.sys.posix.fcntl.O_TRUNC = 512O_TRUNC, (template instance) io_uring_registered_files.octal!600octal!600);
if ((local variable) const(int) wfdwfd < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("could not create temp file");
return 1;
}
const (local variable) const(long) wrwr = long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogcwrite((local variable) const(int) wfdwfd, &(local variable) ubyte[256] patternpattern[0], (local variable) ubyte[256] patternpattern.(constant) ulong ubyte[256].length = 256LUlength);
int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) const(int) wfdwfd);
if ((local variable) const(long) wrwr != (local variable) ubyte[256] patternpattern.(constant) ulong ubyte[256].length = 256LUlength)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("short write to temp file: %d", (local variable) const(long) wrwr);
int core.sys.posix.unistd.unlink(scope const(char*)) nothrow @nogcunlink((local variable) const(char*) pathpath);
return 1;
}
}
scope (exit) int core.sys.posix.unistd.unlink(scope const(char*)) nothrow @nogcunlink((local variable) const(char*) pathpath);
// --- 2. Reopen read-only and register it as fixed-file table index 0. ---
const (local variable) const(int) rfdrfd = open(int core.sys.posix.fcntl.open64(scope const(char*), int, ...) nothrow @nogcpath, (constant) int core.sys.posix.fcntl.O_RDONLY = 0O_RDONLY, 0);
if ((local variable) const(int) rfdrfd < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("could not reopen temp file");
return 1;
}
scope (exit) int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) const(int) rfdrfd);
const int[1] (local variable) const(int[1]) tableFdstableFds = [(local variable) const(int) rfdrfd];
const (local variable) const(int) regRetregRet = (local variable) during.Uring ioio.int during.Uring.registerFiles(const(int)[] fds) nothrow @nogcRegister files for I/O.
To make use of the registered files, the IOSQE_FIXED_FILE flag must be set in the flags
member of the SubmissionEntry, and the fd member is set to the index of the file in the
file descriptor array.
Files are automatically unregistered when the io_uring instance is torn down. An application
need only unregister if it wishes to register a new set of fds.
Use -1 as a file descriptor to mark it as reserved in the array.*
registerFiles((local variable) const(int[1]) tableFdstableFds[]);
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)
{
// Defensive: fixed files exist since 5.1, so this should not happen on a
// kernel that completed io_uring_setup, but stay green if it ever does.
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: IORING_REGISTER_FILES unsupported (errno %d)", -(local variable) const(int) regRetregRet);
return 0;
}
if ((local variable) const(int) regRetregRet != 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("registerFiles failed: errno %d", -(local variable) const(int) regRetregRet);
return 1;
}
// --- 3. Read the file BY TABLE INDEX, not by raw fd. ---
// The buffer the kernel fills lives in process memory; allocate it off-GC so
// the example stays self-contained and pointer-stable for the duration.
enum (alias) object.size_t = ulongsize_t (constant) ulong io_uring_registered_files.main.len = 256LUlen = (local variable) ubyte[256] patternpattern.(constant) ulong ubyte[256].length = 256LUlength;
auto (local variable) ubyte* bufPtrbufPtr = cast(ubyte*) void* core.stdc.stdlib.malloc(ulong size) nothrow @nogcmalloc((constant) ulong io_uring_registered_files.main.len = 256LUlen);
if ((local variable) ubyte* bufPtrbufPtr is null)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("malloc failed");
(local variable) during.Uring ioio.int during.Uring.unregisterFiles() nothrow @nogc @trustedAll previously registered files associated with the io_uring instance will be unregistered.
Files are automatically unregistered when the io_uring instance is torn down. An application
need only unregister if it wishes to register a new set of fds.
unregisterFiles();
return 1;
}
scope (exit) void core.stdc.stdlib.free(void* ptr) nothrow @nogcfree((local variable) ubyte* bufPtrbufPtr);
ubyte[] (local variable) ubyte[] readBufreadBuf = (local variable) ubyte* bufPtrbufPtr[0 .. (constant) ulong io_uring_registered_files.main.len = 256LUlen];
(local variable) ubyte[] readBufreadBuf[] = 0;
(local variable) during.Uring ioio.putWith!((ref SubmissionEntry e, ubyte[] dst) {
// NOTE: the first arg to prepRead is `0` — the *index* into the
// registered-file table, not `rfd`. FIXED_FILE tells the kernel to
// interpret it that way.
e.prepRead(0, dst, 0);
e.flags |= SubmissionEntryFlags.FIXED_FILE;
e.user_data = cookie;
})(during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, ubyte[] dst) nothrow @nogc @safe
{
prepRead(e, 0, dst, 0L);
cast(int)e.flags |= 1;
e.user_data = 1364262913LU;
}
, ubyte[])(ref ubyte[] __param_0) 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().
readBuf);
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);
if ((local variable) const(int) submittedsubmitted < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit failed: errno %d", -(local variable) const(int) submittedsubmitted);
(local variable) during.Uring ioio.int during.Uring.unregisterFiles() nothrow @nogc @trustedAll previously registered files associated with the io_uring instance will be unregistered.
Files are automatically unregistered when the io_uring instance is torn down. An application
need only unregister if it wishes to register a new set of fds.
unregisterFiles();
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();
// --- 4. Unregister the table (kernel drops its single held reference). ---
const (local variable) const(int) unregRetunregRet = (local variable) during.Uring ioio.int during.Uring.unregisterFiles() nothrow @nogc @trustedAll previously registered files associated with the io_uring instance will be unregistered.
Files are automatically unregistered when the io_uring instance is torn down. An application
need only unregister if it wishes to register a new set of fds.
unregisterFiles();
if ((local variable) const(int) unregRetunregRet != 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("unregisterFiles failed: errno %d", -(local variable) const(int) unregRetunregRet);
return 1;
}
// Validate the fixed-file read.
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("fixed-file read failed: errno %d", -(local variable) const(int) resres);
return 1;
}
if ((local variable) const(ulong) echoedechoed != (constant) ulong io_uring_registered_files.main.cookie = 1364262913LUcookie)
{
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_files.main.cookie = 1364262913LUcookie, (local variable) const(ulong) echoedechoed);
return 1;
}
if ((local variable) const(int) resres != cast(int) (constant) ulong io_uring_registered_files.main.len = 256LUlen || (local variable) ubyte[] readBufreadBuf[] != (local variable) ubyte[256] patternpattern[])
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("content mismatch: read %d bytes, pattern check failed", (local variable) const(int) resres);
return 1;
}
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: read %d bytes via fixed-file table index 0 (IOSQE_FIXED_FILE), pattern verified", (local variable) const(int) resres);
return 0;
}
/// Compile-time octal literal helper (D dropped the `0NNN` octal syntax).
private enum (constant) int io_uring_registered_files.octal!600 = 384Compile-time octal literal helper (D dropped the 0NNN octal syntax).
octal(int n) = {
int (local variable) int vv = 0, (local variable) int mulmul = 1, (local variable) int xx = (constant) int io_uring_registered_files.n = 600n;
while ((local variable) int xx > 0) { (local variable) int vv += ((local variable) int xx % 10) * (local variable) int mulmul; (local variable) int mulmul *= 8; (local variable) int xx /= 10; }
return (local variable) int vv;
}();