Swap old implementation with rewrite

This commit is contained in:
2024-04-09 22:42:30 +02:00
parent dfffb872fb
commit a56170aa7b
7 changed files with 858 additions and 1751 deletions
+2
View File
@@ -0,0 +1,2 @@
gcc example.c io.c -o example -Wall -Wextra -ggdb
gcc example2.c io.c -o example2 -Wall -Wextra -ggdb
+1 -1
View File
@@ -1,5 +1,5 @@
#include <stdio.h> #include <stdio.h>
#include "io2.h" #include "io.h"
#define NUM_OPS 3 #define NUM_OPS 3
+775 -532
View File
File diff suppressed because it is too large Load Diff
+80 -111
View File
@@ -1,45 +1,35 @@
#include <stdint.h>
#include <stdbool.h>
#define IO_VERSION_MAJOR 0 #define IO_VERSION_MAJOR 0
#define IO_VERSION_MINOR 0 #define IO_VERSION_MINOR 0
# ifdef _WIN32 #ifdef _WIN32
# define IO_PLATFORM_WINDOWS 1 # define IO_PLATFORM_WINDOWS 1
# define IO_PLATFORM_LINUX 0 # define IO_PLATFORM_LINUX 0
# define IO_PLATFORM_OTHER 0 # define IO_PLATFORM_OTHER 0
# elif __linux__ #elif __linux__
# define IO_PLATFORM_WINDOWS 0 # define IO_PLATFORM_WINDOWS 0
# define IO_PLATFORM_LINUX 1 # define IO_PLATFORM_LINUX 1
# define IO_PLATFORM_OTHER 0 # define IO_PLATFORM_OTHER 0
# else #else
# define IO_PLATFORM_WINDOWS 0 # define IO_PLATFORM_WINDOWS 0
# define IO_PLATFORM_LINUX 0 # define IO_PLATFORM_LINUX 0
# define IO_PLATFORM_OTHER 1 # define IO_PLATFORM_OTHER 1
# endif
#include <stdint.h>
#include <stdbool.h>
#if IO_PLATFORM_LINUX
#include <stdatomic.h>
#include <linux/io_uring.h>
#endif #endif
/*
* The OS handle type.
*/
#if IO_PLATFORM_LINUX
typedef int io_raw_handle;
#elif IO_PLATFORM_WINDOWS
typedef void *io_raw_handle;
#endif
typedef uint16_t io_handle;
/*
* Windows calls this structure OVERLAPPED
*/
#if IO_PLATFORM_WINDOWS #if IO_PLATFORM_WINDOWS
struct io_overlap { typedef void *io_os_handle;
#endif
#if IO_PLATFORM_LINUX
typedef int io_os_handle;
#endif
typedef uint32_t io_handle;
#define IO_INVALID ((uint32_t) -1)
struct io_os_overlap {
unsigned long *internal; unsigned long *internal;
unsigned long *internal_high; unsigned long *internal_high;
union { union {
@@ -51,19 +41,6 @@ struct io_overlap {
}; };
void *event; void *event;
}; };
#endif
enum io_resource_type {
IO_RES_VOID,
IO_RES_FILE,
IO_RES_SOCKET,
};
struct io_resource {
enum io_resource_type type;
io_raw_handle raw_handle;
uint16_t headop;
};
enum io_optype { enum io_optype {
IO_VOID, IO_VOID,
@@ -76,15 +53,30 @@ enum io_optype {
struct io_operation { struct io_operation {
io_handle handle;
uint16_t nextop;
enum io_optype type; enum io_optype type;
void *user; struct io_resource *res;
void *user;
#if IO_PLATFORM_WINDOWS #if IO_PLATFORM_WINDOWS
io_raw_handle accept_handle; io_os_handle accepted;
struct io_overlap ov; struct io_os_overlap ov;
#endif
};
enum io_restype {
IO_RES_VOID,
IO_RES_FILE,
IO_RES_SOCKET,
};
struct io_resource {
enum io_restype type;
io_os_handle os_handle;
uint16_t pending;
uint16_t gen;
#if IO_PLATFORM_WINDOWS
void *acceptfn;
char accept_buffer[2 * (IO_SOCKADDR_IN_SIZE + 16)]; char accept_buffer[2 * (IO_SOCKADDR_IN_SIZE + 16)];
#endif #endif
}; };
@@ -117,12 +109,11 @@ struct io_completion_queue {
#endif #endif
struct io_context { struct io_context {
io_os_handle os_handle;
io_raw_handle raw_handle;
uint16_t max_ops;
uint16_t max_res; uint16_t max_res;
uint16_t max_ops;
struct io_resource *res;
struct io_operation *ops; struct io_operation *ops;
struct io_resource *res;
#if IO_PLATFORM_LINUX #if IO_PLATFORM_LINUX
struct io_submission_queue submissions; struct io_submission_queue submissions;
@@ -130,70 +121,54 @@ struct io_context {
#endif #endif
}; };
struct io_event { enum io_evtype {
bool error; IO_ERROR,
void *user; IO_ABORT,
enum io_optype type; IO_COMPLETE,
io_handle handle;
/*
* Operation-specific results
*/
union {
uint32_t num; // recv, send
io_handle handle; // accept
} data;
}; };
struct io_event {
enum io_evtype evtype;
enum io_optype optype;
io_handle handle;
void *user;
union {
uint32_t num;
io_handle accepted;
};
};
bool io_global_init(void); bool io_global_init(void);
void io_global_free(void); void io_global_free(void);
/* bool io_init(struct io_context *ioc,
* Initialize an I/O context struct io_resource *res,
*/ struct io_operation *ops,
bool io_context_init(struct io_context *ioc, uint16_t max_res,
struct io_resource *res, uint16_t max_ops);
struct io_operation *ops,
uint16_t max_res,
uint16_t max_ops);
/* void io_free(struct io_context *ioc);
* Deinitialize an I/O context. This will not close any previously
* created handles.
*/
void io_context_free(struct io_context *ioc);
/*
* Start an asynchronous receive operation on the handle.
* Only one pending receive operation per handle is supported.
*
* When the operation completes, one of the following calls to
* "io_wait" will return a completion event associated to this
* recv. The "num" field of the event will hold the number of
* bytes actually written from "dst".
*/
bool io_start_recv(struct io_context *ioc, io_handle handle,
void *dst, uint32_t max);
/*
* Works like "io_start_recv" but for sending.
*/
bool io_start_send(struct io_context *ioc, io_handle handle,
void *src, uint32_t num);
bool io_start_accept(struct io_context *ioc, io_handle handle);
/*
* Wait for the completion of an I/O event.
*/
void io_wait(struct io_context *ioc, void io_wait(struct io_context *ioc,
struct io_event *ev); struct io_event *ev);
bool io_recv(struct io_context *ioc,
void *user, io_handle handle,
void *dsc, uint32_t max);
bool io_send(struct io_context *ioc,
void *user, io_handle handle,
void *src, uint32_t num);
bool io_accept(struct io_context *ioc,
void *user, io_handle handle);
void io_close(struct io_context *ioc,
io_handle handle);
/* /*
* Flags for "io_open_file" and "io_create_file" * Flags for io_open_file and io_create_file
*/ */
enum { enum {
IO_ACCESS_RD = 1 << 0, IO_ACCESS_RD = 1 << 0,
@@ -203,16 +178,10 @@ enum {
}; };
io_handle io_open_file(struct io_context *ioc, io_handle io_open_file(struct io_context *ioc,
const char *name, int flags, const char *file, int flags);
void *user);
io_handle io_create_file(struct io_context *ioc, io_handle io_create_file(struct io_context *ioc,
const char *name, int flags, const char *file, int flags);
void *user);
io_handle io_listen(struct io_context *ioc, io_handle io_start_server(struct io_context *ioc,
const char *addr, int port, const char *addr, int port);
void *user);
void io_close(struct io_context *ioc,
io_handle handle);
-946
View File
@@ -1,946 +0,0 @@
#include "io2.h"
#include <assert.h>
#if IO_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mswsock.h>
#endif
#define IO_DEBUG
#ifdef IO_DEBUG
#include <stdio.h>
#define DEBUG_LOG(fmt, ...) fprintf(stderr, "Log :: %s:%d :: " fmt, __FILE__, __LINE__, ## __VA_ARGS__);
#else
#define DEBUG_LOG(...)
#endif
bool io_global_init(void)
{
#if IO_PLATFORM_WINDOWS
WSADATA data;
return WSAStartup(MAKEWORD(2, 2), &data) == NO_ERROR;
#endif
#if IO_PLATFORM_LINUX
return true;
#endif
}
void io_global_free(void)
{
#if IO_PLATFORM_WINDOWS
WSACleanup();
#endif
}
#if IO_PLATFORM_WINDOWS
bool io_init_windows(struct io_context *ioc)
{
io_os_handle os_handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
if (os_handle == INVALID_HANDLE_VALUE)
return false;
ioc->os_handle = os_handle;
return true;
}
#endif
#if IO_PLATFORM_WINDOWS
void io_free_windows(struct io_context *ioc)
{
CloseHandle(ioc->os_handle);
}
#endif
#if IO_PLATFORM_LINUX
bool io_init_linux(struct io_context *ioc)
{
struct io_uring_params p;
void *sq_ptr, *cq_ptr;
/* See io_uring_setup(2) for io_uring_params.flags you can set */
memset(&p, 0, sizeof(p));
int fd = io_uring_setup(32, &p);
if (fd < 0)
return false;
ioc->os_handle = fd;
/*
* io_uring communication happens via 2 shared kernel-user space ring
* buffers, which can be jointly mapped with a single mmap() call in
* kernels >= 5.4.
*/
int sring_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned);
int cring_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe);
/* Rather than check for kernel version, the recommended way is to
* check the features field of the io_uring_params structure, which is a
* bitmask. If IORING_FEAT_SINGLE_MMAP is set, we can do away with the
* second mmap() call to map in the completion ring separately.
*/
if (p.features & IORING_FEAT_SINGLE_MMAP) {
if (cring_sz > sring_sz)
sring_sz = cring_sz;
cring_sz = sring_sz;
}
/* Map in the submission and completion queue ring buffers.
* Kernels < 5.4 only map in the submission queue, though.
*/
sq_ptr = mmap(0, sring_sz, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_SQ_RING);
if (sq_ptr == MAP_FAILED) {
// TODO: Cleanup
return false;
}
if (p.features & IORING_FEAT_SINGLE_MMAP) {
cq_ptr = sq_ptr;
} else {
/* Map in the completion queue ring buffer in older kernels separately */
cq_ptr = mmap(0, cring_sz, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_CQ_RING);
if (cq_ptr == MAP_FAILED) {
// TODO: Cleanup
return false;
}
}
/* Save useful fields for later easy reference */
ioc->submissions.head = (_Atomic unsigned*) (sq_ptr + p.sq_off.head);
ioc->submissions.tail = (_Atomic unsigned*) (sq_ptr + p.sq_off.tail);
ioc->submissions.mask = (unsigned*) (sq_ptr + p.sq_off.ring_mask);
ioc->submissions.array = sq_ptr + p.sq_off.array;
ioc->submissions.limit = p.sq_entries;
/* Map in the submission queue entries array */
ioc->submissions.entries = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE,
fd, IORING_OFF_SQES);
if (ioc->submissions.entries == MAP_FAILED) {
// TODO: Cleanup
return false;
}
/* Save useful fields for later easy reference */
ioc->completions.head = cq_ptr + p.cq_off.head;
ioc->completions.tail = cq_ptr + p.cq_off.tail;
ioc->completions.mask = cq_ptr + p.cq_off.ring_mask;
ioc->completions.entries = cq_ptr + p.cq_off.cqes;
ioc->completions.limit = p.cq_entries;
return true;
}
#endif
#if IO_PLATFORM_LINUX
void io_free_linux(struct io_context *ioc)
{
close(ioc->os_handle);
}
#endif
#if IO_PLATFORM_LINUX
static bool start_uring_op(struct io_context *ioc,
struct io_uring_sqe sqe)
{
// TODO
return false;
}
#endif
static void clear_res(struct io_resource *res)
{
res->type = IO_RES_VOID;
res->pending = 0;
#if IO_PLATFORM_WINDOWS
res->os_handle = INVALID_HANDLE_VALUE;
#endif
#if IO_PLATFORM_LINUX
res->os_handle = -1;
#endif
}
bool io_init(struct io_context *ioc,
struct io_resource *res,
struct io_operation *ops,
uint16_t max_res,
uint16_t max_ops)
{
ioc->res = res;
ioc->ops = ops;
ioc->max_res = max_res;
ioc->max_ops = max_ops;
for (uint16_t i = 0; i < max_res; i++) {
res[i].gen = 0;
clear_res(&res[i]);
}
for (uint16_t i = 0; i < max_ops; i++)
ops[i].type = IO_VOID;
#if IO_PLATFORM_WINDOWS
return io_init_windows(ioc);
#endif
#if IO_PLATFORM_LINUX
return io_init_linux(ioc);
#endif
}
static void
close_internal(struct io_context *ioc,
struct io_resource *res)
{
#if IO_PLATFORM_WINDOWS
if (res->type == IO_RES_SOCKET)
closesocket((SOCKET) res->os_handle);
else
CloseHandle(res->os_handle);
#elif IO_PLATFORM_LINUX
close(res->os_handle);
#endif
// Mark associated operation structures as unused
for (uint16_t i = 0, marked = 0; marked < res->pending; i++) {
struct io_operation *op;
op = &ioc->ops[i];
if (op->type != IO_VOID && op->res == res) {
op->type = IO_VOID;
op->res = NULL;
marked++;
}
}
clear_res(res);
res->gen++;
if (res->gen == UINT16_MAX)
res->gen = 0;
}
static struct io_resource*
res_from_handle(struct io_context *ioc, io_handle handle)
{
if (handle == IO_INVALID)
return NULL;
static_assert(sizeof(uint32_t) == sizeof(io_handle));
uint16_t idx = handle & 0xFFFF;
uint16_t gen = handle >> 16;
if (idx >= ioc->max_res)
return NULL;
struct io_resource *res = &ioc->res[idx];
if (res->gen != gen)
return NULL;
return res;
}
static io_handle handle_from_res(struct io_context *ioc,
struct io_resource *res)
{
io_handle handle;
static_assert(sizeof(uint32_t) == sizeof(io_handle));
uint32_t idx = res - ioc->res;
uint32_t gen = res->gen;
handle = idx | (gen << 16);
assert(gen != UINT16_MAX);
assert(handle != IO_INVALID);
return handle;
}
void io_close(struct io_context *ioc,
io_handle handle)
{
struct io_resource *res;
res = res_from_handle(ioc, handle);
if (res == NULL)
return;
close_internal(ioc, res);
}
void io_free(struct io_context *ioc)
{
for (uint16_t i = 0; i < ioc->max_res; i++)
if (ioc->res[i].type != IO_RES_VOID)
close_internal(ioc, &ioc->res[i]);
#if IO_PLATFORM_WINDOWS
io_free_windows(ioc);
#endif
#if IO_PLATFORM_LINUX
io_free_linux(ioc);
#endif
}
static struct io_operation*
find_unused_op(struct io_context *ioc)
{
for (uint16_t i = 0; i < ioc->max_ops; i++) {
struct io_operation *op = &ioc->ops[i];
if (op->type == IO_VOID)
return op;
}
return NULL;
}
#if IO_PLATFORM_LINUX
static bool io_recv_linux(struct io_context *ioc,
struct io_resource *res,
struct io_operation *op,
void *dst, uint32_t max)
{
struct io_uring_sqe sqe;
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORING_OP_READ;
sqe.fd = (int) res->os_handle;
sqe.addr = (uint64_t) dst;
sqe.len = max;
sqe.user_data = (uint64_t) op;
return start_uring_op(ioc, sqe);
}
#endif
#if IO_PLATFORM_LINUX
static bool io_send_linux(struct io_context *ioc,
struct io_resource *res,
struct io_operation *op,
void *src, uint32_t num)
{
struct io_uring_sqe sqe;
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORING_OP_WRITE;
sqe.fd = (int) res->os_handle;
sqe.addr = (uint64_t) sec;
sqe.len = num;
sqe.user_data = (uint64_t) op;
return start_uring_op(ioc, sqe);
}
#endif
#if IO_PLATFORM_LINUX
static bool io_accept_linux(struct io_context *ioc,
struct io_resource *res,
struct io_operation *op,
io_os_handle os_handle)
{
struct io_uring_sqe sqe;
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORING_OP_ACCEPT;
sqe.fd = (int) os_handle;
sqe.user_data = (uint64_t) op;
return start_uring_op(ioc, sqe);
}
#endif
#if IO_PLATFORM_WINDOWS
static bool io_recv_windows(struct io_context *ioc,
struct io_resource *res,
struct io_operation *op,
void *dst, uint32_t max)
{
(void) ioc;
memset(&op->ov, 0, sizeof(struct io_os_overlap));
int ok = ReadFile(res->os_handle, dst, max, NULL, (OVERLAPPED*) &op->ov);
if (!ok && GetLastError() != ERROR_IO_PENDING)
return false;
return true;
}
#endif
#if IO_PLATFORM_WINDOWS
static bool io_send_windows(struct io_context *ioc,
struct io_resource *res,
struct io_operation *op,
void *src, uint32_t num)
{
(void) ioc;
memset(&op->ov, 0, sizeof(struct io_os_overlap));
int ok = ReadFile(res->os_handle, src, num, NULL, (OVERLAPPED*) &op->ov);
if (!ok && GetLastError() != ERROR_IO_PENDING)
return false;
return true;
}
#endif
#if IO_PLATFORM_WINDOWS
static bool io_accept_windows(struct io_context *ioc,
struct io_resource *res,
struct io_operation *op,
io_os_handle os_handle)
{
(void) ioc;
(void) res;
memset(&op->ov, 0, sizeof(struct io_os_overlap));
SOCKET new_os_handle = socket(AF_INET, SOCK_STREAM, 0);
if (new_os_handle == INVALID_SOCKET)
return false;
LPFN_ACCEPTEX lpfnAcceptEx = res->acceptfn;
_Static_assert(IO_SOCKADDR_IN_SIZE == sizeof(struct sockaddr_in));
unsigned long num;
int ok = lpfnAcceptEx((SOCKET) os_handle, new_os_handle, res->accept_buffer,
sizeof(res->accept_buffer) - ((sizeof(struct sockaddr_in) + 16) * 2),
sizeof(struct sockaddr_in) + 16,
sizeof(struct sockaddr_in) + 16,
&num, (OVERLAPPED*) &op->ov);
if (!ok && GetLastError() != ERROR_IO_PENDING) {
DEBUG_LOG("AcceptEx failure\n");
closesocket(new_os_handle);
return false;
}
op->accepted = (io_os_handle) new_os_handle;
return true;
}
#endif
bool io_recv(struct io_context *ioc,
void *user, io_handle handle,
void *dst, uint32_t max)
{
struct io_operation *op;
struct io_resource *res;
res = res_from_handle(ioc, handle);
if (res == NULL)
return false;
op = find_unused_op(ioc);
if (op == NULL)
return false;
enum io_optype type = IO_RECV;
#if IO_PLATFORM_LINUX
if (!io_recv_linux(ioc, res, op, dst, max))
return false;
#endif
#if IO_PLATFORM_WINDOWS
if (!io_recv_windows(ioc, res, op, dst, max))
return false;
#endif
res->pending++;
op->res = res;
op->type = type;
op->user = user;
return true;
}
bool io_send(struct io_context *ioc,
void *user, io_handle handle,
void *src, uint32_t num)
{
struct io_operation *op;
struct io_resource *res;
res = res_from_handle(ioc, handle);
if (res == NULL)
return false;
op = find_unused_op(ioc);
if (op == NULL)
return false;
enum io_optype type = IO_SEND;
#if IO_PLATFORM_LINUX
if (!io_send_linux(ioc, res, op, src, num))
return false;
#endif
#if IO_PLATFORM_WINDOWS
if (!io_send_windows(ioc, res, op, src, num))
return false;
#endif
res->pending++;
op->res = res;
op->type = type;
op->user = user;
return true;
}
bool io_accept(struct io_context *ioc,
void *user, io_handle handle)
{
struct io_operation *op;
struct io_resource *res;
res = res_from_handle(ioc, handle);
if (res == NULL)
return false;
op = find_unused_op(ioc);
if (op == NULL)
return false;
enum io_optype type = IO_ACCEPT;
#if IO_PLATFORM_LINUX
if (!io_accept_linux(ioc, res, op, res->os_handle))
return false;
#endif
#if IO_PLATFORM_WINDOWS
if (!io_accept_windows(ioc, res, op, res->os_handle))
return false;
#endif
res->pending++;
op->res = res;
op->type = type;
op->user = user;
return true;
}
static struct io_resource*
find_unused_res(struct io_context *ioc)
{
for (uint16_t i = 0; i < ioc->max_res; i++) {
struct io_resource *res = &ioc->res[i];
if (res->type == IO_RES_VOID)
return res;
}
return NULL;
}
#if IO_PLATFORM_WINDOWS
static io_os_handle
io_open_file_windows(struct io_context *ioc,
const char *file, int flags)
{
unsigned long access = 0;
if (flags & IO_ACCESS_RD) access |= GENERIC_READ;
if (flags & IO_ACCESS_WR) access |= GENERIC_WRITE;
io_os_handle os_handle = CreateFileA(file, access, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (os_handle == INVALID_HANDLE_VALUE)
return INVALID_HANDLE_VALUE;
if (CreateIoCompletionPort(os_handle, ioc->os_handle, 0, 0) == NULL) {
CloseHandle(os_handle);
return INVALID_HANDLE_VALUE;
}
return os_handle;
}
#endif
#if IO_PLATFORM_LINUX
static io_os_handle
io_open_file_linux(struct io_context *ioc,
const char *file, int flags)
{
int flags2 = 0;
if (flags & IO_ACCESS_RD) flags2 |= O_RDONLY;
if (flags & IO_ACCESS_WR) flags2 |= O_WRONLY;
return open(name, flags2);
}
#endif
io_handle io_open_file(struct io_context *ioc,
const char *file, int flags)
{
io_os_handle os_handle;
struct io_resource *res;
res = find_unused_res(ioc);
if (res == NULL)
return IO_INVALID;
#if IO_PLATFORM_WINDOWS
os_handle = io_open_file_windows(ioc, file, flags);
if (os_handle == INVALID_HANDLE_VALUE)
return IO_INVALID;
#endif
#if IO_PLATFORM_LINUX
os_handle = io_open_file_linux(ioc, file, flags);
if (os_handle < 0)
return IO_INVALID;
#endif
res->type = IO_RES_FILE;
res->pending = 0;
res->os_handle = os_handle;
return handle_from_res(ioc, res);
}
#if IO_PLATFORM_WINDOWS
static io_os_handle
io_create_file_windows(struct io_context *ioc,
const char *file, int flags)
{
unsigned long flags2 = 0;
if (flags & IO_CREATE_CANTEXIST)
flags2 = CREATE_NEW;
else {
if (flags & IO_CREATE_OVERWRITE)
flags2 = CREATE_ALWAYS;
else
flags2 = OPEN_ALWAYS;
}
io_os_handle os_handle = CreateFileA(file, GENERIC_WRITE, 0, NULL, flags2, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (os_handle == INVALID_HANDLE_VALUE)
return INVALID_HANDLE_VALUE;
if (CreateIoCompletionPort(os_handle, ioc->os_handle, 0, 0) == NULL) {
CloseHandle(os_handle);
return INVALID_HANDLE_VALUE;
}
return os_handle;
}
#endif
#if IO_PLATFORM_LINUX
static io_os_handle
io_create_file_linux(struct io_context *ioc,
const char *file, int flags)
{
int flags2 = O_CREAT | O_WRONLY;
if (flags & IO_CREATE_CANTEXIST)
flags2 |= O_EXCL;
else {
if (flags & IO_CREATE_OVERWRITE)
flags2 |= O_TRUNC;
}
// TODO: is 0666 ok?
return open(name, flags2, 0666);
}
#endif
io_handle io_create_file(struct io_context *ioc,
const char *file, int flags)
{
io_os_handle os_handle;
struct io_resource *res;
res = find_unused_res(ioc);
if (res == NULL)
return IO_INVALID;
#if IO_PLATFORM_WINDOWS
os_handle = io_create_file_windows(ioc, file, flags);
if (os_handle == INVALID_HANDLE_VALUE)
return IO_INVALID;
#endif
#if IO_PLATFORM_LINUX
os_handle = io_create_file_linux(ioc, file, flags);
if (os_handle < 0)
return IO_INVALID;
#endif
res->type = IO_RES_FILE;
res->pending = 0;
res->os_handle = os_handle;
return handle_from_res(ioc, res);
}
io_handle io_start_server(struct io_context *ioc,
const char *addr, int port)
{
if (port < 1 || port > UINT16_MAX)
return IO_INVALID;
struct in_addr addr2;
if (addr == NULL)
addr2.s_addr = INADDR_ANY;
else {
if (1 != inet_pton(AF_INET, addr, &addr2))
return IO_INVALID;
}
#if IO_PLATFORM_WINDOWS
SOCKET fd;
#endif
#if IO_PLATFORM_LINUX
int fd;
#endif
struct io_resource *res;
res = find_unused_res(ioc);
if (res == NULL)
return IO_INVALID;
fd = socket(AF_INET, SOCK_STREAM, 0);
#if IO_PLATFORM_LINUX
if (fd < 0)
return IO_INVALID;
#endif
#if IO_PLATFORM_WINDOWS
if (fd == INVALID_SOCKET)
return IO_INVALID;
#endif
int one = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one));
struct sockaddr_in buf;
buf.sin_family = AF_INET;
buf.sin_port = htons(port);
buf.sin_addr = addr2;
if (bind(fd, (struct sockaddr*) &buf, sizeof(buf))) {
#if IO_PLATFORM_WINDOWS
closesocket(fd);
#else
close(fd);
#endif
return IO_INVALID;
}
int backlog = 32;
if (listen(fd, backlog)) {
#if IO_PLATFORM_WINDOWS
closesocket(fd);
#else
close(fd);
#endif
return IO_INVALID;
}
#if IO_PLATFORM_WINDOWS
LPFN_ACCEPTEX lpfnAcceptEx = NULL;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
unsigned long num;
int ret = WSAIoctl(fd,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx, sizeof(GuidAcceptEx),
&lpfnAcceptEx, sizeof(lpfnAcceptEx),
&num, NULL, NULL);
if (ret == SOCKET_ERROR) {
DEBUG_LOG("WSAIoctl failure\n");
closesocket(fd);
return IO_INVALID;
}
if (CreateIoCompletionPort((HANDLE) fd, ioc->os_handle, 0, 0) == NULL) {
closesocket(fd);
return IO_INVALID;
}
res->acceptfn = lpfnAcceptEx;
#endif
res->type = IO_RES_SOCKET;
res->pending = 0;
res->os_handle = (io_os_handle) fd;
return handle_from_res(ioc, res);
}
#if IO_PLATFORM_WINDOWS
static struct io_operation*
op_from_ov(struct io_os_overlap *ov)
{
return (struct io_operation*) ((char*) ov - offsetof(struct io_operation, ov));
}
#endif
#if IO_PLATFORM_WINDOWS
static void
io_wait_windows(struct io_context *ioc,
struct io_event *ev)
{
int timeout = -1;
unsigned long timeout2;
if (timeout < 0)
timeout2 = INFINITE;
else
timeout2 = timeout;
unsigned long long unused;
struct io_os_overlap *ov;
unsigned long num;
int ok = GetQueuedCompletionStatus(ioc->os_handle, &num, &unused, (OVERLAPPED**) &ov, timeout2);
if (!ok) {
if (ov == NULL) {
/*
* General failure
*/
ev->evtype = IO_ERROR;
ev->optype = IO_VOID;
ev->handle = IO_INVALID;
ev->user = NULL;
} else {
/*
* Operation failure
*/
struct io_operation *op = op_from_ov(ov);
struct io_resource *res = op->res;
ev->evtype = IO_ABORT;
ev->optype = op->type;
ev->handle = handle_from_res(ioc, res);
ev->user = op->user;
if (op->type == IO_ACCEPT)
closesocket((SOCKET) op->accepted);
op->type = IO_VOID; // Mark unused
assert(res->pending > 0);
res->pending--;
}
return;
}
struct io_operation *op = op_from_ov(ov);
struct io_resource *res = op->res;
ev->evtype = IO_COMPLETE;
ev->optype = op->type;
ev->handle = handle_from_res(ioc, res);
ev->user = op->user;
switch (op->type) {
case IO_RECV:
case IO_SEND:
ev->num = num;
break;
case IO_ACCEPT:
{
struct io_resource *res2;
res2 = find_unused_res(ioc);
if (res2 == NULL) {
closesocket((SOCKET) op->accepted);
ev->evtype = IO_ABORT;
ev->optype = IO_ACCEPT;
ev->handle = handle_from_res(ioc, res);
ev->user = op->user;
assert(res->pending > 0);
res->pending--;
op->type = IO_VOID;
return;
}
res2->type = IO_RES_SOCKET;
res2->pending = 0;
res2->os_handle = op->accepted;
ev->accepted = handle_from_res(ioc, res2);
}
break;
default:
break;
}
assert(res->pending > 0);
res->pending--;
op->type = IO_VOID; // Mark unused
}
#endif
#if IO_PLATFORM_LINUX
static void
io_wait_linux(struct io_context *ioc,
struct io_event *ev)
{
/* --- Read barrier --- */
unsigned int head = atomic_load(ioc->completions.head);
unsigned int tail = atomic_load(ioc->completions.tail);
if (head == tail) {
/*
* Completion queue is empty. Wait for some operations to complete.
*/
int ret = io_uring_enter(ioc->handle, 0, 1, IORING_ENTER_GETEVENTS);
if (ret < 0) {
ev->evtype = IO_ERROR;
ev->optype = IO_VOID;
ev->handle = IO_INVALID;
ev->user = NULL;
return;
}
}
struct io_uring_cqe *cqe;
struct io_operation *op;
struct io_resource *res;
cqe = &ioc->completions.entries[head & (*ioc->completions.mask)];
op = (void*) cqe->user_data;
res = op->res;
ev->user = op->user;
ev->handle = handle_from_res(ioc, op->res);
ev->optype = op->type;
if (cqe->res < 0)
ev->evtype = IO_ABORT;
else {
ev->evtype = IO_COMPLETE;
switch (op->type) {
case IO_RECV: ev->num = cqe->res; break;
case IO_SEND: ev->num = cqe->res; break;
case IO_ACCEPT: ev->accepted = cqe->res; break;
default:break;
}
}
assert(res->pending > 0);
res->pending--;
op->type = IO_VOID; // Mark unused
/* --- write barrier --- */
atomic_store(ioc->completions.head, head+1);
}
#endif
void io_wait(struct io_context *ioc,
struct io_event *ev)
{
#if IO_PLATFORM_WINDOWS
io_wait_windows(ioc, ev);
#endif
#if IO_PLATFORM_LINUX
io_wait_linux(ioc, ev);
#endif
}
-155
View File
@@ -1,155 +0,0 @@
#include <stdint.h>
#include <stdbool.h>
#define IO_VERSION_MAJOR 0
#define IO_VERSION_MINOR 0
#ifdef _WIN32
# define IO_PLATFORM_WINDOWS 1
# define IO_PLATFORM_LINUX 0
# define IO_PLATFORM_OTHER 0
#elif __linux__
# define IO_PLATFORM_WINDOWS 0
# define IO_PLATFORM_LINUX 1
# define IO_PLATFORM_OTHER 0
#else
# define IO_PLATFORM_WINDOWS 0
# define IO_PLATFORM_LINUX 0
# define IO_PLATFORM_OTHER 1
#endif
#if IO_PLATFORM_WINDOWS
typedef void *io_os_handle;
#endif
#if IO_PLATFORM_LINUX
typedef int io_os_handle;
#endif
typedef uint32_t io_handle;
#define IO_INVALID ((uint32_t) -1)
struct io_os_overlap {
unsigned long *internal;
unsigned long *internal_high;
union {
struct {
unsigned long offset;
unsigned long offset_high;
};
void *pointer;
};
void *event;
};
enum io_optype {
IO_VOID,
IO_RECV,
IO_SEND,
IO_ACCEPT,
};
#define IO_SOCKADDR_IN_SIZE 16
struct io_operation {
enum io_optype type;
struct io_resource *res;
void *user;
#if IO_PLATFORM_WINDOWS
io_os_handle accepted;
struct io_os_overlap ov;
#endif
};
enum io_restype {
IO_RES_VOID,
IO_RES_FILE,
IO_RES_SOCKET,
};
struct io_resource {
enum io_restype type;
io_os_handle os_handle;
uint16_t pending;
uint16_t gen;
#if IO_PLATFORM_WINDOWS
void *acceptfn;
char accept_buffer[2 * (IO_SOCKADDR_IN_SIZE + 16)];
#endif
};
struct io_context {
io_os_handle os_handle;
uint16_t max_res;
uint16_t max_ops;
struct io_resource *res;
struct io_operation *ops;
};
enum io_evtype {
IO_ERROR,
IO_ABORT,
IO_COMPLETE,
};
struct io_event {
enum io_evtype evtype;
enum io_optype optype;
io_handle handle;
void *user;
union {
uint32_t num;
io_handle accepted;
};
};
bool io_global_init(void);
void io_global_free(void);
bool io_init(struct io_context *ioc,
struct io_resource *res,
struct io_operation *ops,
uint16_t max_res,
uint16_t max_ops);
void io_free(struct io_context *ioc);
void io_wait(struct io_context *ioc,
struct io_event *ev);
bool io_recv(struct io_context *ioc,
void *user, io_handle handle,
void *dsc, uint32_t max);
bool io_send(struct io_context *ioc,
void *user, io_handle handle,
void *src, uint32_t num);
bool io_accept(struct io_context *ioc,
void *user, io_handle handle);
void io_close(struct io_context *ioc,
io_handle handle);
/*
* Flags for io_open_file and io_create_file
*/
enum {
IO_ACCESS_RD = 1 << 0,
IO_ACCESS_WR = 1 << 1,
IO_CREATE_OVERWRITE = 1 << 2,
IO_CREATE_CANTEXIST = 1 << 3,
};
io_handle io_open_file(struct io_context *ioc,
const char *file, int flags);
io_handle io_create_file(struct io_context *ioc,
const char *file, int flags);
io_handle io_start_server(struct io_context *ioc,
const char *addr, int port);
-6
View File
@@ -1,6 +0,0 @@
all:
gcc example.c io2.c -o example -Wall -Wextra -ggdb
gcc example2.c io2.c -o example2 -Wall -Wextra -ggdb
clean:
rm example example.exe