Project reordering and mpmc code
This commit is contained in:
147
experiments/io_ring_test.c
Normal file
147
experiments/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");
|
||||
}
|
||||
721
experiments/io_uring_test.c
Normal file
721
experiments/io_uring_test.c
Normal file
@@ -0,0 +1,721 @@
|
||||
/*
|
||||
# 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 TEST_FILE "test_io_uring.txt"
|
||||
#define BUFFER_SIZE 4096
|
||||
#define NUM_BUFFERS 4
|
||||
#define NUM_REGISTERED_FILES 8 // Maximum number of files to register
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Create a test file with known content
|
||||
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");
|
||||
|
||||
// Allocate and prepare buffers
|
||||
size_t total_size = BUFFER_SIZE * NUM_BUFFERS;
|
||||
*buffers = aligned_alloc(4096, total_size); // Page-aligned for O_DIRECT
|
||||
if (!*buffers) {
|
||||
print_failure("Buffer allocation", strerror(errno));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Initialize iovecs
|
||||
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 2b: Register files
|
||||
static int test_register_files(struct io_uring *ring, int *fds, int num_fds,
|
||||
TestResults *results) {
|
||||
print_step("File registration");
|
||||
|
||||
if (num_fds == 0) {
|
||||
print_info("No files to register");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ret = io_uring_register_files(ring, fds, num_fds);
|
||||
if (ret < 0) {
|
||||
// File registration might not be supported on all kernels
|
||||
if (ret == -EOPNOTSUPP || ret == -EINVAL) {
|
||||
print_info("File registration not supported on this kernel, skipping");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
print_failure("io_uring_register_files", strerror(-ret));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" Registered %d files\n", num_fds);
|
||||
print_success("Files registered successfully");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 3: Open file
|
||||
static int test_open_file(const char *filename, int *fd, bool use_direct,
|
||||
TestResults *results) {
|
||||
print_step("File opening");
|
||||
|
||||
// Get file size
|
||||
struct stat st;
|
||||
if (stat(filename, &st) != 0) {
|
||||
print_failure("stat", strerror(errno));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int page_size = plat_get_pagesize();
|
||||
size_t file_size = st.st_size;
|
||||
|
||||
printf(" File: %s\n", filename);
|
||||
printf(" File size: %zu bytes\n", file_size);
|
||||
printf(" Page size: %d bytes\n", page_size);
|
||||
|
||||
if (file_size % page_size != 0) {
|
||||
printf(" Extending read size from %zu to %zu bytes\n", file_size,
|
||||
ALIGN_UP_POW2(file_size, page_size));
|
||||
}
|
||||
|
||||
// Try to open with specified flags
|
||||
int flags = O_RDONLY;
|
||||
if (use_direct) {
|
||||
flags |= O_DIRECT;
|
||||
}
|
||||
|
||||
*fd = open(filename, flags);
|
||||
if (*fd < 0) {
|
||||
if (use_direct) {
|
||||
print_info("O_DIRECT failed, trying without it");
|
||||
*fd = open(filename, O_RDONLY);
|
||||
if (*fd < 0) {
|
||||
print_failure("open", strerror(errno));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
print_info("Using buffered I/O (O_DIRECT not available)");
|
||||
} else {
|
||||
print_failure("open", strerror(errno));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
const char *io_type = use_direct ? "O_DIRECT" : "buffered I/O";
|
||||
printf(" File opened with %s\n", io_type);
|
||||
print_success("File opened successfully");
|
||||
}
|
||||
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 4: Build and submit read operation (using registered file)
|
||||
static int test_submit_read_registered(struct io_uring *ring, int file_index,
|
||||
struct iovec *iovs, int buffer_id,
|
||||
uint64_t user_data, size_t file_size,
|
||||
TestResults *results) {
|
||||
print_step("Building and submitting read operation (registered file)");
|
||||
|
||||
u32 page_size = plat_get_pagesize();
|
||||
size_t read_size = BUFFER_SIZE;
|
||||
|
||||
// For O_DIRECT, ensure read size is sector-aligned
|
||||
if (read_size > file_size) {
|
||||
read_size = ALIGN_UP_POW2(file_size, page_size);
|
||||
printf(" Adjusted read size to %zu bytes for O_DIRECT alignment\n",
|
||||
read_size);
|
||||
}
|
||||
|
||||
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
|
||||
if (!sqe) {
|
||||
print_failure("io_uring_get_sqe", "No available SQE");
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use fixed file descriptor
|
||||
io_uring_prep_read_fixed(sqe, file_index, iovs[buffer_id].iov_base, read_size,
|
||||
0, buffer_id);
|
||||
io_uring_sqe_set_data64(sqe, user_data);
|
||||
|
||||
int ret = io_uring_submit(ring);
|
||||
if (ret < 0) {
|
||||
print_failure("io_uring_submit", strerror(-ret));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" Using registered file index: %d\n", file_index);
|
||||
print_success("Read operation submitted successfully (registered file)");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 4b: Build and submit read operation (using fd directly)
|
||||
static int test_submit_read(struct io_uring *ring, int fd, struct iovec *iovs,
|
||||
int buffer_id, uint64_t user_data,
|
||||
TestResults *results) {
|
||||
print_step("Building and submitting read operation");
|
||||
|
||||
// Get file size for proper alignment
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0) {
|
||||
print_failure("fstat", strerror(errno));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 page_size = plat_get_pagesize();
|
||||
size_t file_size = st.st_size;
|
||||
size_t read_size = BUFFER_SIZE;
|
||||
|
||||
// For O_DIRECT, ensure read size is sector-aligned
|
||||
if (read_size > file_size) {
|
||||
read_size = ALIGN_UP_POW2(file_size, page_size);
|
||||
printf(" Adjusted read size to %zu bytes for O_DIRECT alignment\n",
|
||||
read_size);
|
||||
}
|
||||
|
||||
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
|
||||
if (!sqe) {
|
||||
print_failure("io_uring_get_sqe", "No available SQE");
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Prepare read operation using registered buffer
|
||||
io_uring_prep_read_fixed(sqe, fd, iovs[buffer_id].iov_base, read_size, 0,
|
||||
buffer_id);
|
||||
io_uring_sqe_set_data64(sqe, user_data);
|
||||
|
||||
int ret = io_uring_submit(ring);
|
||||
if (ret < 0) {
|
||||
print_failure("io_uring_submit", strerror(-ret));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
print_success("Read operation submitted successfully");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 5: Wait for completion
|
||||
static int test_wait_completion(struct io_uring *ring,
|
||||
struct io_uring_cqe **cqe,
|
||||
TestResults *results) {
|
||||
print_step("Waiting for completion");
|
||||
|
||||
int ret = io_uring_wait_cqe(ring, cqe);
|
||||
if (ret < 0) {
|
||||
print_failure("io_uring_wait_cqe", strerror(-ret));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
print_success("Completion received");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 6: Process completion
|
||||
static int test_process_completion(struct io_uring_cqe *cqe,
|
||||
uint64_t expected_user_data,
|
||||
TestResults *results) {
|
||||
print_step("Processing completion");
|
||||
|
||||
uint64_t user_data = io_uring_cqe_get_data64(cqe);
|
||||
int res = cqe->res;
|
||||
|
||||
printf(" Completion data:\n");
|
||||
printf(" User data: %lu (expected: %lu)\n", user_data, expected_user_data);
|
||||
printf(" Result: %d bytes read\n", res);
|
||||
|
||||
if (user_data != expected_user_data) {
|
||||
print_failure("User data mismatch",
|
||||
"User data doesn't match expected value");
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (res < 0) {
|
||||
print_failure("Read operation", strerror(-res));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
print_success("Completion processed successfully");
|
||||
results->passed++;
|
||||
return res; // Return number of bytes read
|
||||
}
|
||||
|
||||
// Test 7: Verify read data
|
||||
static int test_verify_data(struct iovec *iovs, int buffer_id, int bytes_read,
|
||||
const char *expected_content,
|
||||
TestResults *results) {
|
||||
print_step("Data verification");
|
||||
|
||||
char *data = (char *)iovs[buffer_id].iov_base;
|
||||
|
||||
printf(" Read data (first 200 chars):\n");
|
||||
printf(" ---\n");
|
||||
for (int i = 0; i < bytes_read && i < 200; i++) {
|
||||
putchar(data[i]);
|
||||
}
|
||||
if (bytes_read > 200)
|
||||
printf("...");
|
||||
printf("\n ---\n");
|
||||
|
||||
// Check if data is not empty
|
||||
if (bytes_read == 0) {
|
||||
print_failure("Data verification", "No data read");
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if data contains expected content
|
||||
if (expected_content && strstr(data, expected_content) == NULL) {
|
||||
print_failure("Data verification", "Expected content not found");
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
print_success("Data verified successfully");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 8: Test multiple concurrent reads
|
||||
static int test_concurrent_reads(struct io_uring *ring, int fd,
|
||||
struct iovec *iovs, TestResults *results) {
|
||||
print_step("Concurrent reads test");
|
||||
|
||||
int num_reads = 3;
|
||||
int submitted = 0;
|
||||
|
||||
// Submit multiple reads
|
||||
for (int i = 0; i < num_reads; i++) {
|
||||
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
|
||||
if (!sqe) {
|
||||
print_failure("Getting SQE for concurrent read", "No available SQE");
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
off_t offset = i * 100; // Read from different offsets
|
||||
io_uring_prep_read_fixed(sqe, fd, iovs[i].iov_base, BUFFER_SIZE, offset, i);
|
||||
io_uring_sqe_set_data64(sqe, i);
|
||||
submitted++;
|
||||
}
|
||||
|
||||
int ret = io_uring_submit(ring);
|
||||
if (ret != submitted) {
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "Expected %d, got %d", submitted, ret);
|
||||
|
||||
print_failure("Submitting concurrent reads", msg);
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
print_success("Concurrent reads submitted");
|
||||
|
||||
// Wait for and process completions
|
||||
for (int i = 0; i < submitted; i++) {
|
||||
struct io_uring_cqe *cqe;
|
||||
ret = io_uring_wait_cqe(ring, &cqe);
|
||||
if (ret < 0) {
|
||||
print_failure("Waiting for concurrent read completion", strerror(-ret));
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t user_data = io_uring_cqe_get_data64(cqe);
|
||||
int res = cqe->res;
|
||||
|
||||
printf(" Concurrent read %lu completed: %d bytes read\n", user_data, res);
|
||||
io_uring_cqe_seen(ring, cqe);
|
||||
}
|
||||
|
||||
print_success("Concurrent reads completed successfully");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Test 9: Test file registration with multiple files
|
||||
static int test_file_registration(struct io_uring *ring, TestResults *results) {
|
||||
print_step("File registration with multiple files");
|
||||
|
||||
// Create multiple test files
|
||||
const char *filenames[] = {"test_file1.txt", "test_file2.txt",
|
||||
"test_file3.txt"};
|
||||
const char *contents[] = {"Content of file 1: Hello World!",
|
||||
"Content of file 2: io_uring is fast!",
|
||||
"Content of file 3: Registered files test."};
|
||||
|
||||
int fds[3];
|
||||
int num_files = 3;
|
||||
|
||||
// Create and open files
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
if (create_test_file(filenames[i], contents[i]) != 0) {
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
fds[i] = open(filenames[i], O_RDONLY);
|
||||
if (fds[i] < 0) {
|
||||
print_failure("Opening file for registration", strerror(errno));
|
||||
// Close previously opened files
|
||||
for (int j = 0; j < i; j++)
|
||||
close(fds[j]);
|
||||
results->failed++;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Register files
|
||||
int ret = io_uring_register_files(ring, fds, num_files);
|
||||
if (ret < 0) {
|
||||
if (ret == -EOPNOTSUPP || ret == -EINVAL) {
|
||||
print_info("File registration not supported, skipping test");
|
||||
results->passed++;
|
||||
} else {
|
||||
print_failure("io_uring_register_files", strerror(-ret));
|
||||
results->failed++;
|
||||
}
|
||||
// Cleanup
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
close(fds[i]);
|
||||
remove(filenames[i]);
|
||||
}
|
||||
return (ret == -EOPNOTSUPP || ret == -EINVAL) ? 0 : -1;
|
||||
}
|
||||
|
||||
print_success("Multiple files registered successfully");
|
||||
|
||||
// Read from each registered file using fixed operations
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
struct iovec iov;
|
||||
char buf[256] = {0};
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = sizeof(buf);
|
||||
|
||||
// Register a single buffer for this test
|
||||
ret = io_uring_register_buffers(ring, &iov, 1);
|
||||
if (ret < 0) {
|
||||
print_failure("Registering buffer for file test", strerror(-ret));
|
||||
break;
|
||||
}
|
||||
|
||||
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
|
||||
if (!sqe) {
|
||||
print_failure("Getting SQE for registered file", "No available SQE");
|
||||
break;
|
||||
}
|
||||
|
||||
// Use fixed file and fixed buffer
|
||||
io_uring_prep_read_fixed(sqe, i, iov.iov_base, strlen(contents[i]), 0, 0);
|
||||
io_uring_sqe_set_data64(sqe, i);
|
||||
|
||||
ret = io_uring_submit(ring);
|
||||
if (ret < 0) {
|
||||
print_failure("Submitting read for registered file", strerror(-ret));
|
||||
break;
|
||||
}
|
||||
|
||||
struct io_uring_cqe *cqe;
|
||||
ret = io_uring_wait_cqe(ring, &cqe);
|
||||
if (ret < 0) {
|
||||
print_failure("Waiting for registered file read", strerror(-ret));
|
||||
break;
|
||||
}
|
||||
|
||||
if (cqe->res < 0) {
|
||||
print_failure("Reading registered file", strerror(-cqe->res));
|
||||
io_uring_cqe_seen(ring, cqe);
|
||||
break;
|
||||
}
|
||||
|
||||
printf(" File %d: Read %d bytes: %.*s\n", i, cqe->res, cqe->res, buf);
|
||||
io_uring_cqe_seen(ring, cqe);
|
||||
|
||||
// Unregister buffer for next iteration
|
||||
io_uring_unregister_buffers(ring);
|
||||
}
|
||||
|
||||
// Cleanup files
|
||||
io_uring_unregister_files(ring);
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
close(fds[i]);
|
||||
remove(filenames[i]);
|
||||
}
|
||||
|
||||
print_success("File registration test completed");
|
||||
results->passed++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
static void cleanup(struct io_uring *ring, int *fds, int num_fds,
|
||||
void *buffers) {
|
||||
if (fds) {
|
||||
io_uring_unregister_files(ring);
|
||||
for (int i = 0; i < num_fds; i++) {
|
||||
if (fds[i] >= 0)
|
||||
close(fds[i]);
|
||||
}
|
||||
}
|
||||
if (buffers) {
|
||||
io_uring_unregister_buffers(ring);
|
||||
free(buffers);
|
||||
}
|
||||
io_uring_queue_exit(ring);
|
||||
remove(TEST_FILE);
|
||||
}
|
||||
|
||||
int main() {
|
||||
TestResults results = {0, 0};
|
||||
struct io_uring ring;
|
||||
int fd = -1;
|
||||
int registered_fds[1] = {-1}; // For registered file test
|
||||
void *buffers = NULL;
|
||||
struct iovec iovs[NUM_BUFFERS];
|
||||
|
||||
printf(COLOR_BLUE "\n========================================\n");
|
||||
printf(" io_uring Test Suite with File Registration\n");
|
||||
printf("========================================\n" COLOR_RESET);
|
||||
|
||||
// Create main test file
|
||||
const char *test_content =
|
||||
"Hello, io_uring! This is a test file for async I/O operations.\n"
|
||||
"Line 2: Testing reads with registered buffers.\n"
|
||||
"Line 3: The quick brown fox jumps over the lazy dog.\n"
|
||||
"Line 4: ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"
|
||||
"Line 5: 0123456789\n";
|
||||
|
||||
if (create_test_file(TEST_FILE, test_content) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 1: Create io_uring
|
||||
if (test_io_uring_create(&ring, &results) != 0) {
|
||||
cleanup(&ring, NULL, 0, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 2: Register buffers
|
||||
if (test_register_buffers(&ring, &buffers, iovs, &results) != 0) {
|
||||
cleanup(&ring, NULL, 0, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 3: Open file
|
||||
if (test_open_file(TEST_FILE, &fd, true, &results) != 0) {
|
||||
cleanup(&ring, NULL, 0, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 4: Submit read with direct fd
|
||||
uint64_t test_user_data = 12345;
|
||||
if (test_submit_read(&ring, fd, iovs, 0, test_user_data, &results) != 0) {
|
||||
cleanup(&ring, NULL, 0, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 5: Wait for completion
|
||||
struct io_uring_cqe *cqe;
|
||||
if (test_wait_completion(&ring, &cqe, &results) != 0) {
|
||||
cleanup(&ring, NULL, 0, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 6: Process completion
|
||||
int bytes_read = test_process_completion(cqe, test_user_data, &results);
|
||||
if (bytes_read < 0) {
|
||||
cleanup(&ring, NULL, 0, buffers);
|
||||
return 1;
|
||||
}
|
||||
io_uring_cqe_seen(&ring, cqe);
|
||||
|
||||
// Test 7: Verify data
|
||||
if (test_verify_data(iovs, 0, bytes_read, "io_uring", &results) != 0) {
|
||||
cleanup(&ring, NULL, 0, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Close the file for file registration test
|
||||
close(fd);
|
||||
|
||||
// Reopen and register the file
|
||||
registered_fds[0] = open(TEST_FILE, O_RDONLY);
|
||||
if (registered_fds[0] < 0) {
|
||||
print_failure("Reopening file for registration", strerror(errno));
|
||||
cleanup(&ring, NULL, 0, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 2b: Register files
|
||||
if (test_register_files(&ring, registered_fds, 1, &results) != 0) {
|
||||
cleanup(&ring, registered_fds, 1, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get file size for the registered read test
|
||||
struct stat st;
|
||||
stat(TEST_FILE, &st);
|
||||
|
||||
// Test 4b: Submit read using registered file
|
||||
test_user_data = 67890;
|
||||
if (test_submit_read_registered(&ring, 0, iovs, 0, test_user_data, st.st_size,
|
||||
&results) != 0) {
|
||||
cleanup(&ring, registered_fds, 1, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Wait for and process completion
|
||||
if (test_wait_completion(&ring, &cqe, &results) != 0) {
|
||||
cleanup(&ring, registered_fds, 1, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bytes_read = test_process_completion(cqe, test_user_data, &results);
|
||||
if (bytes_read < 0) {
|
||||
cleanup(&ring, registered_fds, 1, buffers);
|
||||
return 1;
|
||||
}
|
||||
io_uring_cqe_seen(&ring, cqe);
|
||||
|
||||
// Verify data from registered file read
|
||||
if (test_verify_data(iovs, 0, bytes_read, "io_uring", &results) != 0) {
|
||||
cleanup(&ring, registered_fds, 1, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 8: Concurrent reads
|
||||
if (test_concurrent_reads(&ring, registered_fds[0], iovs, &results) != 0) {
|
||||
cleanup(&ring, registered_fds, 1, buffers);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Test 9: File registration with multiple files (requires new ring)
|
||||
cleanup(&ring, registered_fds, 1, buffers);
|
||||
buffers = NULL;
|
||||
registered_fds[0] = -1;
|
||||
|
||||
if (test_io_uring_create(&ring, &results) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
test_file_registration(&ring, &results);
|
||||
|
||||
// Cleanup the second ring
|
||||
io_uring_queue_exit(&ring);
|
||||
|
||||
// Print summary
|
||||
printf(COLOR_BLUE "\n========================================\n");
|
||||
printf(" TEST SUMMARY\n");
|
||||
printf("========================================\n" COLOR_RESET);
|
||||
printf(" Total tests: %d\n", results.passed + results.failed);
|
||||
printf(COLOR_GREEN " Passed: %d\n" COLOR_RESET, results.passed);
|
||||
if (results.failed > 0) {
|
||||
printf(COLOR_RED " Failed: %d\n" COLOR_RESET, results.failed);
|
||||
} else {
|
||||
printf(COLOR_GREEN " ✓ ALL TESTS PASSED!\n" COLOR_RESET);
|
||||
}
|
||||
|
||||
return results.failed > 0 ? 1 : 0;
|
||||
}
|
||||
397
experiments/io_uring_test2.c
Normal file
397
experiments/io_uring_test2.c
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
# Compile
|
||||
gcc -o io_uring_test io_uring_test2.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
experiments/ioringapi.c
Normal file
285
experiments/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.
|
||||
Reference in New Issue
Block a user