io_uring
- A journeyio_uring
universeNow that we have our io_uring
ring setup, we can start doing something with it and I chose to continue from the stand point of a server listening for requests on a socket.
Any operation we perform is submitted to the ring’s queue to be executed asynchronously, as one would assume. The minimum steps required to submit any operation are:
sqe
)Note it’s possible to prepare various operations and submit them all with a single submit call.
After submitting an operation, we can obtain (reap) the result by calling io_uring_wait_cqe
.
I’ll assume the reader is familiar with the process of having a socket listening on a port. We’ll then start from the point where the socket is ready to accept requests, and for that, we’ll use liburing
’s io_uring_prep_multishot_accept_direct
call.
There are a few ways to submit accept requests with liburing
but we’ll use this for a few reasons.
First it’s multi-shot, which means we don’t need to submit a new accept operation everything a connection is accepted.
The other reason is the use of direct file descriptors (remember the file descriptor table registration we did last time?).
The parameters passed to io_uring_prep_multishot_accept_direct
are the ring, a submission queue and the rest are the same as you’d pass to accept4. One notable fact is the output socketaddr
value does not make much sense in a multi-shot setup since it could be overwritten by other accept operations before being read.
Putting everything together, this his how the code might look like.
int serverSocket;
// here would be the code to create, bind and put the socket in listen mode
struct io_uring *ring = malloc(sizeof(struct io_uring));
memset(ring, 0, sizeof(struct io_uring));
struct io_uring_params uringParams;
memset(&uringParams, 0, sizeof(struct io_uring_params));
uringParams.sq_thread_idle = 2; // millis before putting the worker thread to sleep
int errorCode = io_uring_queue_init_params(8, ring, &uringParams);
if (errorCode != 0) {
perror(strerror(-errorCode));
exit(1);
}
errorCode = io_uring_register_ring_fd(ring);
if (errorCode != 1) {
perror(strerror(-errorCode));
exit(1);
}
// registers 8 file descriptors
errorCode = io_uring_register_files_sparse(ring, 8);
if (errorCode != 0) {
perror(strerror(-errorCode));
exit(1);
}
// the ring is setup, let's start accepting connections
struct io_uring_sqe *sQueue = io_uring_get_sqe(ring);
io_uring_prep_multishot_accept_direct(sQueue, socketDesc, NULL, NULL, 0);
if(io_uring_submit(ring) != 1){
exit(1);
}
// here our application would be accepting connections
Not the best code but you get the idea. Just don’t copy/paste + leave it untouched.
Note that the error code returned is negative so we need to negate it again to find out what it was.
Now we need to process all the accept operations we executed and for that, we need an event loop.