io_uring In Linux

Introduction To io_uring In Linux

io_uring is a new I/O System Call API in Linux. It is an asynchronous type system call.

This is available in Linux kernel & used in Application layer with the help of its system call.

io_uring is a Linux kernel system call interface for storage device asynchronous I/O operations addressing performance issues with similar interfaces provided by functions like read() / write() or aio_read() / aio_write() etc. for operations on data accessed by file descriptors.

io_uring is a Linux kernel interface that allows applications to perform high-speed, asynchronous input/output (I/O) operations using a ring buffer and a set of completion events. It was introduced in Linux kernel version 5.1 as a replacement for the older epoll interface, with the goal of providing a more efficient and scalable way of performing I/O operations.

History Of io_uring In Linux

io_uring was introduced in Linux kernel version 5.1 as a replacement for the older epoll interface, which had been the primary mechanism for performing asynchronous I/O operations in Linux since its introduction in kernel version 2.5. The old name of io_uring is aioring or it was named as aioring.

The development of io_uring began in 2018, when Jens Axboe, a kernel developer at Facebook, started working on a new I/O interface that would address some of the limitations of epoll. The goal of the new interface was to provide a more efficient and scalable way of performing I/O operations, particularly for high-concurrency workloads such as web servers and databases.

Axboe began by experimenting with various designs and prototypes, including a prototype called io_wq that used work queues to execute I/O requests. He eventually settled on a design that used a ring buffer and a set of completion events, which he called io_uring.

io_uring was merged into the Linux kernel in 2019 and was released as part of kernel version 5.1 in March 2019. Since its introduction, it has been widely adopted by applications and has become the primary mechanism for performing asynchronous I/O operations in Linux.

Need Of io_uring In Linux

With io_uring, an application can submit a batch of I/O requests to the kernel and then wait for the completion of these requests using a single system call. The kernel will then execute the requests and notify the application when they are completed using a completion event. This allows the application to avoid the overhead of making multiple system calls and can improve the performance of I/O-bound applications.

Use Of io_uring In Linux

To use io_uring, an application first needs to initialize an io_uring structure and create a ring buffer and a set of completion events. It can then submit I/O requests to the kernel using the io_uring_submit system call and wait for the completion of these requests using the io_uring_wait system call. The kernel will execute the requests and add a completion event to the set of completion events for each completed request. The application can then retrieve the completion events using the io_uring_peek_cqe system call and process them.

Overall, io_uring is a powerful Linux kernel interface that allows applications to perform high-speed, asynchronous I/O operations using a ring buffer and a set of completion events. It can improve the performance of I/O-bound applications by allowing them to submit a batch of I/O requests and wait for their completion using a single system call.

How To Use io_uring In Linux?

To use io_uring in a Linux application, you need to perform the following steps:

Step-1: Include the necessary headers:

#include <liburing.h>

Step-2: Initialize an io_uring structure and create a ring buffer and a set of completion events:

struct io_uring ring;
int ret = io_uring_queue_init(ENTRIES, &ring, 0);
if (ret < 0) {
    // handle error
}

Here, ENTRIES is the number of entries in the ring buffer.

Step-3: Set up the I/O operations you want to perform. This typically involves creating an io_uring_sqe structure and filling it with the details of the operation, such as the type of operation (read, write, etc.), the file descriptor, and the buffer.

Step-4: Submit the I/O requests to the kernel using the io_uring_submit system call:

int ret = io_uring_submit(&ring);
if (ret < 0) {
    // handle error
}

Step-5: Wait for the completion of the I/O requests using the io_uring_wait system call:

struct io_uring_cqe *cqe;
int ret = io_uring_wait_cqe(&ring, &cqe);
if (ret < 0) {
    // handle error
}

Step-6: Process the completed I/O requests. This typically involves examining the io_uring_cqe structure to determine the result of the operation and performing any necessary cleanup.

Step-7: Repeat the process as needed.

read() Operation Using io_uring In Linux

#include <liburing.h>
#include <unistd.h>

int main() {
    // Initialize the io_uring structure and create a ring buffer and completion events
    struct io_uring ring;
    int ret = io_uring_queue_init(1, &ring, 0);
    if (ret < 0) {
        // handle error
    }

    // Set up the read operation
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    io_uring_prep_read(sqe, STDIN_FILENO, sqe->buf, sqe->len, 0);

    // Submit the read request to the kernel
    ret = io_uring_submit(&ring);
    if (ret < 0) {
        // handle error
    }

    // Wait for the completion of the read request
    struct io_uring_cqe *cqe;
    ret = io_uring_wait_cqe(&ring, &cqe);
    if (ret < 0) {
        // handle error
    }

    // Process the completed read request
    if (cqe->res < 0) {
        // handle error
    } else {
        // process the data read from stdin
    }

    // Clean up
    io_uring_cqe_seen(&ring, cqe);
    io_uring_queue_exit(&ring);
    return 0;

write() Operation Using io_uring In Linux

Here is an example of using io_uring to perform a write operation:

#include <liburing.h>
#include <unistd.h>

int main() {
    // Initialize the io_uring structure and create a ring buffer and completion events
    struct io_uring ring;
    int ret = io_uring_queue_init(1, &ring, 0);
    if (ret < 0) {
        // handle error
    }

    // Set up the write operation
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    char *buf = "Hello, world!";
    io_uring_prep_write(sqe, STDOUT_FILENO, buf, strlen(buf), 0);

    // Submit the write request to the kernel
    ret = io_uring_submit(&ring);
    if (ret < 0) {
        // handle error
    }

    // Wait for the completion of the write request
    struct io_uring_cqe *cqe;
    ret = io_uring_wait_cqe(&ring, &cqe);
    if (ret < 0) {
        // handle error
    }

    // Process the completed write request
    if (cqe->res < 0) {
        // handle error
    } else {
        // data has been written to stdout
    }

    // Clean up
    io_uring_cqe_seen(&ring, cqe);
    io_uring_queue_exit(&ring);
    return 0;
}

In this example, we initialize an io_uring structure, create a ring buffer and completion events, and set up a write operation using the io_uring_prep_write function. We then submit the write request to the kernel using the io_uring_submit system call, wait for the completion of the request using the io_uring_wait_cqe system call, and process the completed request. Finally, we clean up by marking the completion event as seen and exiting the io_uring structure.


Discover more from PiEmbSysTech

Subscribe to get the latest posts sent to your email.

Leave a Reply

Scroll to Top

Discover more from PiEmbSysTech

Subscribe now to keep reading and get access to the full archive.

Continue reading