异步IO -- io_uring

1. 概述

io_uring 是一种高效的 Linux 输入/输出(I/O)子系统,用于提高异步I/O的性能和灵活性。

它提供了一种比传统的 POSIX 异步 I/O 接口更高效和可扩展的方法来执行文件操作、网络通信等任务。

2. io_uing 原理图

io_uring 组成:

  • 用户空间库(liburing):用户空间程序与内核通信的接口。
  • 环形提交队列:用于向内核提交 I/O 请求的环形队列。应用程序将待执行的 I/O 请求描述符填充到提交队列中,然后通知内核开始执行相应的 I/O 操作。
  • 环形完成队列: 用于存放已完成的 I/O 请求事件的环形队列。当内核完成一个 I/O 请求时,会将相关的完成事件描述符填充到完成队列中,并通知应用程序。应用程序可以从完成队列中读取完成事件,以获取有关已完成的 I/O 请求的信息。
  • 共享内存:用户空间的应用程序和内核之间需要共享一块内存区域,用于传递提交的 I/O 请求和完成的 I/O 请求事件。

处理流程:

  1. 初始化 io_uring: 初始化共享内存的大小,环形队列的大小,以及是否启用提交和完成事件的批量处理。
  2. 应用程序提交请求:应用程序通过 mmap 填充一个或多个 I/O 操作 到 环形提交队列。
  3. 内核处理请求:内核检查提交队列中的请求,并开始执行相应的 I/O 操作。
  4. I/O 操作完成:当内核完成一个 I/O 操作时,它将相关的完成事件描述符填充到完成队列中。
  5. 应用程序处理完成事件:应用程序可以通过轮询或者异步通知等方式,从完成队列中读取完成事件描述符。

性能优势:

  1. 零拷贝:用户事件提交和内核完成事件返回都使用 mmap 。
  2. 异步 I/O:应用程序可以将I/O请求提交到io_uring的提交队列中,而无需等待这些请求的完成。
  3. 批量操作:一次性提交多个I/O请求和一次性处理多个完成事件,从而减少系统调用和上下文切换的开销,提高了系统的效率。
  4. 事件通知机制:io_uring通过epoll机制来通知应用程序提交的I/O请求是否已完成。

3. io_uing API

long io_uring_setup(u32 entries, struct io_uring_params __user *params);
作用:
	用于创建一个新的 io_uring 实例。
    它初始化了提交队列(SQ)和完成队列(CQ)并返回一个文件描述符。
参数:
	entries:提交队列的条目数,确定了可以排队的 I/O 操作数量。
    params: 用户空间指针,指向 io_uring_params 结构,用于配置和获取 io_uring 实例的信息。
返回值:
	成功时返回一个文件描述符。
	失败时返回一个负值的错误码。
----------------------------------------------------------------------------------------------------------------------     
long io_uring_enter(unsigned fd, unsigned to_submit, unsigned min_complete, unsigned flags, sigset_t *sig);
作用:
	触发内核来处理提交队列中的 I/O 请求
参数:
    fd:由 io_uring_setup 返回的 io_uring 实例的文件描述符。
    to_submit:指定要从 SQ 提交到内核的 I/O 请求的数量。
    min_complete:告诉内核在返回之前至少需要完成多少个 I/O 操作。
    flags:用于修改 io_uring_enter 的行为,例如可以设置以阻塞的方式等待。
    sig:指向 sigset_t 结构的指针,用于指定在等待时要屏蔽的信号集。
返回值:
	成功:返回已完成的请求数量。
	如果指定了 IORING_ENTER_GETEVENTS,则返回在 min_complete 参数中指定的事件数或更多。
	失败:返回一个负值的错误码。
----------------------------------------------------------------------------------------------------------------------    
long io_uring_register(unsigned fd, unsigned opcode, void *arg, unsigned nr_args);
作用:
	可以注册文件描述符、缓冲区、I/O 缓冲区、文件更新通知等
参数:
	fd:由 io_uring_setup 返回的 io_uring 实例的文件描述符。
	opcode:指定要执行的注册操作的类型,例如注册文件描述符或缓冲区。
	arg:指向要注册资源的指针。
	nr_args:指定 arg 指向的资源数量。
返回值:
	成功时通常返回 0。
	失败时返回一个负值的错误码
---------------------------------------------------------------------------------------------------------------------- 

4. liburing

liburing 是一个为了简化 io_uring 接口的使用而创建的库。它提供了一组更高级别的 API,使得开发者能够更容易地利用 Linux 的异步 I/O 功能。

io_uring_queue_init();    //初始化 io_uring 实例。
io_uring_queue_exit();    //清理和释放 io_uring 实例。
io_uring_get_sqe();       //从提交队列中获取一个提交队列条目(SQE)。
io_uring_sqe_set_data();  //给 SQE 设置用户数据。
io_uring_sqe_set_flags(); //设置 SQE 的标志。
io_uring_prep_readv();    //io_uring_prep_writev(): 准备读写向量操作的 SQE。
io_uring_prep_read_fixed(), io_uring_prep_write_fixed(); //准备带有固定缓冲区的读写操作的 SQE。
io_uring_submit();        //提交请求到内核。
io_uring_wait_cqe()       //等待至少一个完成事件。
io_uring_peek_cqe();      //非阻塞地检查是否有完成事件。
io_uring_cqe_seen();      //标记一个完成事件已被用户处理。

5. 示例

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <liburing.h>

#define ENTRIES_LENGTH		4096
#define MAX_CONNECTIONS		1024
#define BUFFER_LENGTH		1024

char buf_table[MAX_CONNECTIONS][BUFFER_LENGTH] = {0};

enum {
	READ,
	WRITE,
	ACCEPT,
};

struct conninfo {
	int connfd;
	int type;
};

// 准备并设置一个读事件到 io_uring 队列中
void set_read_event(struct io_uring *ring, int fd, void *buf, size_t len, int flags) {
	struct io_uring_sqe *sqe = io_uring_get_sqe(ring);  // 获取提交队列条目
	io_uring_prep_recv(sqe, fd, buf, len, flags);       // 准备接收请求
	struct conninfo ci = {
		.connfd = fd,
		.type = READ
	};
	memcpy(&sqe->user_data, &ci, sizeof(struct conninfo)); // 关联用户数据
	return ;
}

// 准备并设置一个写事件到 io_uring 队列中
void set_write_event(struct io_uring *ring, int fd, const void *buf, size_t len, int flags) {
	struct io_uring_sqe *sqe = io_uring_get_sqe(ring);  // 获取提交队列条目
	io_uring_prep_send(sqe, fd, buf, len, flags);       // 准备发送请求
	struct conninfo ci = {
		.connfd = fd,
		.type = WRITE
	};
	memcpy(&sqe->user_data, &ci, sizeof(struct conninfo)); // 关联用户数据
	return ;
}

// 准备并设置一个接受连接事件到 io_uring 队列中
void set_accept_event(struct io_uring *ring, int fd,
	struct sockaddr *cliaddr, socklen_t *clilen, unsigned flags) {
	struct io_uring_sqe *sqe = io_uring_get_sqe(ring);  // 获取提交队列条目
	io_uring_prep_accept(sqe, fd, cliaddr, clilen, flags); // 准备接受连接请求
	struct conninfo ci = {
		.connfd = fd,
		.type = ACCEPT
	};
	memcpy(&sqe->user_data, &ci, sizeof(struct conninfo)); // 关联用户数据
	return ;
}

int main() {
	// 创建监听套接字
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);  
	if (listenfd == -1) return -1;

	struct sockaddr_in servaddr, clientaddr;
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(9999);

	// 绑定套接字到指定端口
	if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) {
		return -2;
	}

	// 开始监听
	listen(listenfd, 10);

	struct io_uring_params params;
	memset(&params, 0, sizeof(params));
	struct io_uring ring;
	memset(&ring, 0, sizeof(ring));

	// 初始化 io_uring 实例
	io_uring_queue_init_params(ENTRIES_LENGTH, &ring, &params);

	socklen_t clilen = sizeof(clientaddr);
	// 设置接受连接的事件
	set_accept_event(&ring, listenfd, (struct sockaddr*)&clientaddr, &clilen, 0);

	while (1) {
		struct io_uring_cqe *cqe;

		// 提交所有准备好的事件到内核
		io_uring_submit(&ring);

		// 等待至少一个完成事件
		int ret = io_uring_wait_cqe(&ring, &cqe);

		struct io_uring_cqe *cqes[10];
		// 一次性检查多个完成事件
		int cqecount = io_uring_peek_batch_cqe(&ring, cqes, 10);
		unsigned count = 0;

		for (int i = 0; i < cqecount; i++) {
			cqe = cqes[i];
			count++;
			struct conninfo ci;
			// 提取用户数据
			memcpy(&ci, &cqe->user_data, sizeof(ci));

			if (ci.type == ACCEPT) {
				// 处理接受连接的事件
				int connfd = cqe->res;
				char *buffer = buf_table[connfd];
				// 设置读事件
				set_read_event(&ring, connfd, buffer, BUFFER_LENGTH, 0);
				// 重新设置接受连接的事件
				set_accept_event(&ring, listenfd, (struct sockaddr*)&clientaddr, &clilen, 0);
			} else if (ci.type == READ) {
				// 处理读取数据的事件
				int bytes_read = cqe->res;
				if (bytes_read <= 0) {
					// 客户端关闭连接或读取错误
					close(ci.connfd);
				} else {
					// 回显读取到的数据
					char *buffer = buf_table[ci.connfd];
					// 设置写事件
					set_write_event(&ring, ci.connfd, buffer, bytes_read, 0);
				}
			} else if (ci.type == WRITE) {
				// 处理写数据的事件
				char *buffer = buf_table[ci.connfd];
				// 设置读事件等待更多数据
				set_read_event(&ring, ci.connfd, buffer, BUFFER_LENGTH, 0);
			}
		}

		// 标记所有处理过的完成事件
		io_uring_cq_advance(&ring, count);
	}

	return 0;
}
  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值