文章目录
一 .io_uring简介
io_uring 是 Linux 内核提供的一种高性能的异步 I/O 框架。它旨在提供一种效率更高、更灵活的 I/O 模型,以满足现代应用程序对高吞吐量和低延迟 I/O 操作的需求。
传统的 I/O 模型(如阻塞 I/O 和非阻塞 I/O)在处理大量并发 I/O 操作时可能存在性能瓶颈。io_uring 通过提供一个高效的 I/O 操作队列和事件通知机制,允许应用程序以异步的方式提交 I/O 操作,同时避免了传统 I/O 模型中的一些开销,如系统调用的切换和数据拷贝。
二. io_uring 的主要特点
1. 异步操作
应用程序可以将 I/O 操作提交到 io_uring 队列中,并继续执行其他任务,而无需等待 I/O 操作完成。这样可以充分利用系统资源,并提高应用程序的并发性能。
2. 事件通知
io_uring 使用事件通知机制,例如使用 ring buffer 和 Completion Event Polling(完成事件轮询),以及 Linux 的 epoll 系统调用,来通知应用程序 I/O 操作的完成。
3. 零拷贝传输
io_uring 支持直接内存访问(Direct Memory Access,DMA)机制,可以在用户空间和内核空间之间进行零拷贝传输,减少了数据拷贝的开销。
4. 多功能性
io_uring 不仅支持常见的文件 I/O 操作,还可以处理网络套接字(sockets)的 I/O 操作,包括 TCP 和 UDP 的读写操作。
三 .io_uring 的系统调用函数
io_uring_setup()
用于初始化 io_uring 环。它创建了一个 io_uring 实例,并返回一个文件描述符,用于后续的 io_uring 操作。其原型为:
int io_uring_setup(unsigned entries, struct io_uring_params *p);
//参数 entries 指定了 io_uring 环的大小,而 io_uring_params 结构体包含了其他的一些初始化参数,如 io_uring 的特性和行为等。
io_uring_enter()
用于提交 I/O 操作到 io_uring 环。一旦 I/O 操作被提交,它会被内核异步处理。其原型为:
int io_uring_enter(int fd, unsigned to_submit, unsigned min_complete, unsigned flags, sigset_t *sig);
//参数 fd 是之前通过 io_uring_setup() 返回的文件描述符
to_submit 指定要提交的 I/O 操作数量
min_complete 指定最小要完成的 I/O 操作数量,flags 是控制 I/O 操作行为的一些标志
sig 是一个可选的信号集合,在完成 I/O 操作时通知应用程序
io_uring_register()
用于注册文件描述符到 io_uring 环中,以便进行 I/O 操作。其原型为:
int io_uring_register(int fd, unsigned opcode, const void *arg, unsigned nr_args);
//参数 fd 是文件描述符,opcode 是操作码,指定了要执行的操作类型,arg 是指向操作参数的指针,nr_args 是参数的数量。
四 .io_uring 常用编程函数
1. io_uring_queue_init();
用于初始化 io_uring 队列。它会分配并初始化一个 io_uring 对象,并返回一个指向该对象的指针。其原型为:
struct io_uring *io_uring_queue_init(unsigned entries, struct io_uring_params *p);
2. io_uring_prep_read(); / io_uring_prep_write();
用于准备读取和写入操作的函数。这些函数将指定的文件描述符和缓冲区等参数填充到 io_uring 操作数据结构中。其原型为:
void io_uring_prep_read(struct io_uring_sqe *sqe, int fd, void *buf, unsigned nbytes, off_t offset);
void io_uring_prep_write(struct io_uring_sqe *sqe, int fd, const void *buf, unsigned nbytes, off_t offset);
3. io_uring_submit();
用于将提交的 I/O 操作发送到 io_uring 环。其原型为:
int io_uring_submit(struct io_uring *ring);
4. io_uring_wait_cqe();
用于等待完成的 I/O 操作。当完成的操作可用时,它会返回一个指向完成的操作的指针。其原型为:
int io_uring_wait_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr);
5. io_uring_cqe_seen();
用于告知内核该完成的操作已经被处理,以便内核可以继续使用该完成队列项。其原型为:
void io_uring_cqe_seen(struct io_uring *ring, struct io_uring_cqe *cqe);
6. io_uring_peek_batch_cqe()与io_uring_wait_cqe()的区别
1.阻塞 vs 非阻塞
io_uring_wait_cqe() 是一个阻塞式函数。当你调用它时,如果没有完成的 I/O 操作,它会一直等待,直到有操作完成并且可用。
io_uring_peek_batch_cqe() 是一个非阻塞式函数。无论是否有完成的 I/O 操作,它都会立即返回。如果没有完成的操作,它返回 0。
2.处理方式
使用 io_uring_wait_cqe() 时,你等待操作完成,然后手动调用 io_uring_cqe_seen() 来告知内核这个完成队列项已经被处理过了。
使用 io_uring_peek_batch_cqe() 时,你可以立即得到完成的操作指针数组,你可以处理这些操作而不需要手动通知内核。
3.适用场景
io_uring_wait_cqe() 适用于需要等待操作完成后再继续执行的情况,比如在单线程中进行 I/O 操作的情况。
io_uring_peek_batch_cqe() 适用于不需要等待操作完成就可以继续执行的情况,比如在多线程环境中,可以同时处理多个完成的操作。
五 .io_uring 在 Linux 内核中的要求
-
Linux 内核版本:io_uring 首次引入于 Linux 内核版本 5.1,因此要使用 io_uring,需要运行至少 Linux 内核 5.1 或更高版本( [uname -r] 查看linux系统内核版本)。
-
内核配置选项:为了启用 io_uring,您需要确保您的 Linux 内核配置中启用了以下选项:
- CONFIG_IO_URING:这是 io_uring 模块的主要选项,用于启用和支持 io_uring 功能。
- CONFIG_HAVE_IO_URING:这是一个辅助选项,用于检查系统是否支持 io_uring。
请注意,内核配置选项的名称可能会因不同的 Linux 发行版和内核版本而有所不同。可以查看您正在使用的 Linux 发行版的内核配置文档,以了解如何启用 io_uring。
- 用户空间库:为了在应用程序中使用 io_uring,需要链接并使用适当的用户空间库。目前,主要的 io_uring 用户空间库是 liburing。您需要安装 liburing 库,并在编译(-luring)和链接应用程序时引用它。