Merge branch 'io_ring'
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
233
README.md
233
README.md
@@ -1,24 +1,221 @@
|
|||||||
# 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 that blocks until the table is set up. Asynchronous request submitted to the ring just like a read/write operation.
|
||||||
|
Partial Updates Supports IORING_REGISTER_FILES_UPDATE to swap specific indices without a full reset. Does not support partial updates; a new registration call replaces the entire existing table.
|
||||||
|
Memory Mapping User must manually mmap() the queues into their address space. The kernel handles memory mapping automatically when the ring is created.
|
||||||
|
Scope of Operations Extremely broad (files, sockets, timers, signals, even other rings). 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
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|||||||
6
arena.c
6
arena.c
@@ -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
59
base.h
@@ -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
|
||||||
------------------------------------------------------------ */
|
------------------------------------------------------------ */
|
||||||
|
|||||||
@@ -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
31
config.h
Normal 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
|
||||||
@@ -74,21 +74,44 @@ int main(int argc, char **argv) {
|
|||||||
mem_arena *gp_arena = arena_create(¶ms);
|
mem_arena *gp_arena = arena_create(¶ms);
|
||||||
|
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
// 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(¶ms);
|
workers[i].arena = arena_create(¶ms);
|
||||||
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
147
io_ring_test.c
Normal 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
397
io_uring_test.c
Normal 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
285
ioringapi.c
Normal 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 kernel’s 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.
|
||||||
1518
platform.c
1518
platform.c
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user