9 Commits
main ... v5.0

Author SHA1 Message Date
d9098a69a9 Minor fixes after the merge
Deleting some duplicate functions and header
2026-04-28 18:48:30 +01:00
0faf2bc792 Merge branch 'io_ring' 2026-04-28 17:55:41 +01:00
b4487cd3a6 Finalizing the implementation of file registration
Adding the file system check in Linux(can be enabled from the config
file)
Adding a more options to the config file
Writing the README
2026-04-28 17:52:02 +01:00
3393129c5f Implementing registered files in io_uring
The windows implementation is disabled, currently registering files in
IO Ring when there is inflight IO operations causes corruptions.

Implementing a config file.

Some code cleanup
2026-04-24 15:30:04 +01:00
ab31776658 Reworking IO Ring pipeline to fully support multiple infilght files
Reworking the filequeue, the buffer chaining logic and the error
handling.
Renaming functions.
Fix bug in arena.
2026-04-23 19:53:58 +01:00
b8e577b5bb Porting IO Ring to linux by implementing io_uring 2026-04-15 23:15:00 +01:00
0294498538 Add support for multiple inflight files and one shot hash small files
The IO Ring now supports bashing multiple submissions and can handle
multiple files at the same time.

Hashing small files using XXH3_128bits() instead of the streaming
pipeline(XXH3_128bits_reset(), XXH3_128bits_update(),
XXH3_128bits_digest()), this reduses the overhead of creating a state
and digest, coupled with the IO Ring it improves the hashing of small
files whose size is inferior to the size of IO Ring buffers
2026-04-02 14:31:58 +01:00
41ac164881 Updating the IO Ring, Updating the progress printing fn 2026-03-31 19:33:39 +01:00
d4ba121b56 Implementation of IO Ring in Windows
Fixing the two compilation warnings.
2026-03-31 00:26:03 +01:00
11 changed files with 2551 additions and 239 deletions

6
.gitignore vendored
View File

@@ -3,5 +3,11 @@ file_hasher.ilk
file_hasher.rdi file_hasher.rdi
file_hasher.exe file_hasher.exe
file_hashes.txt file_hashes.txt
Binaries/file_hashes.txt
file_list.txt file_list.txt
temp_code.c temp_code.c
/.cache/clangd/index
/file_hasher
/io_uring_test
/file_hasher
/io_uring_test

236
README.md
View File

@@ -1,24 +1,224 @@
# filehasher # filehasher
Collects some metadata and hashes files. # Presentation
Collects some metadata and hashes files. It outputs the path, hash, size, creation and
last modification dates and the author in file_hasher.txt.
Creation and modification dates and author can be disabled in the config file.
## Building: It is a high performance cross platform Windows and Linux compatible program, it uses:
### Windows: * Multiple threads for scanning and hashing (multi-threading can be disabled in the config file).
#### Release: * Stores the generated data in thread local configurable arenas that support growing
clang-cl /O3 file_hasher.c xxh_x86dispatch.c advapi32.lib by committing more memory and chaining blocks.
clang -O3 file_hasher.c xxh_x86dispatch.c -ladvapi32 -o file_hasher * Two Multi Producer Multi Consumer queues, one for the scanners and one between the scanners and hashers.
gcc -O3 file_hasher.c xxh_x86dispatch.c -ladvapi32 -o file_hasher * xxh3_128bits algorithm from xxhash, that supports SIMD instruction sets (SSE2, AVX2, AVX512)
and uses a runtime dispatcher to select the best available instruction set.
* IO Ring for asynchronous I/O in Windows and the equivalent io_uring in Linux.
The implementation is event driven, thread local, uses DMA and direct disk I/O,
bypassing the OS cache completely, registered buffers (and registered files in io_uring),
it supports bashing multiple submissions and can handle multiple files at the same time.
It can be disabled in the config file.
* Fallback to buffered I/O if there is errors in the IO Ring path.
#### Debug: # Building
clang-cl /Zi /Od file_hasher.c xxh_x86dispatch.c advapi32.lib ## Windows
clang -g -O0 file_hasher.c xxh_x86dispatch.c -ladvapi32 -o file_hasher ### Release
gcc -g -O0 file_hasher.c xxh_x86dispatch.c -ladvapi32 -o file_hasher
### Linux: **Note**: Make sur to use UCRT64 environment from MSYS2 instead of the standard MinGW environment.
#### Release: UCRT64 uses the modern Universal C Runtime (ucrtbase.dll), which supports the newest APIs,
clang -O3 -pthread file_hasher.c xxh_x86dispatch.c -o file_hasher the standard MSYS2 uses the legacy msvcrt.dll and does not support IO Ring.
gcc -O3 -pthread file_hasher.c xxh_x86dispatch.c -o file_hasher To install:
pacman -S mingw-w64-ucrt-x86_64-gcc
pacman -S mingw-w64-ucrt-x86_64-clang
pacman -Syu
And add to path:
C:\msys64\ucrt64\bin
#### Debug: gcc -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -o file_hasher
clang -g -O0 -pthread file_hasher.c xxh_x86dispatch.c -o file_hasher clang -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -o file_hasher
gcc -g -O0 -pthread file_hasher.c xxh_x86dispatch.c -o file_hasher clang-cl /O2 file_hasher.c xxhash.c xxh_x86dispatch.c
### Debug
gcc -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -o file_hasher
clang -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -o file_hasher
clang-cl /Zi /Od file_hasher.c xxhash.c xxh_x86dispatch.c
## Linux
### Release
gcc -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher
clang -O3 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher
### Debug
gcc -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher
clang -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -pthread -luring -o file_hasher
# Notes about the IO Ring implementations
## IO Ring
### File registration
Registering files is a performance optimization that allows the kernel to allocate an array
of descriptors/handles to pre-validate and maintain long-term references to file handles.
Instead of passing a standard file descriptor/handle with every I/O request, you pass a simple integer
index into a pre-registered table.
The Linux implementation has io_uring_register_files_scarse() to create an empty array of descriptors
(initialized with -1) without having to create and initialize it in the user space, and we can
use io_uring_register_files_update() to update one or more entries. Windows on the other hand
is limited to BuildIoRingRegisterFileHandles() only, so we need to re register the entire array of handles
each time. This is why there is a provided macro in config.h to disable or enable it.
#### *Why Register Files? (The Benefits)*
When you use a standard file descriptor in a high-frequency I/O loop,
the kernel must perform several "hidden" tasks for every single operation:
* Permission Checks: Validating that the process still has the right to read/write
that specific file.
* Reference Counting: Incrementing the file's internal reference count at the start of
the I/O and decrementing it at the end to ensure the file isn't closed while in use.
* Object Lookup: Traversing the internal "file descriptor table" to find the actual
kernel object associated with your integer ID.
Registering the files performs these checks once at registration time. Subsequent
I/O operations skip these steps, significantly reducing CPU overhead and latency,
especially when handling thousands of small I/O operations per second.
#### *Comparison: Linux vs. Windows Implementation*
While both systems share the same core concept, their APIs and management styles differ significantly.
| Feature | Linux (`io_uring`) | Windows (`IoRing`) |
| :--- | :--- | :--- |
| **API Call** | `io_uring_register` | `BuildIoRingRegisterFileHandles` |
| **Registration Method** | Synchronous system call; blocks until the table is set up. | Asynchronous request submitted to the ring like a read/write operation. |
| **Partial Updates** | Supports `IORING_REGISTER_FILES_UPDATE` to swap specific indices. | No partial updates; a new registration replaces the entire table. |
| **Memory Mapping** | User must manually `mmap()` queues into their address space. | Kernel handles memory mapping automatically when the ring is created. |
| **Scope of Operations** | Extremely broad (files, sockets, timers, signals, etc.). | Primarily focused on file storage (read, write, flush). |
### Completion Wait count
To avoid busy waiting when receiving CQEs, we can use io_uring_submit_and_wait() in Linux by entering a wait count,
the threads sleeps until the count of CQEs are received, in windows the wait_count is present in SubmitIoRing()
but is not implemented yet, so we wait with a completion event for a single completion. Another limitation on the completion
event is that the kernel will waik up the thread only when receiving the first CQE, after that we need to drain the completion
queue completely before sleeping again, or we enter an eternal slumber. And my config, each time the thread wakes up
it receives rarely more than 3 to 5 CQEs and most of the time only one CQE.
### Filtering CQEs
Unlike Linux, The Windows implementation treats buffer and file registration
as an asynchronous operation that we submit to the ring, similar to a read or write.
Those operations produce CQEs (completion queue entries) that we filter here using
cqe.UserData == USERDATA_REGISTER
```c
if (win_cqe.UserData == USERDATA_REGISTER)
continue;
```
## io_uring
### Creation flags
io_uring provides a lot of configuration flags compared to IO Ring, some
of them are at the creation and others during the operations, here what
we use in this implementation at creation time and is lacking in the
IO Ring implementation.
* IORING_SETUP_SINGLE_ISSUER: Since we are using a thread local io_uring, we can
set this flag to remove the atomic operations.
* IORING_SETUP_DEFER_TASKRUN: By default, the kernel sends an interrupts when a CQE
is ready, we use this flag to disable this syscall and wait for a specific number of
CQEs to be ready to group them, this reduces the number of syscall.
### Memlock limit warning
```c
"WARNING: Buffer registration failed due to memlock limits (ENOMEM).\n"
"Increase the limit to solve this warning.\n");
```
The Memlock limit in Linux restricts the amount of memory a process can
"lock" into physical RAM using the mlock() family of system calls. This
prevents the operating system from swapping that memory out to disk.
And registering buffers will lock the buffers memory so the hardware
can access it directly without kernel intervention and prevents the kernel from
swapping it to the SSD or HDD. Increase the limit to be able to register the buffers.
#### *Modifying the Limit*
The method for changing the memlock limit depends on whether you are
managing a user session or a system service.
1. For Users and Interactive Sessions
To permanently increase the limit for a specific user or group, modify
the /etc/security/limits.conf file. Add the following lines:
```conf
# Example for a specific user (replace 'username'), unlimited or a custom value in KB
username soft memlock unlimited
username hard memlock unlimited
```
```conf
# Example for all users
* soft memlock unlimited
* hard memlock unlimited
```
Soft Limit: The value the user starts with; can be raised up to the
hard limit.
Hard Limit: The absolute maximum; only a privileged user
(root) can increase this. Values: Can be set in Kilobytes (KB) or as
unlimited.
2. For Systemd Services
Settings in limits.conf do not affect background services managed by
systemd. To increase the limit for a service, edit its service file
(e.g., /etc/systemd/system/myservice.service) and add:
```conf
[Service]
LimitMEMLOCK=infinity
```
#### *Why Register Buffers?*
In a standard "unregistered" I/O operation, the kernel must perform several
expensive steps for every single read or write:
* Virtual-to-Physical Mapping: The kernel has to translate your application's
virtual memory addresses into physical RAM addresses.
* Page Pinning: The kernel must "pin" the memory pages (using get_user_pages)
to prevent them from being swapped to disk or moved while the hardware
(like your SSD) is writing to them.
* TLB Overhead: Constant mapping and unmapping put pressure on the Translation
Lookaside Buffer (TLB), which can slow down the CPU.
Registering the buffers performs all of this "pinning" and "mapping" once.
### Direct I/O: O_DIRECT (Linux) and FILE_FLAG_NO_BUFFERING (Windows)
Modern operating systems normally use a page cache when reading files. This means file
data is first loaded into kernel memory and then copied to user space. While this improves
performance for many workloads, it introduces extra memory usage and copy overhead.
Both Linux and Windows provide a way to bypass this cache and perform direct I/O:
Linux: O_DIRECT
Windows: FILE_FLAG_NO_BUFFERING
These flags instruct the OS to transfer data directly between disk and user-provided buffers, avoiding the page cache.
#### *Benefits*
1. Reduced memory overhead
Avoids polluting the OS page cache
Especially useful for large sequential reads (e.g. hashing, backups)
2. Lower CPU usage
Eliminates extra memory copies between kernel and user space
3. Predictable performance
No interference from cache eviction or readahead heuristics
More consistent throughput for streaming workloads
4. Better scalability
Ideal for high-throughput, multi-threaded I/O pipelines
Prevents cache contention between threads
5. Avoids double caching
Important when the application already manages its own buffering
#### *File system compatibility*
Not all file systems are compatible with O_DIRECT, if we try to open files residing in an NTFS partition,
most of the time it will fail, and some times it opens but the CQEs return with an error code bad
descriptor, and it causes some lags.
To address this issue the program falls back to sequential read when the open fails, and falls back to
buffered sequential hashing if we receive an error in the CQEs. There is also a file system detection
that we can enable in the config file, it will enable the collection of the file system in scan_folder()
and the file will be opened accordingly, but it costs one additional syscall / directory.

View File

@@ -437,12 +437,14 @@ void *arena_push(mem_arena **arena_ptr, u64 size, bool zero) { // mk push
Commit memory if needed Commit memory if needed
------------------------------------------------------------ */ ------------------------------------------------------------ */
if (local_post > selected->commit_pos) { if (local_post > selected->commit_pos -
u64 new_commit = ALIGN_UP_POW2(local_post, arena_pagesize()); ALIGN_UP_POW2(sizeof(mem_arena), selected->align)) {
u64 new_commit = ALIGN_UP_POW2(local_post + ALIGN_UP_POW2(sizeof(mem_arena), selected->align), arena_pagesize());
new_commit = MIN(new_commit, selected->reserve_size); new_commit = MIN(new_commit, selected->reserve_size);
if (!plat_mem_commit((u8 *)selected + selected->commit_pos, if (!plat_mem_commit((u8 *)selected + selected->commit_pos,
new_commit - selected->commit_pos)) { new_commit - selected->commit_pos)) {
printf("ERROR: Could not commit memory!\n");
return NULL; return NULL;
} }

59
base.h
View File

@@ -1,9 +1,49 @@
#pragma once #pragma once
#define _CRT_SECURE_NO_WARNINGS
#if defined(_WIN32) || defined(_WIN64)
#if defined(_MSC_VER)
#pragma comment(lib, "advapi32.lib")
#endif
#include <aclapi.h>
#include <fcntl.h>
#include <io.h>
#include <ioringapi.h> // Needs to be included before stdatomic to avoid errors
#include <ntioring_x.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <windows.h>
#include <winerror.h>
#elif defined(__linux__)
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dirent.h>
#include <fcntl.h>
#include <liburing.h>
#include <poll.h>
#include <pthread.h>
#include <pwd.h>
#include <sys/eventfd.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <unistd.h>
#endif
#include <assert.h> #include <assert.h>
#include <ctype.h>
#include <immintrin.h> #include <immintrin.h>
#include <limits.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -11,25 +51,6 @@
#include <time.h> #include <time.h>
#include <errno.h> #include <errno.h>
#if defined(_WIN32) || defined(_WIN64)
#define PLATFORM_WINDOWS 1
#include <aclapi.h>
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <windows.h>
#define strdup _strdup
#else
#include <dirent.h>
#include <fcntl.h>
#include <pthread.h>
#include <pwd.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
/* ------------------------------------------------------------ /* ------------------------------------------------------------
Base types Base types
------------------------------------------------------------ */ ------------------------------------------------------------ */

View File

@@ -14,7 +14,7 @@ v3.2: Making the lock free MPMC queue growable
Add padding to avoir false sharing Add padding to avoir false sharing
Add sleep() and SwitchToThread() to limit spinning Add sleep() and SwitchToThread() to limit spinning
v3.3: Fix bug slots used before initialization,compare and swap is protecting updating committed, but it is not protecting the memory initialization. Adding atomic_flag commit_lock to protect against that v3.3: Fix bug slots used before initialization, compare and swap is protecting updating committed, but it is not protecting the memory initialization. Adding atomic_flag commit_lock to protect against that
Fix bug multiple threads committing at the same time, fixed by using atomic_flag commit_lock and re-checking committed after acquiring the lock Fix bug multiple threads committing at the same time, fixed by using atomic_flag commit_lock and re-checking committed after acquiring the lock
Reorder helper functions Reorder helper functions
@@ -49,3 +49,10 @@ Fixing user prompt parsing
4.5: Porting to linux 4.5: Porting to linux
Reorganising the code Reorganising the code
Improving the scan function Improving the scan function
5.0: Implementing the IO Ring for windows and ui_uring for linux instead of buffered hashing, huge performance gains. The IO Ring is event driven, thread local, uses DMA and direct disk I/O, bypassing the OS cache completely, registered buffers (and registered files in io_uring), it supports bashing multiple submissions and can handle multiple files at the same time.
Hashing small files using XXH3_128bits() instead of the streaming pipeline(XXH3_128bits_reset(), XXH3_128bits_update(), XXH3_128bits_digest()), this reduses the overhead of creating a state and digest, coupled with the IO Ring it improves the hashing of small files whose size is inferior to the size of IO Ring buffers
fixing the xxh_x86dispatch warnings
Updating the progress printing function
Implementing a config file
Writing the README file

31
config.h Normal file
View File

@@ -0,0 +1,31 @@
#define FILE_HASHES_TXT "file_hashes.txt"
// Metadata selection
#define FILE_TIMES 1 // created and modified time
#define FILE_OWNER 1
#define MULTI_THREADING 1
#define READ_BLOCK KiB(64)
// -------------------- IO Ring Configuration ----------------------
#define USE_IORING 1
#if USE_IORING
#define IORING_BUFFER_SIZE KiB(256)
#define NUM_BUFFERS_PER_THREAD 32
#define MAX_ACTIVE_FILES 16
#define SUBMIT_TIMEOUT_MS 10000
#define IORING_DEBUG_PRINTS 0
#define IORING_DEBUG_STATS 0
#if defined(_WIN32) || defined(_WIN64)
#define USE_REGISTERED_FILES 1
#elif defined(__linux__)
#define USE_REGISTERED_FILES 1
#define CHECK_FILE_SYSTEM 0
#endif
#endif

View File

@@ -74,21 +74,44 @@ int main(int argc, char **argv) {
mem_arena *gp_arena = arena_create(&params); mem_arena *gp_arena = arena_create(&params);
// ------------------------------- // -------------------------------
// Detect hardware threads // Detect hardware
// ------------------------------- // -------------------------------
// --- Windows: detect PHYSICAL cores (not logical threads) --- // --- Windows: detect PHYSICAL cores (not logical threads) ---
size_t hw_threads = platform_physical_cores(); uint32_t cpu_cores = platform_physical_cores();
// Logical threads = CPU cores * 2 // Logical threads = CPU cores * 2
size_t num_threads = hw_threads * 2; uint32_t cpu_threads = cpu_cores * 2;
printf("Starting thread pool: %zu threads (CPU cores: %zu)\n", num_threads, #if MULTI_THREADING
hw_threads); uint32_t num_scan_threads = cpu_threads;
printf(" Selected instruction set: %s\n", get_xxhash_instruction_set()); uint32_t num_hash_threads = cpu_threads;
printf("%d cores %d threads CPU detected with %s instruction set\n"
"Starting thread pool: %d scanning and %d hashing threads\n",
cpu_cores, cpu_threads, get_xxhash_instruction_set(), num_scan_threads,
num_hash_threads);
#else
uint32_t num_scan_threads = 1;
uint32_t num_hash_threads = 1;
printf(
"%d cores %d threads CPU detected with %s instruction set\n"
"Starting thread pool: %d scanning and %d hashing threads(Debug mode)\n",
cpu_cores, cpu_threads, get_xxhash_instruction_set(), num_scan_threads,
num_hash_threads);
#endif
// Align IO Ring block size to the system page size
#if USE_IORING
g_ioring_buffer_size = ALIGN_UP_POW2(g_ioring_buffer_size, g_pagesize);
#endif
// ------------------------------- // -------------------------------
// Scanning and hashing // Scanning and hashing
// ------------------------------- // -------------------------------
// test_io_ring();
MPMCQueue dir_queue; MPMCQueue dir_queue;
mpmc_init(&dir_queue, MiB(1)); mpmc_init(&dir_queue, MiB(1));
@@ -96,8 +119,6 @@ int main(int argc, char **argv) {
mpmc_init(&file_queue, MiB(1)); mpmc_init(&file_queue, MiB(1));
// Starting hash threads // Starting hash threads
size_t num_hash_threads = num_threads;
WorkerContext workers[num_hash_threads]; WorkerContext workers[num_hash_threads];
Thread *hash_threads = Thread *hash_threads =
arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true); arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true);
@@ -106,8 +127,14 @@ int main(int argc, char **argv) {
workers[i].arena = arena_create(&params); workers[i].arena = arena_create(&params);
workers[i].file_queue = &file_queue; workers[i].file_queue = &file_queue;
#if USE_IORING
if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker_ioring,
&workers[i]) != 0)
#else
if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker, &workers[i]) != if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker, &workers[i]) !=
0) { 0)
#endif
{
fprintf(stderr, "Failed to create hash thread %zu\n", i); fprintf(stderr, "Failed to create hash thread %zu\n", i);
exit(1); exit(1);
} }
@@ -122,8 +149,6 @@ int main(int argc, char **argv) {
} }
// Starting scan threads // Starting scan threads
size_t num_scan_threads = num_threads;
ScannerContext scanners[num_scan_threads]; ScannerContext scanners[num_scan_threads];
Thread *scan_threads = Thread *scan_threads =
arena_push(&gp_arena, sizeof(Thread) * num_scan_threads, true); arena_push(&gp_arena, sizeof(Thread) * num_scan_threads, true);
@@ -197,7 +222,7 @@ int main(int argc, char **argv) {
FILE *f = fopen(FILE_HASHES_TXT, "wb"); FILE *f = fopen(FILE_HASHES_TXT, "wb");
for (int i = 0; i < num_threads; i++) { for (int i = 0; i < num_hash_threads; i++) {
mem_arena *arena = workers[i].arena; mem_arena *arena = workers[i].arena;
u8 *arena_base = u8 *arena_base =
(u8 *)arena + ALIGN_UP_POW2(sizeof(mem_arena), arena->align); (u8 *)arena + ALIGN_UP_POW2(sizeof(mem_arena), arena->align);
@@ -209,6 +234,15 @@ int main(int argc, char **argv) {
// ------------------------------- // -------------------------------
// Print summary // Print summary
// ------------------------------- // -------------------------------
#if USE_IORING
uint64_t incomplete = atomic_load(&g_io_ring_fallbacks);
if (incomplete > 0) {
printf("\nWARNING: I/O Ring incomplete files: %llu (fallback to buffered "
"I/O used)\n",
(unsigned long long)incomplete);
}
#endif
double total_seconds = timer_elapsed(&total_timer); double total_seconds = timer_elapsed(&total_timer);
printf("Completed hashing %zu files\n", total_found); printf("Completed hashing %zu files\n", total_found);

147
io_ring_test.c Normal file
View File

@@ -0,0 +1,147 @@
#pragma once
#include <ioringapi.h>
#include <ntioring_x.h>
// #include "ioringapi.c"
#include <winerror.h>
// Initialize I/O Ring
HIORING io_ring_init(void) {
// if (!io_ring_load_functions()) {
// printf("[I/O Ring] Failed to load functions\n");
// return NULL;
// }
IORING_CAPABILITIES caps;
ZeroMemory(&caps, sizeof(caps));
HRESULT hr = QueryIoRingCapabilities(&caps);
if (FAILED(hr)) {
printf("[I/O Ring] QueryIoRingCapabilities failed: 0x%08lx\n", hr);
return NULL;
}
// printf("[I/O Ring] MaxVersion=%d, MaxSubmission=%u, MaxCompletion=%u\n",
// (int)caps.MaxVersion, caps.MaxSubmissionQueueSize,
// caps.MaxCompletionQueueSize);
if (caps.MaxVersion < IORING_VERSION_1) {
printf("[I/O Ring] Version too old\n");
return NULL;
}
IORING_CREATE_FLAGS flags = {0};
HIORING ring = NULL;
// hr = CreateIoRing(IORING_VERSION_1, flags, 256, 512, &ring);
hr = CreateIoRing(caps.MaxVersion, flags, 256, 512, &ring);
if (FAILED(hr)) {
printf("[I/O Ring] CreateIoRing failed: 0x%08lx\n", hr);
return NULL;
}
// printf("[I/O Ring] Created successfully\n");
// Check if read operation is supported
// HRESULT io_ring_support = IsIoRingOpSupported(ring, IORING_OP_READ);
// if (io_ring_support == S_FALSE) {
// printf("[I/O Ring] Not supported, %ld /n", io_ring_support);
// }
// Get ring info
IORING_INFO info;
ZeroMemory(&info, sizeof(info));
GetIoRingInfo(ring, &info);
// printf("[I/O Ring] Submission: %u, Completion: %u\n",
// info.SubmissionQueueSize, info.CompletionQueueSize);
return ring;
}
void io_ring_cleanup(HIORING ring) {
if (ring) {
CloseIoRing(ring);
// printf("[I/O Ring] Closed\n");
}
}
// Read file using I/O Ring
int io_ring_read_file(HIORING ring, HANDLE hFile, void *buffer, DWORD size,
UINT64 offset) {
IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(hFile);
IORING_BUFFER_REF buf_ref = IoRingBufferRefFromPointer(buffer);
HRESULT hr = BuildIoRingReadFile(ring, file_ref, buf_ref, size, offset,
(UINT_PTR)buffer, IOSQE_FLAGS_NONE);
if (FAILED(hr))
return -1;
UINT32 submitted = 0;
hr = SubmitIoRing(ring, 1, INFINITE, &submitted);
if (FAILED(hr) || submitted == 0)
return -1;
for (;;) {
IORING_CQE cqe;
hr = PopIoRingCompletion(ring, &cqe);
if (FAILED(hr))
continue;
if (cqe.UserData != (UINT_PTR)buffer)
continue;
if (FAILED(cqe.ResultCode))
return -1;
return (int)cqe.Information;
}
}
// Test function
void test_io_ring(void) {
printf("\n=== Testing I/O Ring ===\n");
HIORING ring = io_ring_init();
if (!ring) {
printf("I/O Ring not available\n");
return;
}
// Create test file
HANDLE hFile = CreateFileA("test.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
char test_data[] =
"Hello, I/O Ring! This is a test of the Windows I/O Ring API.";
DWORD written;
WriteFile(hFile, test_data, sizeof(test_data), &written, NULL);
CloseHandle(hFile);
}
// Read using I/O Ring
hFile = CreateFileA("test.txt", GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
char buffer[512] = {0};
int bytes = io_ring_read_file(ring, hFile, buffer, sizeof(buffer), 0);
if (bytes > 0) {
printf("Read %d bytes: %s\n", bytes, buffer);
} else {
printf("Failed to read file\n");
}
CloseHandle(hFile);
} else {
printf("Failed to open test file\n");
}
// Cleanup
DeleteFileA("test.txt");
io_ring_cleanup(ring);
printf("=== Test complete ===\n\n");
}

397
io_uring_test.c Normal file
View File

@@ -0,0 +1,397 @@
/*
# Compile
gcc -o io_uring_test io_uring_test.c -luring
# Run
./io_uring_test
*/
#include "base.h"
#include <stdint.h>
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <liburing.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#define BUFFER_SIZE 4096
#define NUM_BUFFERS 4
#define NUM_REGISTERED_FILES 3 // Test with 3 files
// Colors for output
#define COLOR_GREEN "\033[0;32m"
#define COLOR_RED "\033[0;31m"
#define COLOR_YELLOW "\033[0;33m"
#define COLOR_BLUE "\033[0;34m"
#define COLOR_RESET "\033[0m"
// Test result tracking
typedef struct {
int passed;
int failed;
} TestResults;
static void print_success(const char *step) {
printf(COLOR_GREEN "[✓] SUCCESS: %s" COLOR_RESET "\n", step);
}
static void print_failure(const char *step, const char *error) {
printf(COLOR_RED "[✗] FAILED: %s - %s" COLOR_RESET "\n", step, error);
}
static void print_info(const char *msg) {
printf(COLOR_BLUE "[i] INFO: %s" COLOR_RESET "\n", msg);
}
static void print_step(const char *step) {
printf(COLOR_YELLOW "\n>>> Testing: %s" COLOR_RESET "\n", step);
}
static int create_test_file(const char *filename, const char *content) {
FILE *f = fopen(filename, "w");
if (!f) {
perror("Failed to create test file");
return -1;
}
fprintf(f, "%s", content);
fclose(f);
printf(" Created test file: %s\n", filename);
return 0;
}
// Test 1: Create io_uring instance
static int test_io_uring_create(struct io_uring *ring, TestResults *results) {
print_step("io_uring creation");
int ret = io_uring_queue_init(256, ring, 0);
if (ret < 0) {
print_failure("io_uring_queue_init", strerror(-ret));
results->failed++;
return -1;
}
print_success("io_uring instance created");
results->passed++;
return 0;
}
// Test 2: Register buffers
static int test_register_buffers(struct io_uring *ring, void **buffers,
struct iovec *iovs, TestResults *results) {
print_step("Buffer registration");
size_t total_size = BUFFER_SIZE * NUM_BUFFERS;
*buffers = aligned_alloc(4096, total_size);
if (!*buffers) {
print_failure("Buffer allocation", strerror(errno));
results->failed++;
return -1;
}
for (int i = 0; i < NUM_BUFFERS; i++) {
iovs[i].iov_base = (char *)*buffers + (i * BUFFER_SIZE);
iovs[i].iov_len = BUFFER_SIZE;
memset(iovs[i].iov_base, 0, BUFFER_SIZE);
}
int ret = io_uring_register_buffers(ring, iovs, NUM_BUFFERS);
if (ret < 0) {
print_failure("io_uring_register_buffers", strerror(-ret));
results->failed++;
return -1;
}
print_success("Buffers registered successfully");
results->passed++;
return 0;
}
// Test 3: Register files sparse (empty table)
static int test_register_files_sparse(struct io_uring *ring, unsigned nr_files,
TestResults *results) {
print_step("Sparse file registration (empty table)");
int ret = io_uring_register_files_sparse(ring, nr_files);
if (ret < 0) {
if (ret == -EINVAL) {
print_info(
"io_uring_register_files_sparse not supported (kernel < 5.19)");
print_info("Trying regular file registration with invalid fds...");
// Fallback: register with invalid fds
int *invalid_fds = calloc(nr_files, sizeof(int));
if (!invalid_fds) {
print_failure("Allocating invalid fds array", "Out of memory");
results->failed++;
return -1;
}
for (int i = 0; i < nr_files; i++) {
invalid_fds[i] = -1; // Mark all as invalid
}
ret = io_uring_register_files(ring, invalid_fds, nr_files);
free(invalid_fds);
if (ret < 0) {
print_failure("Regular file registration also failed", strerror(-ret));
results->failed++;
return -1;
}
print_success("File table registered (regular, with invalid fds)");
} else {
print_failure("io_uring_register_files_sparse", strerror(-ret));
results->failed++;
return -1;
}
} else {
printf(" Registered empty file table with %u slots\n", nr_files);
print_success("Sparse file table created");
}
results->passed++;
return 0;
}
// Test 4: Update file slot and read from it
static int test_file_read_loop(struct io_uring *ring, struct iovec *iovs,
const char **filenames,
const char **expected_contents, int num_files,
TestResults *results) {
print_step("File slot update and read loop");
int *fds = calloc(num_files, sizeof(int));
if (!fds) {
print_failure("Allocating fd array", "Out of memory");
results->failed++;
return -1;
}
// Open all files first
for (int i = 0; i < num_files; i++) {
fds[i] = open(filenames[i], O_RDONLY);
if (fds[i] < 0) {
print_failure("Opening file", filenames[i]);
results->failed++;
// Close already opened files
for (int j = 0; j < i; j++)
close(fds[j]);
free(fds);
return -1;
}
printf(" Opened %s (fd=%d)\n", filenames[i], fds[i]);
}
// Test loop: update slot, submit read, verify
for (int slot = 0; slot < num_files; slot++) {
printf("\n --- Testing slot %d with file '%s' ---\n", slot,
filenames[slot]);
// Update the file registration for this slot
printf(" Updating slot %d with fd %d...\n", slot, fds[slot]);
int ret = io_uring_register_files_update(ring, slot, &fds[slot], 1);
if (ret < 0) {
print_failure("File registration update", strerror(-ret));
results->failed++;
continue;
}
printf(" Slot update result: %d (expected 1)\n", ret);
// Get file size for read size calculation
struct stat st;
if (fstat(fds[slot], &st) != 0) {
print_failure("fstat", strerror(errno));
results->failed++;
continue;
}
size_t file_size = st.st_size;
size_t read_size = BUFFER_SIZE;
// Adjust read size for O_DIRECT if needed
int page_size = plat_get_pagesize();
if (read_size > file_size) {
read_size = ALIGN_UP_POW2(file_size, page_size);
}
printf(" File size: %zu, read size: %zu\n", file_size, read_size);
// Clear buffer for this test
memset(iovs[0].iov_base, 0, BUFFER_SIZE);
// Submit read using registered file
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
if (!sqe) {
print_failure("Getting SQE", "No available SQE");
results->failed++;
continue;
}
// Use slot index with fixed file flag
io_uring_prep_read_fixed(sqe, slot, iovs[0].iov_base, read_size, 0, 0);
sqe->flags |= IOSQE_FIXED_FILE;
io_uring_sqe_set_data64(sqe, 100 + slot); // Unique user_data per slot
ret = io_uring_submit(ring);
if (ret < 0) {
print_failure("Submitting read", strerror(-ret));
results->failed++;
continue;
}
printf(" Submitted read (1 SQE)\n");
// Wait for completion
struct io_uring_cqe *cqe;
ret = io_uring_wait_cqe(ring, &cqe);
if (ret < 0) {
print_failure("Waiting for completion", strerror(-ret));
results->failed++;
continue;
}
// Process completion
uint64_t user_data = io_uring_cqe_get_data64(cqe);
int bytes_read = cqe->res;
printf(" Completion: user_data=%lu, result=%d\n", (unsigned long)user_data,
bytes_read);
if (bytes_read < 0) {
print_failure("Read operation", strerror(-bytes_read));
results->failed++;
io_uring_cqe_seen(ring, cqe);
continue;
}
if (user_data != 100 + slot) {
print_failure("User data mismatch", "Wrong user_data value");
results->failed++;
io_uring_cqe_seen(ring, cqe);
continue;
}
// Verify the data
char *data = (char *)iovs[0].iov_base;
printf(" Data read (%d bytes): %.*s\n", bytes_read,
bytes_read < 100 ? bytes_read : 100, data);
if (strstr(data, expected_contents[slot]) == NULL) {
print_failure("Data verification",
"Expected content not found in read data");
results->failed++;
} else {
print_success("Data verified successfully");
results->passed++;
}
io_uring_cqe_seen(ring, cqe);
// Invalidate the slot after use (mark as -1)
int invalid_fd = -1;
ret = io_uring_register_files_update(ring, slot, &invalid_fd, 1);
if (ret < 0) {
printf(" Warning: Could not invalidate slot %d: %s\n", slot,
strerror(-ret));
}
}
// Close all files
for (int i = 0; i < num_files; i++) {
if (fds[i] >= 0)
close(fds[i]);
}
free(fds);
return 0;
}
int main() {
TestResults results = {0, 0};
struct io_uring ring;
void *buffers = NULL;
struct iovec iovs[NUM_BUFFERS];
printf(COLOR_BLUE "\n========================================\n");
printf(" io_uring Sparse File Registration Test\n");
printf("========================================\n" COLOR_RESET);
// Define test files and their content
const char *filenames[] = {"test_file_0.txt", "test_file_1.txt",
"test_file_2.txt"};
const char *contents[] = {
"This is file 0: Hello World! The quick brown fox jumps over the lazy "
"dog.",
"This is file 1: io_uring is awesome for async I/O operations!",
"This is file 2: Testing sparse file registration with multiple files."};
const char *expected_substrings[] = {"Hello World", "io_uring is awesome",
"sparse file registration"};
int num_files = 3;
// Create all test files
print_info("Creating test files...");
for (int i = 0; i < num_files; i++) {
if (create_test_file(filenames[i], contents[i]) != 0) {
return 1;
}
}
// Test 1: Create io_uring
if (test_io_uring_create(&ring, &results) != 0) {
goto cleanup_files;
}
// Test 2: Register buffers
if (test_register_buffers(&ring, &buffers, iovs, &results) != 0) {
io_uring_queue_exit(&ring);
goto cleanup_files;
}
// Test 3: Register empty file table (sparse)
if (test_register_files_sparse(&ring, num_files, &results) != 0) {
io_uring_unregister_buffers(&ring);
free(buffers);
io_uring_queue_exit(&ring);
goto cleanup_files;
}
// Test 4: Loop through files, update slots, read and verify
test_file_read_loop(&ring, iovs, filenames, expected_substrings, num_files,
&results);
// Cleanup
io_uring_unregister_files(&ring);
io_uring_unregister_buffers(&ring);
free(buffers);
io_uring_queue_exit(&ring);
cleanup_files:
// Remove test files
for (int i = 0; i < num_files; i++) {
remove(filenames[i]);
}
// Print summary
int total = results.passed + results.failed;
printf(COLOR_BLUE "\n========================================\n");
printf(" TEST SUMMARY\n");
printf("========================================\n" COLOR_RESET);
printf(" Total tests: %d\n", total);
printf(COLOR_GREEN " Passed: %d\n" COLOR_RESET, results.passed);
if (results.failed > 0) {
printf(COLOR_RED " Failed: %d\n" COLOR_RESET, results.failed);
printf(COLOR_RED "\n ✗ SOME TESTS FAILED!\n" COLOR_RESET);
} else {
printf(COLOR_GREEN "\n ✓ ALL TESTS PASSED!\n" COLOR_RESET);
}
return results.failed > 0 ? 1 : 0;
}

285
ioringapi.c Normal file
View File

@@ -0,0 +1,285 @@
#pragma once
#include <stdio.h>
#include <windows.h>
#include <winnt.h>
// Forward declarations
typedef struct IORING_HANDLE_REF IORING_HANDLE_REF;
typedef struct IORING_BUFFER_REF IORING_BUFFER_REF;
typedef void *HIORING;
/* --------------------- Types declaration --------------------- */
typedef enum IORING_CREATE_ADVISORY_FLAGS {
IORING_CREATE_ADVISORY_FLAGS_NONE,
IORING_CREATE_SKIP_BUILDER_PARAM_CHECKS
} IORING_CREATE_ADVISORY_FLAGS;
// Specifies advisory flags for creating an I/O ring with a call to
// CreateIoRing.
typedef enum IORING_CREATE_REQUIRED_FLAGS {
IORING_CREATE_REQUIRED_FLAGS_NONE
} IORING_CREATE_REQUIRED_FLAGS;
// Specifies required flags for creating an I/O ring with a call to
// CreateIoRing.
typedef enum IORING_REF_KIND {
IORING_REF_RAW = 0,
IORING_REF_REGISTERED = 1,
} IORING_REF_KIND;
// Specifies the type of an IORING_HANDLE_REF structure.
typedef enum IORING_SQE_FLAGS {
IOSQE_FLAGS_NONE,
IOSQE_FLAGS_DRAIN_PRECEDING_OPS
} IORING_SQE_FLAGS;
// Specifies kernel behavior options for I/O ring submission queue entries
// IORING_REGISTERED_BUFFER structure
typedef struct IORING_REGISTERED_BUFFER {
UINT32 Index;
UINT32 Offset;
} IORING_REGISTERED_BUFFER;
// IORING_HANDLE_REF
struct IORING_HANDLE_REF {
IORING_REF_KIND Kind;
union {
HANDLE Handle;
UINT32 Index;
} HandleUnion;
};
// Represents a reference to a file handle used in an I/O ring operation
// IORING_BUFFER_REF
struct IORING_BUFFER_REF {
IORING_REF_KIND Kind;
union {
void *Address;
IORING_REGISTERED_BUFFER IndexAndOffset;
} BufferUnion;
};
typedef struct IORING_BUFFER_INFO {
void *Address;
UINT32 Length;
} IORING_BUFFER_INFO;
// IORING_BUFFER_REF represents a reference to a buffer used in an I/O ring
// operation
// IORING_VERSION enumeration
typedef enum IORING_VERSION {
IORING_VERSION_INVALID = 0,
IORING_VERSION_1 = 1,
IORING_VERSION_2 = 2,
IORING_VERSION_3 = 3,
IORING_VERSION_4 = 4,
} IORING_VERSION;
typedef enum IORING_FEATURE_FLAGS {
IORING_FEATURE_FLAGS_NONE = 0,
IORING_FEATURE_UM_EMULATION = 1
} IORING_FEATURE_FLAGS;
// IORING_CAPABILITIES structure
typedef struct IORING_CAPABILITIES {
IORING_VERSION MaxVersion;
UINT32 MaxSubmissionQueueSize;
UINT32 MaxCompletionQueueSize;
IORING_FEATURE_FLAGS FeatureFlags;
} IORING_CAPABILITIES;
// Represents the IORING API capabilities.
// IORING_CQE structure
typedef struct IORING_CQE {
UINT_PTR UserData;
HRESULT ResultCode;
ULONG_PTR Information;
} IORING_CQE;
// Represents a completed I/O ring queue entry.
// IORING_CREATE_FLAGS structure
typedef struct IORING_CREATE_FLAGS {
IORING_CREATE_REQUIRED_FLAGS Required;
IORING_CREATE_ADVISORY_FLAGS Advisory;
} IORING_CREATE_FLAGS;
// Specifies flags for creating an I/O ring with a call to CreateIoRing.
// IORING_INFO structure
typedef struct IORING_INFO {
IORING_VERSION IoRingVersion;
IORING_CREATE_FLAGS Flags;
UINT32 SubmissionQueueSize;
UINT32 CompletionQueueSize;
} IORING_INFO;
// Represents the shape and version information for the specified I/O ring
// IORING_OP_CODE for IsIoRingOpSupported
typedef enum IORING_OP_CODE {
IORING_OP_NOP = 0,
IORING_OP_READ = 1,
IORING_OP_WRITE = 2,
IORING_OP_FLUSH = 3,
IORING_OP_REGISTER_BUFFERS = 4,
IORING_OP_REGISTER_FILES = 5,
IORING_OP_CANCEL = 6,
} IORING_OP_CODE;
/* --------------------- Dynamic loader --------------------- */
// Function pointer types
typedef BOOL(WINAPI *IsIoRingOpSupported_t)(HIORING, IORING_OP_CODE);
typedef HRESULT(WINAPI *QueryIoRingCapabilities_t)(IORING_CAPABILITIES *);
typedef HRESULT(WINAPI *GetIoRingInfo_t)(HIORING, IORING_INFO *);
typedef HRESULT(WINAPI *CreateIoRing_t)(IORING_VERSION, IORING_CREATE_FLAGS,
UINT32, UINT32, HIORING *);
typedef HRESULT(WINAPI *CloseIoRing_t)(HIORING);
typedef HRESULT(WINAPI *SubmitIoRing_t)(HIORING, UINT32, UINT32, UINT32 *);
typedef HRESULT(WINAPI *PopIoRingCompletion_t)(HIORING, IORING_CQE *);
typedef HRESULT(WINAPI *SetIoRingCompletionEvent_t)(HIORING, HANDLE);
typedef HRESULT(WINAPI *BuildIoRingCancelRequest_t)(HIORING, IORING_HANDLE_REF,
UINT_PTR, UINT_PTR);
typedef HRESULT(WINAPI *BuildIoRingReadFile_t)(HIORING, IORING_HANDLE_REF,
IORING_BUFFER_REF, UINT32,
UINT64, UINT_PTR,
IORING_SQE_FLAGS);
typedef HRESULT(WINAPI *BuildIoRingRegisterBuffers_t)(
HIORING, UINT32, IORING_BUFFER_INFO const[], UINT_PTR);
typedef HRESULT(WINAPI *BuildIoRingRegisterFileHandles_t)(HIORING, UINT32,
HANDLE const[],
UINT_PTR);
// Core:
// Queries the support of the specified operation for the specified I/O ring
static IsIoRingOpSupported_t IsIoRingOpSupported = NULL;
// Queries the OS for the supported capabilities for IORINGs
static QueryIoRingCapabilities_t QueryIoRingCapabilities = NULL;
// Gets information about the API version and queue sizes of an I/O ring
static GetIoRingInfo_t GetIoRingInfo = NULL;
// Creates a new instance of an I/O ring submission/completion queue pair and
// returns a handle for referencing the I/O ring
static CreateIoRing_t CreateIoRing = NULL;
// Closes an HIORING handle that was previously opened with a call to
// CreateIoRing
static CloseIoRing_t CloseIoRing = NULL;
// Submission / completion:
// Submits all constructed but not yet submitted entries to the kernels queue
// and optionally waits for a set of operations to complete
static SubmitIoRing_t SubmitIoRing = NULL;
// Pops a single entry from the completion queue, if one is available
static PopIoRingCompletion_t PopIoRingCompletion = NULL;
// Registers a completion queue event with an IORING
static SetIoRingCompletionEvent_t SetIoRingCompletionEvent = NULL;
// Operations:
// Performs an asynchronous read from a file using an I/O ring
static BuildIoRingReadFile_t BuildIoRingReadFile = NULL;
// Attempts to cancel a previously submitted I/O ring operation
static BuildIoRingCancelRequest_t BuildIoRingCancelRequest = NULL;
// Registers an array of buffers with the system for future I/O ring operations
static BuildIoRingRegisterBuffers_t BuildIoRingRegisterBuffers = NULL;
// Registers an array of file handles with the system for future I/O ring
// operations
static BuildIoRingRegisterFileHandles_t BuildIoRingRegisterFileHandles = NULL;
static int io_ring_loaded = 0;
static int io_ring_load_functions(void) {
if (io_ring_loaded)
return 1;
HMODULE hKernel = GetModuleHandleW(L"kernel32.dll");
if (!hKernel)
return 0;
IsIoRingOpSupported =
(IsIoRingOpSupported_t)GetProcAddress(hKernel, "IsIoRingOpSupported");
QueryIoRingCapabilities = (QueryIoRingCapabilities_t)GetProcAddress(
hKernel, "QueryIoRingCapabilities");
GetIoRingInfo = (GetIoRingInfo_t)GetProcAddress(hKernel, "GetIoRingInfo");
CreateIoRing = (CreateIoRing_t)GetProcAddress(hKernel, "CreateIoRing");
CloseIoRing = (CloseIoRing_t)GetProcAddress(hKernel, "CloseIoRing");
SubmitIoRing = (SubmitIoRing_t)GetProcAddress(hKernel, "SubmitIoRing");
PopIoRingCompletion =
(PopIoRingCompletion_t)GetProcAddress(hKernel, "PopIoRingCompletion");
SetIoRingCompletionEvent = (SetIoRingCompletionEvent_t)GetProcAddress(
hKernel, "SetIoRingCompletionEvent");
BuildIoRingReadFile =
(BuildIoRingReadFile_t)GetProcAddress(hKernel, "BuildIoRingReadFile");
BuildIoRingCancelRequest = (BuildIoRingCancelRequest_t)GetProcAddress(
hKernel, "BuildIoRingCancelRequest");
BuildIoRingRegisterBuffers = (BuildIoRingRegisterBuffers_t)GetProcAddress(
hKernel, "BuildIoRingRegisterBuffers");
BuildIoRingRegisterFileHandles =
(BuildIoRingRegisterFileHandles_t)GetProcAddress(
hKernel, "BuildIoRingRegisterFileHandles");
io_ring_loaded =
(IsIoRingOpSupported && QueryIoRingCapabilities && CreateIoRing &&
CloseIoRing && SubmitIoRing && PopIoRingCompletion &&
SetIoRingCompletionEvent && BuildIoRingReadFile &&
BuildIoRingCancelRequest && BuildIoRingRegisterBuffers &&
BuildIoRingRegisterFileHandles);
if (io_ring_loaded)
printf("[I/O Ring] Functions loaded\n");
else
printf("[I/O Ring] Some functions not available\n");
return io_ring_loaded;
}
/* ------------- Standard helper functions definition ------------- */
// Creates an instance of the IORING_BUFFER_REF structure with the provided
// buffer index and offset
static inline IORING_BUFFER_REF
IoRingBufferRefFromIndexAndOffset(UINT32 index, UINT32 offset) {
IORING_BUFFER_REF ref;
ref.Kind = IORING_REF_REGISTERED;
ref.BufferUnion.IndexAndOffset.Index = index;
ref.BufferUnion.IndexAndOffset.Offset = offset;
return ref;
}
// Creates an instance of the IORING_BUFFER_REF structure from the provided
// pointer
static IORING_BUFFER_REF IoRingBufferRefFromPointer(void *addr) {
IORING_BUFFER_REF ref;
ref.Kind = IORING_REF_RAW;
ref.BufferUnion.Address = addr;
return ref;
}
// Creates an instance of the IORING_HANDLE_REF structure from the provided file
// handle
static IORING_HANDLE_REF IoRingHandleRefFromHandle(HANDLE h) {
IORING_HANDLE_REF ref;
ref.Kind = IORING_REF_RAW;
ref.HandleUnion.Handle = h;
return ref;
}
// Creates an instance of the IORING_HANDLE_REF structure from the provided
// index
static inline IORING_HANDLE_REF IoRingHandleRefFromIndex(UINT32 index) {
IORING_HANDLE_REF ref;
ref.Kind = IORING_REF_REGISTERED; // MUST be registered
ref.HandleUnion.Index = index;
return ref;
}
// NOTE: If you are using index-based buffers or handles, make sure you have
// successfully called BuildIoRingRegisterBuffers or
// BuildIoRingRegisterFileHandles first so the kernel has a valid table to look
// into, otherwise the kernel will treat the index as an invalid memory
// address/handle.

1548
platform.c

File diff suppressed because it is too large Load Diff