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.